Skip to content

Commit

Permalink
Merge pull request #1146 from appwrite/refactor-filter-application-logic
Browse files Browse the repository at this point in the history
refactor: filter application logic
  • Loading branch information
ArmanNik authored Jul 2, 2024
2 parents 03aaa5c + 3fddb40 commit afa92ba
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 112 deletions.
129 changes: 21 additions & 108 deletions src/lib/components/filters/content.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,94 +8,21 @@
FormList,
InputSelectCheckbox
} from '$lib/elements/forms';
import { Query } from '@appwrite.io/console';
import { createEventDispatcher, onMount } from 'svelte';
import { tags, type Operator, queries, type TagValue } from './store';
import { tags, queries, type TagValue, operators, addFilter } from './store';
import type { Column } from '$lib/helpers/types';
import type { Writable } from 'svelte/store';
import { tooltip } from '$lib/actions/tooltip';
// We cast to any to not cause type errors in the input components
/* eslint @typescript-eslint/no-explicit-any: 'off' */
export let value: any = null;
export let columns: Writable<Column[]>;
export let columnId: string | null = null;
const dispatch = createEventDispatcher<{
clear: void;
apply: { applied: number };
}>();
export let arrayValues: string[] = [];
export let operatorKey: string | null = null;
$: column = $columns.find((c) => c.id === columnId) as Column;
let arrayValues: string[] = [];
dispatch('apply', { applied: $tags.length });
const operators: Record<string, Operator> = {
'starts with': {
toQuery: Query.startsWith,
toTag: (attribute, input) => `**${attribute}** starts with **${input}**`,
types: ['string']
},
'ends with': {
toQuery: Query.endsWith,
toTag: (attribute, input) => `**${attribute}** ends with **${input}**`,
types: ['string']
},
'greater than': {
toQuery: (attr, input) => Query.greaterThan(attr, Number(input)),
toTag: (attribute, input) => `**${attribute}** greater than **${input}**`,
types: ['integer', 'double', 'datetime']
},
'greater than or equal': {
toQuery: (attr, input) => Query.greaterThanEqual(attr, Number(input)),
toTag: (attribute, input) => `**${attribute}** greater than or equal to **${input}**`,
types: ['integer', 'double', 'datetime']
},
'less than': {
toQuery: Query.lessThan,
toTag: (attribute, input) => `**${attribute}** less than **${input}**`,
types: ['integer', 'double', 'datetime']
},
'less than or equal': {
toQuery: Query.lessThanEqual,
toTag: (attribute, input) => `**${attribute}** less than or equal to **${input}**`,
types: ['integer', 'double', 'datetime']
},
equal: {
toQuery: Query.equal,
toTag: (attribute, input) => `**${attribute}** equal to **${input}**`,
types: ['string', 'integer', 'double', 'boolean']
},
'not equal': {
toQuery: Query.notEqual,
toTag: (attribute, input) => `**${attribute}** not equal to **${input}**`,
types: ['string', 'integer', 'double', 'boolean']
},
'is not null': {
toQuery: Query.isNotNull,
toTag: (attribute) => `**${attribute}** is not null`,
types: ['string', 'integer', 'double', 'boolean', 'datetime', 'relationship'],
hideInput: true
},
'is null': {
toQuery: Query.isNull,
toTag: (attribute) => `**${attribute}** is null`,
types: ['string', 'integer', 'double', 'boolean', 'datetime', 'relationship'],
hideInput: true
},
contains: {
toQuery: Query.contains,
toTag: (attribute, input) => {
if (Array.isArray(input) && input.length > 2) {
return {
value: input,
tag: `**${attribute}** contains **${formatArray(input)}** `
};
} else {
return `**${attribute}** contains **${input}**`;
}
},
types: ['string', 'integer', 'double', 'boolean', 'datetime', 'enum']
}
};
$: operatorsForColumn = Object.entries(operators)
.filter(([, v]) => v.types.includes(column?.type))
Expand All @@ -104,48 +31,30 @@
value: k
}));
let operatorKey: string | null = null;
$: operator = operatorKey ? operators[operatorKey] : null;
$: {
columnId;
operatorKey = null;
}
// We cast to any to not cause type errors in the input components
/* eslint @typescript-eslint/no-explicit-any: 'off' */
let value: any = null;
$: isDisabled = !operator;
onMount(() => {
value = column?.array ? [] : null;
});
// This Map is keyed by tags, and has a query as the value
function addFilter() {
if (!column || !operator) return;
if (column.array) {
queries.addFilter({ column, operator, value: arrayValues });
columnId = null;
arrayValues = [];
} else {
queries.addFilter({ column, operator, value: value ?? '' });
columnId = null;
value = null;
}
function addFilterAndReset() {
addFilter($columns, columnId, operatorKey, value, arrayValues);
columnId = null;
operatorKey = null;
value = null;
arrayValues = [];
}
function tagFormat(node: HTMLElement) {
node.innerHTML = node.innerHTML.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>');
}
function formatArray(array: string[]) {
if (!array?.length) return;
if (array.length > 2) {
return `${array[0]} or ${array.length - 1} others`;
} else {
return array.join(' or ');
}
}
function isTypeTagValue(obj: any): obj is TagValue {
if (typeof obj === 'string') return false;
return (
Expand All @@ -157,11 +66,15 @@
);
}
$: isDisabled = !operator;
const dispatch = createEventDispatcher<{
clear: void;
apply: { applied: number };
}>();
dispatch('apply', { applied: $tags.length });
</script>

<div>
<form on:submit|preventDefault={addFilter}>
<form on:submit|preventDefault={addFilterAndReset}>
<ul class="selects u-flex u-gap-8 u-margin-block-start-16">
<InputSelect
id="column"
Expand Down Expand Up @@ -231,9 +144,9 @@
</ul>
{/if}
{/if}
<Button text disabled={isDisabled} class="u-margin-block-start-4" submit>
<Button text disabled={isDisabled} class="u-margin-block-start-4" noMargin submit>
<i class="icon-plus" />
Add filter
Add condition
</Button>
</form>

Expand Down
33 changes: 29 additions & 4 deletions src/lib/components/filters/filters.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import type { Column } from '$lib/helpers/types';
import type { Writable } from 'svelte/store';
import Content from './content.svelte';
import { queries, queriesAreDirty, queryParamToMap, tags } from './store';
import { addFilter, queries, queriesAreDirty, queryParamToMap, tags } from './store';
export let query = '[]';
export let columns: Writable<Column[]>;
Expand All @@ -15,7 +15,12 @@
const parsedQueries = queryParamToMap(query);
queries.set(parsedQueries);
/* eslint @typescript-eslint/no-explicit-any: 'off' */
let value: any = null;
let selectedColumn: string | null = null;
let operatorKey: string | null = null;
let arrayValues: string[] = [];
// We need to separate them so we don't trigger Drop's handlers
let showFiltersDesktop = false;
let showFiltersMobile = false;
Expand All @@ -33,9 +38,22 @@
queries.clearAll();
}
function apply() {
if (selectedColumn && operatorKey && value) {
addFilter($columns, selectedColumn, operatorKey, value, arrayValues);
selectedColumn = null;
value = null;
operatorKey = null;
arrayValues = [];
}
queries.apply();
}
$: if (!showFiltersDesktop && !showFiltersMobile) {
selectedColumn = null;
}
$: isButtonDisabled = $queriesAreDirty ? false : !selectedColumn || !operatorKey || !value;
</script>

