Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 5 additions & 1 deletion backend/FwLite/FwLiteWeb/Routes/MiniLcmRoutes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using MiniLcm;
using MiniLcm.Filtering;
using MiniLcm.Models;
using MiniLcm.Project;
using MiniLcm.Validators;
Expand Down Expand Up @@ -168,7 +169,8 @@ public QueryOptions ToQueryOptions()
Ascending ?? SortOptions.Default.Ascending),
exemplarOptions,
Count ?? QueryOptions.Default.Count,
Offset ?? QueryOptions.Default.Offset);
Offset ?? QueryOptions.Default.Offset,
string.IsNullOrEmpty(GridifyFilter) ? null : new EntryFilter {GridifyFilter = GridifyFilter});
}

public SortField? SortField { get; set; } = SortOptions.Default.Field;
Expand All @@ -191,5 +193,7 @@ public QueryOptions ToQueryOptions()

[FromQuery]
public int? Offset { get; set; }
[FromQuery]
public string? GridifyFilter { get; set; }
}
}
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));

Check failure on line 46 in frontend/viewer/src/project/browse/SearchFilter.svelte

View workflow job for this annotation

GitHub Actions / check-and-lint

Property 'wsI' does not exist on type 'IWritingSystem'. Did you mean 'wsId'?
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">

Check failure on line 12 in frontend/viewer/src/project/browse/filter/WsSelect.svelte

View workflow job for this annotation

GitHub Actions / check-and-lint

Type '{ children: () => any; class: string; }' is not assignable to type 'Omit<SelectTriggerProps, \"child\"> & { downIcon: IconClass | null; }'.\n Property 'downIcon' is missing in type '{ children: () => any; class: string; }' but required in type '{ downIcon: IconClass | null; }'.
{#if value.length === 0}

Check failure on line 13 in frontend/viewer/src/project/browse/filter/WsSelect.svelte

View workflow job for this annotation

GitHub Actions / check-and-lint

'value' is possibly 'undefined'.
<span class="text-muted-foreground">{$t`Writing System`}</span>
{:else if value.length === writingSystems.length}

Check failure on line 15 in frontend/viewer/src/project/browse/filter/WsSelect.svelte

View workflow job for this annotation

GitHub Actions / check-and-lint

'value' is possibly 'undefined'.
<span class="text-muted-foreground">
{$t`Any Ws`}
</span>
{:else}
{writingSystems.filter(w => value.includes(w.wsId)).map(w => w.abbreviation).join(', ')}

Check failure on line 20 in frontend/viewer/src/project/browse/filter/WsSelect.svelte

View workflow job for this annotation

GitHub Actions / check-and-lint

'value' is possibly 'undefined'.
{/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