Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

let {
value = $bindable(),
...constProps

Check warning on line 18 in frontend/viewer/src/lib/components/field-editors/select.svelte

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

frontend/viewer/src/lib/components/field-editors/select.svelte#L18

[svelte/valid-compile] Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option. https://svelte.dev/e/custom_element_props_identifier(custom_element_props_identifier)
}: {
value?: Value;
options: ReadonlyArray<Value>;
Expand All @@ -29,6 +29,7 @@
emptyResultsPlaceholder?: string;
drawerTitle?: string;
onchange?: (value: Value) => void;
class?: string;
} = $props();

const {
Expand All @@ -41,6 +42,7 @@
emptyResultsPlaceholder,
drawerTitle,
onchange,
class: className
} = $derived(constProps);

function getId(value: Value): Primitive {
Expand Down Expand Up @@ -89,7 +91,7 @@

{#snippet trigger({ props }: { props: Record<string, unknown> })}
<Button disabled={readonly} bind:ref={triggerRef} variant="outline" {...props} role="combobox" aria-expanded={open}
class="w-full h-auto px-2 justify-between disabled:opacity-100 disabled:border-transparent">
class={cn('w-full h-auto px-2 justify-between disabled:opacity-100 disabled:border-transparent', className)}>
{#if value}
<span>
{getLabel(value)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
import {cn} from '$lib/utils.js';
import {Select as SelectPrimitive, type WithoutChild} from 'bits-ui';
import {Icon} from '../icon';
import type {IconClass} from '$lib/icon-class';

let {
ref = $bindable(null),
class: className,
downIcon = 'i-mdi-chevron-down',
children,
...restProps

Check warning on line 12 in frontend/viewer/src/lib/components/ui/select/select-trigger.svelte

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

frontend/viewer/src/lib/components/ui/select/select-trigger.svelte#L12

[svelte/valid-compile] Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option. https://svelte.dev/e/custom_element_props_identifier(custom_element_props_identifier)
}: WithoutChild<SelectPrimitive.TriggerProps> = $props();
}: WithoutChild<SelectPrimitive.TriggerProps> & {
downIcon: IconClass | null;
} = $props();
</script>

<SelectPrimitive.Trigger
Expand All @@ -20,5 +24,7 @@
{...restProps}
>
{@render children?.()}
<Icon icon="i-mdi-chevron-down" class="size-4 opacity-50" />
{#if downIcon}
<Icon icon={downIcon} class="size-4 opacity-50" />
{/if}
</SelectPrimitive.Trigger>
6 changes: 6 additions & 0 deletions frontend/viewer/src/lib/writing-system-service.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {type ResourceReturn} from 'runed';
export type WritingSystemSelection =
| 'vernacular'
| 'analysis'
| 'vernacular-no-audio'
| 'analysis-no-audio'
| 'first-vernacular'
| 'first-analysis'
| 'vernacular-analysis'
Expand Down Expand Up @@ -88,6 +90,10 @@ export class WritingSystemService {
return this.writingSystems.vernacular;
case 'analysis':
return this.writingSystems.analysis;
case 'vernacular-no-audio':
return this.vernacularNoAudio;
case 'analysis-no-audio':
return this.analysisNoAudio;
}
console.error(`Unknown writing system selection: ${ws as string}`);
return [];
Expand Down
2 changes: 1 addition & 1 deletion frontend/viewer/src/project/browse/BrowseView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
collapsible
collapsedSize={0}
minSize={15}
class="min-h-0 flex flex-col relative"
class="min-h-0 flex flex-col relative @container/list"
>
<div class="flex flex-col h-full p-2 md:p-4 md:pr-0">
<div class="md:mr-3">
Expand Down
63 changes: 63 additions & 0 deletions frontend/viewer/src/project/browse/SearchFilter.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@
import {useCurrentView} from '$lib/views/view-service';
import {formatNumber} from '$lib/components/ui/format';
import ViewT from '$lib/views/ViewT.svelte';
import Select from '$lib/components/field-editors/select.svelte';
import { Input } from '$lib/components/ui/input';
import {watch} from 'runed';
import OpFilter, {type Op} from './filter/OpFilter.svelte';
import WsSelect from './filter/WsSelect.svelte';
import {useWritingSystemService} from '$lib/writing-system-service.svelte';
const stats = useProjectStats();
const currentView = useCurrentView();
const wsService = useWritingSystemService();
let {
search = $bindable(),
Expand All @@ -28,6 +35,22 @@
let missingSenses = $state(false);
let missingPartOfSpeech = $state(false);
let missingSemanticDomains = $state(false);
let fields: Array<{id: string, label: string, ws: 'vernacular-no-audio' | 'analysis-no-audio'}> = $derived([
{ id: 'lexemeForm', label: pt($t`Lexeme Form`, $t`Word`, $currentView), ws: 'vernacular-no-audio' },
{ id: 'citationForm', label: pt($t`Citation Form`, $t`Display as`, $currentView), ws: 'vernacular-no-audio' },
{ id: 'senses.gloss', label: $t`Gloss`, ws: 'analysis-no-audio' },
]);
// svelte-ignore state_referenced_locally
let selectedField = $state(fields[0]);
let selectedWs = $state<string[]>(wsService.vernacularNoAudio.map(ws => ws.wsI));
watch(() => fields, fields => {
//updates selected field when selected view changes
selectedField = fields.find(f => f.id === selectedField.id) ?? fields[0];
});
let fieldFilterValue = $state('');
let filterOp = $state<Op>('contains')
$effect(() => {
let newFilter: string[] = [];
if (missingExamples) {
Expand All @@ -42,6 +65,22 @@
if (missingSemanticDomains) {
newFilter.push('Senses.SemanticDomains=null')
}
if (fieldFilterValue && selectedWs?.length > 0) {
let op: string;
switch (filterOp) {
case 'starts-with': op = '^'; break;
case 'contains': op = '=*'; break;
case 'ends-with': op = '$'; break;
case 'equals': op = '='; break;
case 'not-equals': op = '!='; break;
}
let fieldFilter = [];
for (let ws of selectedWs) {
fieldFilter.push(`${selectedField.id}[${ws}]${op}${fieldFilterValue}`);
}
//construct a filter like LexemeForm[en]=value|LexemeForm[fr]=value
newFilter.push('(' + fieldFilter.join('|') + ')')
}
gridifyFilter = newFilter.join(', ');
});
Expand Down Expand Up @@ -78,6 +117,30 @@
</ComposableInput>
</div>
<Collapsible.Content class="p-2 mb-2 space-y-2">
<div class="flex flex-col @md/list:flex-row gap-2 items-stretch">
<div class="flex flex-row gap-2 flex-1">
<!-- Field Picker -->
<Select
bind:value={selectedField}
options={fields}
idSelector="id"
labelSelector="label"
placeholder={$t`Field`}
class="flex-1"
/>
<!-- Writing System Picker -->
<WsSelect bind:value={selectedWs} wsType={selectedField.ws} />
</div>
<!-- Text Box: on mobile, wraps to new line -->
<div class="flex flex-row gap-2 flex-1">
<OpFilter bind:value={filterOp}/>
<Input
bind:value={fieldFilterValue}
placeholder={$t`Filter for`}
class="flex-1"
/>
</div>
</div>
<Switch bind:checked={missingExamples} label={$t`Missing Examples`} />
<Switch bind:checked={missingSenses} label={$t`Missing Senses`} />
<Switch bind:checked={missingPartOfSpeech} label={$t`Missing Part of Speech`} />
Expand Down
35 changes: 35 additions & 0 deletions frontend/viewer/src/project/browse/filter/OpFilter.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script lang="ts" module>
export type Op = 'starts-with' | 'contains' | 'ends-with' | 'equals' | 'not-equals';
</script>

<script lang="ts">
import * as Select from '$lib/components/ui/select';
import {Icon} from '$lib/components/ui/icon';
import {t} from 'svelte-i18n-lingui';
import type {IconClass} from '$lib/icon-class';
let {value = $bindable()}: {value: string} = $props();
const ops: {label: string, value: Op, icon: IconClass}[] = [
{label: $t`Starts with`, value: 'starts-with', icon: 'i-mdi-contain-start'},
{label: $t`Contains`, value: 'contains', icon: 'i-mdi-contain'},
{label: $t`Ends with`, value: 'ends-with', icon: 'i-mdi-contain-end'},
{label: $t`Equals`, value: 'equals', icon: 'i-mdi-equal'},
{label: $t`Not equal`, value: 'not-equals', icon: 'i-mdi-not-equal-variant'},
] ;
</script>

<Select.Root type="single" bind:value>
<Select.Trigger class="w-13" downIcon={null}>
<Icon icon={ops.find(o => o.value === value)?.icon ?? 'i-mdi-close'}/>
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.GroupHeading>Filter By</Select.GroupHeading>
{#each ops as op}
<Select.Item value={op.value}>
<Icon icon={op.icon} class="mr-2"/>
{op.label}
</Select.Item>
{/each}
</Select.Group>
</Select.Content>
</Select.Root>
33 changes: 33 additions & 0 deletions frontend/viewer/src/project/browse/filter/WsSelect.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script lang="ts">
import * as Select from '$lib/components/ui/select';
import {useWritingSystemService, type WritingSystemSelection} from '$lib/writing-system-service.svelte';
import {t} from 'svelte-i18n-lingui';
const wsService = useWritingSystemService();
let {value = $bindable(), wsType}: { value: string[] | undefined, wsType: WritingSystemSelection } = $props();
let writingSystems = $derived(wsService.pickWritingSystems(wsType));
</script>
<Select.Root type="multiple" bind:value>
<Select.Trigger class="flex-1">
{#if value.length === 0}
<span class="text-muted-foreground">{$t`Writing System`}</span>
{:else if value.length === writingSystems.length}
<span class="text-muted-foreground">
{$t`Any Ws`}
</span>
{:else}
{writingSystems.filter(w => value.includes(w.wsId)).map(w => w.abbreviation).join(', ')}
{/if}
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.GroupHeading>{$t`Writing Systems`}</Select.GroupHeading>
{#each writingSystems as ws}
<Select.Item value={ws.wsId}>
{ws.abbreviation} ({ws.wsId})
</Select.Item>
{/each}
</Select.Group>
</Select.Content>
</Select.Root>
Loading