Use melt combobox over native select
This commit is contained in:
@@ -7,20 +7,20 @@
|
||||
|
||||
let {
|
||||
options,
|
||||
label,
|
||||
label = undefined,
|
||||
defaultValue = undefined,
|
||||
onChange = undefined
|
||||
}: {
|
||||
options: ComboOption[];
|
||||
label: string;
|
||||
defaultValue?: string;
|
||||
label?: string;
|
||||
defaultValue?: string | null;
|
||||
onChange?: (value: string) => void;
|
||||
} = $props();
|
||||
|
||||
let initialValueChange = true;
|
||||
const combobox = new Combobox<string>({
|
||||
onValueChange: (value) => {
|
||||
if (initialValueChange && typeof defaultValue !== 'undefined') {
|
||||
if (initialValueChange && defaultValue) {
|
||||
initialValueChange = false;
|
||||
return;
|
||||
}
|
||||
@@ -53,13 +53,15 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="field label suffix surface-container-highest">
|
||||
<div class="field suffix surface-container-highest" class:label={typeof label === 'string'}>
|
||||
<input
|
||||
{...mergeAttrs(combobox.input, {
|
||||
value: valueToLabel[combobox.input.value]
|
||||
})}
|
||||
/>
|
||||
<label {...combobox.label}>{label}</label>
|
||||
{#if label}
|
||||
<label {...combobox.label}>{label}</label>
|
||||
{/if}
|
||||
<i>keyboard_arrow_down</i>
|
||||
|
||||
<div {...combobox.content}>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import { addToast } from '../Toast.svelte';
|
||||
import { Clipboard } from '@capacitor/clipboard';
|
||||
import { downloadStringAsFile } from '$lib/misc';
|
||||
import ComboBox from '../ComboBox.svelte';
|
||||
|
||||
let remoteFilterListUrl: string = $state($filterContentUrlStore ?? '');
|
||||
let remoteError: string = $state('');
|
||||
@@ -165,17 +166,21 @@
|
||||
<article class="no-margin surface-container-high">
|
||||
<div class="grid">
|
||||
<div class="s12 m6 l6">
|
||||
<div class="label field suffix surface-container-highest">
|
||||
<select name="content-type">
|
||||
{#each filterTypes as filterType (filterType)}
|
||||
<option selected={filterType === filter.type} value={filterType}
|
||||
>{titleCase(filterType)}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
<label for="content-type">{$_('layout.filter.contentType')}</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
label={$_('layout.filter.contentType')}
|
||||
defaultValue={filter.type}
|
||||
options={filterTypes.map((filterType) => {
|
||||
return {
|
||||
label: titleCase(filterType),
|
||||
value: filterType
|
||||
};
|
||||
})}
|
||||
onChange={(value) => {
|
||||
filter.type = value as 'channel' | 'video';
|
||||
filterContentListStore.set(contentFilters);
|
||||
filterContentUrlAutoUpdateStore.set(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="s12 m6 l6 right-align">
|
||||
<button onclick={() => removeFilter(filter)} class="surface-container-highest">
|
||||
@@ -203,89 +208,70 @@
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="label field suffix surface-container-highest">
|
||||
<select
|
||||
onchange={(event: Event & { currentTarget: HTMLSelectElement }) => {
|
||||
condition.field = event.currentTarget.value;
|
||||
filterContentListStore.set(contentFilters);
|
||||
filterContentUrlAutoUpdateStore.set(false);
|
||||
}}
|
||||
name="field"
|
||||
>
|
||||
{#each Object.keys(schema[filter.type]) as key (key)}
|
||||
<option value={key} selected={condition.field === key}
|
||||
>{camelCaseToHuman(key)}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
<label for="field">{$_('layout.filter.field')}</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
label={$_('layout.filter.field')}
|
||||
defaultValue={condition.field}
|
||||
options={Object.keys(schema[filter.type]).map((key) => {
|
||||
return {
|
||||
label: camelCaseToHuman(key),
|
||||
value: key
|
||||
};
|
||||
})}
|
||||
onChange={(value) => {
|
||||
condition.field = value;
|
||||
filterContentListStore.set(contentFilters);
|
||||
filterContentUrlAutoUpdateStore.set(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="field label suffix surface-container-highest">
|
||||
<select
|
||||
onchange={(event: Event & { currentTarget: HTMLSelectElement }) => {
|
||||
condition.operator = event.currentTarget.value as z.infer<
|
||||
typeof zFilterOperatorEnum
|
||||
>;
|
||||
filterContentListStore.set(contentFilters);
|
||||
filterContentUrlAutoUpdateStore.set(false);
|
||||
}}
|
||||
name="operator"
|
||||
>
|
||||
{#each zFilterOperatorEnum.options as operator (operator)}
|
||||
<option selected={operator === condition.operator} value={operator}
|
||||
>{operator}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
<label for="operator">{$_('layout.filter.operator')}</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
label={$_('layout.filter.operator')}
|
||||
defaultValue={condition.operator}
|
||||
options={zFilterOperatorEnum.options.map((operator) => {
|
||||
return {
|
||||
label: operator,
|
||||
value: operator
|
||||
};
|
||||
})}
|
||||
onChange={(value) => {
|
||||
condition.operator = value as z.infer<typeof zFilterOperatorEnum>;
|
||||
filterContentListStore.set(contentFilters);
|
||||
filterContentUrlAutoUpdateStore.set(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
{#each condition.values as conditionValue, index (index)}
|
||||
<nav>
|
||||
{#if schema[filter.type][condition.field] === 'boolean'}
|
||||
<div class="field label suffix surface-container-highest max">
|
||||
<select
|
||||
onchange={(event: Event & { currentTarget: HTMLSelectElement }) => {
|
||||
condition.values[index] = event.currentTarget.value;
|
||||
filterContentListStore.set(contentFilters);
|
||||
filterContentUrlAutoUpdateStore.set(false);
|
||||
}}
|
||||
name="boolean-options"
|
||||
>
|
||||
<option value="" disabled selected
|
||||
>{$_('layout.filter.optionPlaceholder')}</option
|
||||
>
|
||||
<option selected={conditionValue === 'true'} value="true">true</option>
|
||||
<option selected={conditionValue === 'false'} value="false">false</option>
|
||||
</select>
|
||||
<label for="boolean-options">Value</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
label="Value"
|
||||
defaultValue={conditionValue as string}
|
||||
options={[
|
||||
{ label: 'True', value: 'true' },
|
||||
{ label: 'False', value: 'false' }
|
||||
]}
|
||||
onChange={(value) => {
|
||||
condition.values[index] = value;
|
||||
filterContentListStore.set(contentFilters);
|
||||
filterContentUrlAutoUpdateStore.set(false);
|
||||
}}
|
||||
/>
|
||||
{:else if Array.isArray(schema[filter.type][condition.field])}
|
||||
<div class="field label suffix surface-container-highest max">
|
||||
<select
|
||||
onchange={(event: Event & { currentTarget: HTMLSelectElement }) => {
|
||||
condition.values[index] = event.currentTarget.value;
|
||||
filterContentListStore.set(contentFilters);
|
||||
filterContentUrlAutoUpdateStore.set(false);
|
||||
}}
|
||||
name="array-options"
|
||||
>
|
||||
<option value="" disabled selected
|
||||
>{$_('layout.filter.optionPlaceholder')}</option
|
||||
>
|
||||
{#each schema[filter.type][condition.field] as value (value)}
|
||||
<option selected={conditionValue === value} {value}
|
||||
>{camelCaseToHuman(value)}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
<label for="array-options">Value</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
label="Value"
|
||||
defaultValue={conditionValue as string}
|
||||
options={(schema[filter.type][condition.field] as string[]).map((value) => {
|
||||
return {
|
||||
label: camelCaseToHuman(value),
|
||||
value
|
||||
};
|
||||
})}
|
||||
onChange={(value) => {
|
||||
condition.values[index] = value;
|
||||
filterContentListStore.set(contentFilters);
|
||||
filterContentUrlAutoUpdateStore.set(false);
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="field label surface-container-highest max">
|
||||
<input
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { bookmarkletSaveToUrl } from '$lib/externalSettings/index';
|
||||
import { letterCase, titleCases } from '$lib/letterCasing';
|
||||
import { letterCase, titleCases, type TitleCase } from '$lib/letterCasing';
|
||||
import { setAmoledTheme } from '$lib/theme';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import ui from 'beercss';
|
||||
@@ -38,9 +38,6 @@
|
||||
import ComboBox from '../ComboBox.svelte';
|
||||
|
||||
let invidiousInstance = $state(get(invidiousInstanceStore));
|
||||
let region = $state(get(interfaceRegionStore));
|
||||
let forceCase = $state(get(interfaceForceCase));
|
||||
let defaultPage = $state(get(interfaceDefaultPage));
|
||||
|
||||
let invalidInstance = $state(false);
|
||||
let colorPickerOpen = $state(false);
|
||||
@@ -396,58 +393,41 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="field label suffix surface-container-highest">
|
||||
<select
|
||||
tabindex="0"
|
||||
name="region"
|
||||
bind:value={region}
|
||||
onchange={() => interfaceRegionStore.set(region)}
|
||||
>
|
||||
{#each iso31661 as region (region)}
|
||||
<option selected={$interfaceRegionStore === region.alpha2} value={region.alpha2}
|
||||
>{region.alpha2} - {region.name}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
<label tabindex="-1" for="region">{$_('region')}</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
label={$_('region')}
|
||||
defaultValue={$interfaceRegionStore}
|
||||
options={iso31661.map((region) => {
|
||||
return {
|
||||
label: region.name,
|
||||
value: region.alpha2
|
||||
};
|
||||
})}
|
||||
onChange={(value) => interfaceRegionStore.set(value)}
|
||||
/>
|
||||
|
||||
<div class="field label suffix surface-container-highest">
|
||||
<select
|
||||
tabindex="0"
|
||||
name="case"
|
||||
bind:value={forceCase}
|
||||
onchange={() => interfaceForceCase.set(forceCase)}
|
||||
>
|
||||
<option selected={$interfaceForceCase === null} value={null}>Default</option>
|
||||
{#each titleCases as caseType (caseType)}
|
||||
<option selected={$interfaceForceCase === caseType} value={caseType}
|
||||
>{letterCase(`${caseType}`, caseType)}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
<label tabindex="-1" for="case">{$_('letterCase')}</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
label={$_('letterCase')}
|
||||
defaultValue={$interfaceForceCase}
|
||||
options={titleCases.map((caseType) => {
|
||||
return {
|
||||
label: letterCase(`${caseType}`, caseType),
|
||||
value: caseType as string
|
||||
};
|
||||
})}
|
||||
onChange={(value) => interfaceForceCase.set(value as TitleCase)}
|
||||
/>
|
||||
|
||||
<div class="field label suffix surface-container-highest">
|
||||
<select
|
||||
name="defaultPage"
|
||||
tabindex="0"
|
||||
bind:value={defaultPage}
|
||||
onchange={() => interfaceDefaultPage.set(defaultPage)}
|
||||
>
|
||||
{#each pages as page (page)}
|
||||
{#if !page.requiresAuth || get(invidiousAuthStore)}
|
||||
<option selected={$interfaceDefaultPage === page.href} value={page.href}>{page.name}</option
|
||||
>
|
||||
{/if}
|
||||
{/each}
|
||||
</select>
|
||||
<label tabindex="-1" for="defaultPage">{$_('defaultPage')}</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
label={$_('defaultPage')}
|
||||
defaultValue={$interfaceDefaultPage}
|
||||
options={pages.map((page) => {
|
||||
return {
|
||||
label: page.name,
|
||||
value: page.href
|
||||
};
|
||||
})}
|
||||
onChange={(value) => interfaceDefaultPage.set(value)}
|
||||
/>
|
||||
|
||||
{#if !Capacitor.isNativePlatform()}
|
||||
<div class="space"></div>
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
} from '../../store';
|
||||
import { playbackRates } from '$lib/player/index';
|
||||
import { isUnrestrictedPlatform } from '$lib/misc';
|
||||
|
||||
let defaultLanguage = $state(get(playerDefaultLanguage));
|
||||
import ComboBox from '../ComboBox.svelte';
|
||||
|
||||
let localVideoFallback: 'enabled' | 'disabled' | 'always' = $state('enabled');
|
||||
if (!$playerYouTubeJsFallback) {
|
||||
@@ -38,18 +37,10 @@
|
||||
ISO6391.getName(code).toLocaleLowerCase()
|
||||
);
|
||||
|
||||
function onQualityChange(event: Event) {
|
||||
const value = (event.target as HTMLSelectElement).value;
|
||||
playerDefaultQualityStore.set(value);
|
||||
}
|
||||
languageNames.unshift('original');
|
||||
|
||||
function onSpeedChange(event: Event) {
|
||||
const value = (event.target as HTMLSelectElement).value;
|
||||
playerDefaultPlaybackSpeed.set(Number(value));
|
||||
}
|
||||
|
||||
function onLocalVideoFallbackChange() {
|
||||
switch (localVideoFallback) {
|
||||
function onLocalVideoFallbackChange(value: string) {
|
||||
switch (value) {
|
||||
case 'enabled':
|
||||
playerYouTubeJsFallback.set(true);
|
||||
playerYouTubeJsAlways.set(false);
|
||||
@@ -67,79 +58,62 @@
|
||||
</script>
|
||||
|
||||
<div class="margin"></div>
|
||||
<div class="field label suffix surface-container-highest">
|
||||
<select
|
||||
tabindex="0"
|
||||
name="case"
|
||||
bind:value={defaultLanguage}
|
||||
onchange={() => playerDefaultLanguage.set(defaultLanguage)}
|
||||
>
|
||||
<option selected={$playerDefaultLanguage === 'original'} value="original">Original</option>
|
||||
{#each languageNames as language (language)}
|
||||
<option selected={$playerDefaultLanguage === language} value={language}
|
||||
>{titleCase(language)}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
<label tabindex="-1" for="case">{$_('player.defaultLanguage')}</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
label={$_('player.defaultLanguage')}
|
||||
defaultValue={$playerDefaultLanguage}
|
||||
options={languageNames.map((language) => {
|
||||
return {
|
||||
label: titleCase(language),
|
||||
value: language
|
||||
};
|
||||
})}
|
||||
onChange={(value) => playerDefaultLanguage.set(value)}
|
||||
/>
|
||||
|
||||
<div class="field label suffix surface-container-highest">
|
||||
<select
|
||||
tabindex="0"
|
||||
name="quality"
|
||||
id="quality"
|
||||
bind:value={$playerDefaultQualityStore}
|
||||
onchange={onQualityChange}
|
||||
>
|
||||
<option value="auto">Auto (Recommended)</option>
|
||||
<option value="144">144p (Ultra low)</option>
|
||||
<option value="240">240p (Low)</option>
|
||||
<option value="360">360p (SD)</option>
|
||||
<option value="480">480p (SD+)</option>
|
||||
<option value="720">720p (HD)</option>
|
||||
<option value="1080">1080p (Full HD)</option>
|
||||
<option value="1440">1440p (2K)</option>
|
||||
<option value="2160">2160p (4K UHD)</option>
|
||||
</select>
|
||||
<label tabindex="-1" for="quality">{$_('player.preferredQuality')}</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
label={$_('player.preferredQuality')}
|
||||
defaultValue={$playerDefaultQualityStore}
|
||||
onChange={(value) => playerDefaultQualityStore.set(value)}
|
||||
options={[
|
||||
{ label: 'Auto (Recommended)', value: 'auto' },
|
||||
{ label: '144p (Ultra low)', value: '144' },
|
||||
{ label: '240p (Low)', value: '240' },
|
||||
{ label: '360p (SD)', value: '360' },
|
||||
{ label: '480p (SD+)', value: '480' },
|
||||
{ label: '720p (HD)', value: '720' },
|
||||
{ label: '1080p (Full HD)', value: '1080' },
|
||||
{ label: '1440p (2K)', value: '1440' },
|
||||
{ label: '2160p (4K UHD)', value: '2160' }
|
||||
]}
|
||||
/>
|
||||
|
||||
<div class="field label suffix surface-container-highest">
|
||||
<select
|
||||
tabindex="0"
|
||||
name="quality"
|
||||
id="quality"
|
||||
bind:value={$playerDefaultPlaybackSpeed}
|
||||
onchange={onSpeedChange}
|
||||
>
|
||||
{#each playbackRates as speed (speed)}
|
||||
<option value={speed}>x{speed}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<label tabindex="-1" for="quality">{$_('layout.player.defaultPlaybackSpeed')}</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
label={$_('layout.player.defaultPlaybackSpeed')}
|
||||
defaultValue={$playerDefaultPlaybackSpeed.toString()}
|
||||
options={playbackRates.map((rate) => {
|
||||
return {
|
||||
label: `x${rate}`,
|
||||
value: rate.toString()
|
||||
};
|
||||
})}
|
||||
onChange={(value) => playerDefaultPlaybackSpeed.set(Number(value))}
|
||||
/>
|
||||
|
||||
{#if isUnrestrictedPlatform() && $backendInUseStore === 'ivg'}
|
||||
<div class="field suffix surface-container-highest label">
|
||||
<select
|
||||
tabindex="0"
|
||||
name="ytfallback"
|
||||
bind:value={localVideoFallback}
|
||||
onchange={onLocalVideoFallbackChange}
|
||||
>
|
||||
<option value="enabled">{$_('enabled')}</option>
|
||||
<option value="always">{$_('layout.player.youtubeJsAlways')}</option>
|
||||
<option value="disabled">{$_('disabled')}</option>
|
||||
</select>
|
||||
<label tabindex="-1" for="ytfallback">{$_('layout.player.localVideoFallback')}</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
label={$_('layout.player.localVideoFallback')}
|
||||
defaultValue={localVideoFallback}
|
||||
options={[
|
||||
{ label: $_('enabled'), value: 'enabled' },
|
||||
{ label: $_('layout.player.youtubeJsAlways'), value: 'always' },
|
||||
{ label: $_('disabled'), value: 'disabled' }
|
||||
]}
|
||||
onChange={(value) => onLocalVideoFallbackChange(value)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="space"></div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
sponsorBlockTimelineStore,
|
||||
sponsorBlockUrlStore
|
||||
} from '../../store';
|
||||
import ComboBox from '../ComboBox.svelte';
|
||||
|
||||
let sponsorBlockInstance = $state(get(sponsorBlockUrlStore));
|
||||
|
||||
@@ -22,11 +23,8 @@
|
||||
{ name: $_('layout.sponsors.tangentJokes'), category: 'filler' }
|
||||
];
|
||||
|
||||
function onSponsorSet(
|
||||
category: string,
|
||||
event: Event & { currentTarget: EventTarget & HTMLSelectElement }
|
||||
) {
|
||||
const value = event.currentTarget.value as 'automatic' | 'manual' | 'timeline' | 'disabled';
|
||||
function onSponsorSet(category: string, givenValue: string) {
|
||||
const value = givenValue as 'automatic' | 'manual' | 'timeline' | 'disabled';
|
||||
|
||||
const categories = get(sponsorBlockCategoriesStore);
|
||||
|
||||
@@ -112,29 +110,23 @@
|
||||
<p class="bold">{$_('layout.sponsors.Catagories')}</p>
|
||||
|
||||
{#each sponsorCategories as sponsor (sponsor)}
|
||||
{@const currentCatergoryTrigger = $sponsorBlockCategoriesStore[sponsor.category]}
|
||||
<div class="field middle-align no-margin">
|
||||
{@const currentCategoryTrigger = $sponsorBlockCategoriesStore[sponsor.category]}
|
||||
|
||||
<div class="field middle-align">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<p>{sponsor.name}</p>
|
||||
</div>
|
||||
<div class="field suffix surface-container-highest">
|
||||
<select onchange={(event) => onSponsorSet(sponsor.category, event)}>
|
||||
<option selected={currentCatergoryTrigger === undefined} value="disabled"
|
||||
>{$_('disabled')}</option
|
||||
>
|
||||
<option selected={currentCatergoryTrigger === 'automatic'} value="automatic"
|
||||
>{$_('layout.sponsors.automatic')}</option
|
||||
>
|
||||
<option selected={currentCatergoryTrigger === 'manual'} value="manual"
|
||||
>{$_('layout.sponsors.manual')}</option
|
||||
>
|
||||
<option selected={currentCatergoryTrigger === 'timeline'} value="timeline"
|
||||
>{$_('layout.sponsors.timeline')}</option
|
||||
>
|
||||
</select>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<ComboBox
|
||||
options={[
|
||||
{ label: $_('disabled'), value: 'disabled' },
|
||||
{ label: $_('layout.sponsors.automatic'), value: 'automatic' },
|
||||
{ label: $_('layout.sponsors.manual'), value: 'manual' },
|
||||
{ label: $_('layout.sponsors.timeline'), value: 'timeline' }
|
||||
]}
|
||||
defaultValue={currentCategoryTrigger ?? 'disabled'}
|
||||
onChange={(value) => onSponsorSet(sponsor.category, value)}
|
||||
/>
|
||||
</nav>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -199,6 +199,8 @@ menu.player-settings {
|
||||
padding: 1rem;
|
||||
font-size: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
overflow-y: scroll;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
[data-melt-combobox-option] {
|
||||
|
||||
Reference in New Issue
Block a user