<div class="is-not-mobile">
Expand All @@ -54,13 +72,16 @@
<p>Apply filter rules to refine the table view</p>
<Content
bind:columnId={selectedColumn}
bind:operatorKey
bind:value
bind:arrayValues
{columns}
on:apply={(e) => (applied = e.detail.applied)}
on:clear={() => (applied = 0)} />
<hr />
<div class="u-flex u-margin-block-start-16 u-main-end u-gap-8">
<Button text on:click={clearAll}>Clear all</Button>
<Button on:click={queries.apply} disabled={!$queriesAreDirty}>Apply</Button>
<Button on:click={apply} disabled={isButtonDisabled}>Apply</Button>
</div>
</div>
</svelte:fragment>
Expand All @@ -85,12 +106,16 @@
size="big">
<Content
{columns}
bind:columnId={selectedColumn}
bind:operatorKey
bind:value
bind:arrayValues
on:apply={(e) => (applied = e.detail.applied)}
on:clear={() => (applied = 0)} />
<svelte:fragment slot="footer">
<Button text on:click={clearAll}>Clear all</Button>
<Button on:click={queries.apply} disabled={!$queriesAreDirty}>Apply</Button
></svelte:fragment>
<Button on:click={apply} disabled={isButtonDisabled}>Apply</Button>
</svelte:fragment>
</Modal>
</div>

