From 88eaf83f9fe2c9040345898f27ebb5ec634cf34f Mon Sep 17 00:00:00 2001 From: AaronPlave Date: Mon, 8 Dec 2025 15:41:27 -0500 Subject: [PATCH 1/2] Filter builder refactor WIP --- .../ActivityFilterBuilder.svelte | 767 +++--------------- .../ExternalEventFilterBuilder.svelte | 728 ++--------------- .../form/TimelineEditor/FilterBuilder.svelte | 694 ++++++++++++++++ 3 files changed, 866 insertions(+), 1323 deletions(-) create mode 100644 src/components/timeline/form/TimelineEditor/FilterBuilder.svelte diff --git a/src/components/timeline/form/TimelineEditor/ActivityFilterBuilder.svelte b/src/components/timeline/form/TimelineEditor/ActivityFilterBuilder.svelte index 1c4debc3c2..f151c62715 100644 --- a/src/components/timeline/form/TimelineEditor/ActivityFilterBuilder.svelte +++ b/src/components/timeline/form/TimelineEditor/ActivityFilterBuilder.svelte @@ -1,10 +1,6 @@ -
- - {#if shown} - -
- - - - -
-
- -
-
-
- Manually Select Types - {#if dirtyFilter.static_types?.length} - - {/if} -
-
-
- -
- { - requestAnimationFrame(() => { - if (!manualInputOpen) { - manualInputOpen = true; - } - }); - }} - /> - -
-
- (manualInputOpen = false)} - > -
- {#if filteredActivityTypes.length > 0} - onAddAllManualTypes()}> -
- Add {filteredActivityTypes.length !== $planModelActivityTypes.length ? 'Matching' : 'All'} + -
-
- {#each filteredActivityTypes as type} - onManualTypeToggled(type.name)}> - -1} - tabindex={-1} - style:pointer-events="none" - /> -
{type.name}
-
- {/each} - {:else} - No activities matching your filter - {/if} -
-
-
- {#if dirtyFilter.static_types?.length} -
- {#each dirtyFilter.static_types as name} - onManualTypeToggled(name)} /> - {/each} -
- {/if} -
-
-
-
-
- Dynamically Select Types -
Type includes...
-
- -
- {#if dirtyFilter.dynamic_type_filters?.length} -
-
- {#each dirtyFilter.dynamic_type_filters as filter, i (filter.id)} - onDynamicFilterRemove('dynamic_type_filters', filter.id)} - on:change={event => onDynamicFilterChange('dynamic_type_filters', event)} - verb={i === 0 ? 'Where' : 'and'} - schema={{ - Subsystem: { - does_not_include: { type: 'tag', values: $subsystemTags }, - includes: { type: 'tag', values: $subsystemTags }, - }, - Type: { - does_not_equal: { type: 'variant', values: $planModelActivityTypes.map(type => type.name) }, - does_not_include: { type: 'string' }, - equals: { type: 'variant', values: $planModelActivityTypes.map(type => type.name) }, - includes: { type: 'string' }, - }, - }} - /> - {/each} -
-
- {/if} -
-
-
-
- Other Filters -
Tags, parameter, scheduling goal, etc...
-
- -
- {#if dirtyFilter.other_filters?.length} -
-
- {#each dirtyFilter.other_filters as filter, i (filter.id)} - onDynamicFilterRemove('other_filters', filter.id)} - on:change={event => onDynamicFilterChange('other_filters', event)} - verb={i === 0 ? 'Where' : 'and'} - schema={{ - Name: { - does_not_equal: { type: 'string' }, - does_not_include: { type: 'string' }, - equals: { type: 'string' }, - includes: { type: 'string' }, - }, - Parameter: { - subfields: parameterSubfields, - }, - SchedulingGoalId: { - does_not_equal: { type: 'int' }, - equals: { type: 'int' }, - }, - Tags: { - does_not_include: { type: 'tag', values: $tags }, - includes: { type: 'tag', values: $tags }, - }, - }} - /> - {/each} -
-
- {/if} -
-
- - - -
-
- Resulting Types -
-
{matchingTypes.length} types
-
{instanceCount} instance{pluralize(instanceCount)}
-
-
- -
- - -
- {#each filteredMatchingTypes as type} - - - - {#if dirtyFilter.type_subfilters && dirtyFilter.type_subfilters[type.name] && dirtyFilter.type_subfilters[type.name].length} -
- {#each dirtyFilter.type_subfilters[type.name] as filter (filter.id)} - onTypeSubfilterRemove(type.name, filter.id)} - on:change={event => onTypeSubfilterChange(type.name, event)} - verb={''} - schema={{ - Name: { - does_not_equal: { type: 'string' }, - does_not_include: { type: 'string' }, - equals: { type: 'string' }, - includes: { type: 'string' }, - }, - Parameter: { - // Filter subfields to only those matching this type - subfields: parameterSubfields.filter(subfield => { - return subfield.activityTypes.indexOf(type.name) > -1; - }), - }, - SchedulingGoalId: { - does_not_equal: { type: 'int' }, - equals: { type: 'int' }, - }, - Tags: { - does_not_include: { type: 'tag', values: $tags }, - includes: { type: 'tag', values: $tags }, - }, - }} - /> - {/each} -
- {/if} -
-
- {/each} - {#if resultingTypesMessage} -
{resultingTypesMessage}
- {/if} -
-
-
-
- -
- {/if} -
- - + + + + diff --git a/src/components/timeline/form/TimelineEditor/ExternalEventFilterBuilder.svelte b/src/components/timeline/form/TimelineEditor/ExternalEventFilterBuilder.svelte index 549ee2fda4..22e7becfea 100644 --- a/src/components/timeline/form/TimelineEditor/ExternalEventFilterBuilder.svelte +++ b/src/components/timeline/form/TimelineEditor/ExternalEventFilterBuilder.svelte @@ -1,221 +1,63 @@ -
- - {#if shown} - -
- - - - -
-
- -
-
-
- Manually Select Types - {#if dirtyFilter.static_types?.length} - - {/if} -
-
-
- -
- { - requestAnimationFrame(() => { - if (!manualInputOpen) { - manualInputOpen = true; - } - }); - }} - /> - -
-
- (manualInputOpen = false)} - > -
- {#if filteredExternalEventTypes.length > 0} - onAddAllManualTypes()}> -
- Add {filteredExternalEventTypes.length !== $externalEventTypes.length ? 'Matching' : 'All'} + -
-
- {#each filteredExternalEventTypes as type} - onManualTypeToggled(type.name)}> - -1} - tabindex={-1} - style:pointer-events="none" - /> -
{type.name}
-
- {/each} - {:else} - No external events matching your filter - {/if} -
-
-
- {#if dirtyFilter.static_types?.length} -
- {#each dirtyFilter.static_types as name} - - onManualTypeToggled(name)} /> - {/each} -
- {/if} -
-
-
-
-
- Dynamically Select Types -
Type includes...
-
- -
- {#if dirtyFilter.dynamic_type_filters?.length} -
-
- {#each dirtyFilter.dynamic_type_filters as filter, i (filter.id)} - onDynamicFilterRemove('dynamic_type_filters', filter.id)} - on:change={event => onDynamicFilterChange('dynamic_type_filters', event)} - verb={i === 0 ? 'Where' : 'and'} - schema={{ - Type: { - does_not_equal: { type: 'variant', values: $externalEventTypes.map(type => type.name) }, - does_not_include: { type: 'string' }, - equals: { type: 'variant', values: $externalEventTypes.map(type => type.name) }, - includes: { type: 'string' }, - }, - }} - /> - {/each} -
-
- {/if} -
-
-
-
- Other Filters -
Names, attributes
-
- -
- {#if dirtyFilter.other_filters?.length} -
-
- {#each dirtyFilter.other_filters as filter, i (filter.id)} - - onDynamicFilterRemove('other_filters', filter.id)} - on:change={event => onDynamicFilterChange('other_filters', event)} - verb={i === 0 ? 'Where' : 'and'} - schema={{ - Attribute: { - subfields: parameterSubfields, - }, - Name: { - does_not_equal: { type: 'string' }, - does_not_include: { type: 'string' }, - equals: { type: 'string' }, - includes: { type: 'string' }, - }, - }} - /> - {/each} -
-
- {/if} -
-
- - - -
-
- Resulting Types -
-
{matchingTypes.length} types
-
- {instanceCount} instance{pluralize(instanceCount)} -
-
-
- -
- - -
- {#each filteredMatchingTypes as type} - - - - - {#if dirtyFilter.type_subfilters && dirtyFilter.type_subfilters[type.name] && dirtyFilter.type_subfilters[type.name].length} -
- {#each dirtyFilter.type_subfilters[type.name] as filter (filter.id)} - onTypeSubfilterRemove(type.name, filter.id)} - on:change={event => onTypeSubfilterChange(type.name, event)} - verb={''} - schema={{ - Attribute: { - // Filter subfields to only those matching this type - subfields: parameterSubfields.filter(subfield => { - return subfield.externalEventTypes.indexOf(type.name) > -1; - }), - }, - Name: { - does_not_equal: { type: 'string' }, - does_not_include: { type: 'string' }, - equals: { type: 'string' }, - includes: { type: 'string' }, - }, - }} - /> - {/each} -
- {/if} -
-
- {/each} - {#if resultingTypesMessage} -
{resultingTypesMessage}
- {/if} -
-
-
-
-
- {/if} -
- - + + + diff --git a/src/components/timeline/form/TimelineEditor/FilterBuilder.svelte b/src/components/timeline/form/TimelineEditor/FilterBuilder.svelte new file mode 100644 index 0000000000..6c03403245 --- /dev/null +++ b/src/components/timeline/form/TimelineEditor/FilterBuilder.svelte @@ -0,0 +1,694 @@ + + + + +
+ + {#if shown} + +
+ + + + +
+
+ +
+
+
+ Manually Select Types + {#if dirtyFilter.static_types?.length} + + {/if} +
+
+
+ +
+ { + requestAnimationFrame(() => { + if (!manualInputOpen) { + manualInputOpen = true; + } + }); + }} + /> + +
+
+ (manualInputOpen = false)} + > +
+ {#if filteredTypes.length > 0} + onAddAllManualTypes()}> +
+ Add {filteredTypes.length !== allTypes.length ? 'Matching' : 'All'} + +
+
+ {#each filteredTypes as type} + onManualTypeToggled(type.name)}> + -1} + tabindex={-1} + style:pointer-events="none" + /> +
{type.name}
+
+ {/each} + {:else} + {noItemsMessage} + {/if} +
+
+
+ {#if dirtyFilter.static_types?.length} +
+ {#each dirtyFilter.static_types as name} + onManualTypeToggled(name)} /> + {/each} +
+ {/if} +
+
+
+
+
+ Dynamically Select Types +
{dynamicTypeFilterHint}
+
+ +
+ {#if dirtyFilter.dynamic_type_filters?.length} +
+
+ {#each dirtyFilter.dynamic_type_filters as filterItem, i (filterItem.id)} + onDynamicFilterRemove('dynamic_type_filters', filterItem.id)} + on:change={event => onDynamicFilterChange('dynamic_type_filters', event)} + verb={i === 0 ? 'Where' : 'and'} + schema={dynamicTypeFilterSchema} + /> + {/each} +
+
+ {/if} +
+
+
+
+ Other Filters +
{otherFilterHint}
+
+ +
+ {#if dirtyFilter.other_filters?.length} +
+
+ {#each dirtyFilter.other_filters as filterItem, i (filterItem.id)} + onDynamicFilterRemove('other_filters', filterItem.id)} + on:change={event => onDynamicFilterChange('other_filters', event)} + verb={i === 0 ? 'Where' : 'and'} + schema={otherFilterSchema} + /> + {/each} +
+
+ {/if} +
+
+ + + +
+
+ Resulting Types +
+
{matchingTypes.length} types
+
+ {instanceCount} instance{pluralize(instanceCount)} +
+
+
+ +
+ + +
+ {#each filteredMatchingTypes as type} + + + + {#if dirtyFilter.type_subfilters && dirtyFilter.type_subfilters[type.name] && dirtyFilter.type_subfilters[type.name].length} +
+ {#each dirtyFilter.type_subfilters[type.name] as subfilter (subfilter.id)} + onTypeSubfilterRemove(type.name, subfilter.id)} + on:change={event => onTypeSubfilterChange(type.name, event)} + verb={''} + schema={typeSubfilterSchema} + /> + {/each} +
+ {/if} +
+
+ {/each} + {#if resultingTypesMessage} +
{resultingTypesMessage}
+ {/if} +
+
+
+
+ +
+ {/if} +
+ + From 3fd97d5de340745dc3f823734cf0fefd5b7a5f1e Mon Sep 17 00:00:00 2001 From: AaronPlave Date: Tue, 20 Jan 2026 14:59:44 -0800 Subject: [PATCH 2/2] Prettier --- .../ActivityFilterBuilder.svelte | 19 +++++++++---------- .../ExternalEventFilterBuilder.svelte | 14 ++++++++------ .../form/TimelineEditor/FilterBuilder.svelte | 5 ++++- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/components/timeline/form/TimelineEditor/ActivityFilterBuilder.svelte b/src/components/timeline/form/TimelineEditor/ActivityFilterBuilder.svelte index f151c62715..e0f867b985 100644 --- a/src/components/timeline/form/TimelineEditor/ActivityFilterBuilder.svelte +++ b/src/components/timeline/form/TimelineEditor/ActivityFilterBuilder.svelte @@ -10,10 +10,7 @@ import type { ValueSchemaVariant } from '../../../../types/schema'; import type { ActivityLayerFilter, ActivityLayerFilterSubfieldSchema } from '../../../../types/timeline'; import { compare } from '../../../../utilities/generic'; - import { - applyActivityLayerFilter, - getMatchingTypesForActivityLayerFilter, - } from '../../../../utilities/timeline'; + import { applyActivityLayerFilter, getMatchingTypesForActivityLayerFilter } from '../../../../utilities/timeline'; import FilterBuilder from './FilterBuilder.svelte'; export let filter: ActivityLayerFilter | undefined = undefined; @@ -43,12 +40,14 @@ $: activityDirectives = Object.values($activityDirectivesMap || {}); - $: dirtyFilter = filter ? structuredClone(filter) : { - dynamic_type_filters: [], - other_filters: [], - static_types: [], - type_subfilters: {}, - }; + $: dirtyFilter = filter + ? structuredClone(filter) + : { + dynamic_type_filters: [], + other_filters: [], + static_types: [], + type_subfilters: {}, + }; $: appliedFilter = applyActivityLayerFilter( dirtyFilter, diff --git a/src/components/timeline/form/TimelineEditor/ExternalEventFilterBuilder.svelte b/src/components/timeline/form/TimelineEditor/ExternalEventFilterBuilder.svelte index 22e7becfea..9693923620 100644 --- a/src/components/timeline/form/TimelineEditor/ExternalEventFilterBuilder.svelte +++ b/src/components/timeline/form/TimelineEditor/ExternalEventFilterBuilder.svelte @@ -34,12 +34,14 @@ filterBuilder?.hide(); } - $: dirtyFilter = filter ? structuredClone(filter) : { - dynamic_type_filters: [], - other_filters: [], - static_types: [], - type_subfilters: {}, - }; + $: dirtyFilter = filter + ? structuredClone(filter) + : { + dynamic_type_filters: [], + other_filters: [], + static_types: [], + type_subfilters: {}, + }; $: externalEvents = Object.values($externalEventsMap || {}); $: appliedFilter = applyExternalEventLayerFilter(dirtyFilter, externalEvents); diff --git a/src/components/timeline/form/TimelineEditor/FilterBuilder.svelte b/src/components/timeline/form/TimelineEditor/FilterBuilder.svelte index 6c03403245..99a7aff836 100644 --- a/src/components/timeline/form/TimelineEditor/FilterBuilder.svelte +++ b/src/components/timeline/form/TimelineEditor/FilterBuilder.svelte @@ -485,7 +485,10 @@
Resulting Types
-
{matchingTypes.length} types
+
+ + {matchingTypes.length} types +
{instanceCount} instance{pluralize(instanceCount)}