Expand Down
98 changes: 98 additions & 0 deletions src/lib/components/filters/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { derived, get, writable } from 'svelte/store';
import { page } from '$app/stores';
import deepEqual from 'deep-equal';
import type { Column, ColumnType } from '$lib/helpers/types';
import { Query } from '@appwrite.io/console';

export type TagValue = {
tag: string;
Expand Down Expand Up @@ -84,3 +85,100 @@ export const hasPageQueries = derived(page, ($page) => {
});

export const tags = derived(queries, ($queries) => Array.from($queries.keys()));

/* eslint @typescript-eslint/no-explicit-any: 'off' */
export function addFilter(
columns: Column[],
columnId: string,
operatorKey: string,
value: any, // We cast to any to not cause type errors in the input components
arrayValues: string[] = []
) {
const operator = operatorKey ? operators[operatorKey] : null;
const column = columns.find((c) => c.id === columnId) as Column;

if (!column || !operator) return;
if (column.array) {
queries.addFilter({ column, operator, value: arrayValues });
} else {
queries.addFilter({ column, operator, value: value ?? '' });
}
}

export const operators: Record<string, Operator> = {
'starts with': {
toQuery: Query.startsWith,
toTag: (attribute, input) => `**${attribute}** starts with **${input}**`,
types: ['string']
},
'ends with': {
toQuery: Query.endsWith,
toTag: (attribute, input) => `**${attribute}** ends with **${input}**`,
types: ['string']
},
'greater than': {
toQuery: (attr, input) => Query.greaterThan(attr, Number(input)),
toTag: (attribute, input) => `**${attribute}** greater than **${input}**`,
types: ['integer', 'double', 'datetime']
},
'greater than or equal': {
toQuery: (attr, input) => Query.greaterThanEqual(attr, Number(input)),
toTag: (attribute, input) => `**${attribute}** greater than or equal to **${input}**`,
types: ['integer', 'double', 'datetime']
},
'less than': {
toQuery: Query.lessThan,
toTag: (attribute, input) => `**${attribute}** less than **${input}**`,
types: ['integer', 'double', 'datetime']
},
'less than or equal': {
toQuery: Query.lessThanEqual,
toTag: (attribute, input) => `**${attribute}** less than or equal to **${input}**`,
types: ['integer', 'double', 'datetime']
},
equal: {
toQuery: Query.equal,
toTag: (attribute, input) => `**${attribute}** equal to **${input}**`,
types: ['string', 'integer', 'double', 'boolean']
},
'not equal': {
toQuery: Query.notEqual,
toTag: (attribute, input) => `**${attribute}** not equal to **${input}**`,
types: ['string', 'integer', 'double', 'boolean']
},
'is not null': {
toQuery: Query.isNotNull,
toTag: (attribute) => `**${attribute}** is not null`,
types: ['string', 'integer', 'double', 'boolean', 'datetime', 'relationship'],
hideInput: true
},
'is null': {
toQuery: Query.isNull,
toTag: (attribute) => `**${attribute}** is null`,
types: ['string', 'integer', 'double', 'boolean', 'datetime', 'relationship'],
hideInput: true
},
contains: {
toQuery: Query.contains,
toTag: (attribute, input) => {
if (Array.isArray(input) && input.length > 2) {
return {
value: input,
tag: `**${attribute}** contains **${formatArray(input)}** `
};
} else {
return `**${attribute}** contains **${input}**`;
}
},
types: ['string', 'integer', 'double', 'boolean', 'datetime', 'enum']
}
};

function formatArray(array: string[]) {
if (!array?.length) return;
if (array.length > 2) {
return `${array[0]} or ${array.length - 1} others`;
} else {
return array.join(' or ');
}
}

0 comments on commit afa92ba

Please sign in to comment.