Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change order of filters in the filters section #4095

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 4 additions & 1 deletion mathesar_ui/src/component-library/select/Select.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
} from '@mathesar-component-library-dir/list-box';

import StringOrComponent from '../string-or-component/StringOrComponent.svelte';
import Truncate from '../truncate/Truncate.svelte';

import type { SelectProps } from './SelectTypes';

Expand Down Expand Up @@ -172,7 +173,9 @@
{:else if $$slots.default}
<slot option={value} label={getLabel(value)} />
{:else}
<StringOrComponent arg={getLabel(value)} />
<Truncate>
<StringOrComponent arg={getLabel(value)} />
</Truncate>
{/if}
</svelte:fragment>

Expand Down
8 changes: 4 additions & 4 deletions mathesar_ui/src/stores/abstract-types/operations/filtering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function constructParamMapForAllTypes(
const equalityFiltersResponse: AbstractTypeFilterDefinitionResponse[] = [
{
id: 'equal',
name: 'equals',
name: 'equals (case sensitive)',
aliases: constructAliasMapForTypes(allDateTimeTypes, 'is same as'),
uiTypeParameterMap: constructParamMapForAllTypes((category) => [category]),
hasParams: true,
Expand All @@ -79,20 +79,20 @@ const equalityFiltersResponse: AbstractTypeFilterDefinitionResponse[] = [
// This is the API response expected from the server
// Might be better if we can have this with the types endpoint
const filterResponse: AbstractTypeFilterDefinitionResponse[] = [
...equalityFiltersResponse,
{
id: 'contains_case_insensitive',
name: 'contains',
name: 'contains (case insensitive)',
uiTypeParameterMap: {
[abstractTypeCategory.Text]: [abstractTypeCategory.Text],
[abstractTypeCategory.Email]: [abstractTypeCategory.Text],
[abstractTypeCategory.Uri]: [abstractTypeCategory.Text],
},
hasParams: true,
},
...equalityFiltersResponse,
{
id: 'starts_with_case_insensitive',
name: 'starts with',
name: 'starts with (case insensitive)',
uiTypeParameterMap: {
[abstractTypeCategory.Text]: [abstractTypeCategory.Text],
[abstractTypeCategory.Email]: [abstractTypeCategory.Text],
Expand Down
4 changes: 4 additions & 0 deletions mathesar_ui/src/stores/table-data/filtering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ export class Filtering {
this.entries = entries ?? [];
}

hasColumn(columnId: FilterEntry['columnId']): boolean {
return this.entries.some((entry) => entry.columnId === columnId);
}

withEntries(entries: Iterable<FilterEntry>): Filtering {
return new Filtering({
combination: this.combination,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
<script lang="ts">
import { takeLast } from 'iter-tools';
import { onMount, tick } from 'svelte';
import { createEventDispatcher, onMount, tick } from 'svelte';
import { type Writable, writable } from 'svelte/store';
import { _ } from 'svelte-i18n';

import DropdownMenu from '@mathesar/component-library/dropdown-menu/DropdownMenu.svelte';
import type { LinkedRecordInputElement } from '@mathesar/components/cell-fabric/data-types/components/linked-record/LinkedRecordUtils';
import ColumnName from '@mathesar/components/column/ColumnName.svelte';
import { validateFilterEntry } from '@mathesar/components/filter-entry';
import FilterEntry from '@mathesar/components/filter-entry/FilterEntry.svelte';
import { FILTER_INPUT_CLASS } from '@mathesar/components/filter-entry/utils';
import { iconAddNew } from '@mathesar/icons';
import { getImperativeFilterControllerFromContext } from '@mathesar/pages/table/ImperativeFilterController';
import type {
Filtering,
ProcessedColumns,
import {
type Filtering,
type ProcessedColumns,
getTabularDataStoreFromContext,
} from '@mathesar/stores/table-data';
import type { FilterCombination } from '@mathesar/stores/table-data/filtering';
import type RecordSummaryStore from '@mathesar/stores/table-data/record-summaries/RecordSummaryStore';
import { Button, Icon } from '@mathesar-component-library';
import { getColumnConstraintTypeByColumnId } from '@mathesar/utils/columnUtils';
import { ButtonMenuItem } from '@mathesar-component-library';

import { deepCloneFiltering } from '../utils';

import FilterEntries from './FilterEntries.svelte';

const imperativeFilterController = getImperativeFilterControllerFromContext();

export let filtering: Writable<Filtering>;
Expand All @@ -37,6 +39,14 @@
let element: HTMLElement;

$: filterCount = $internalFiltering.entries.length;
// $: availableColumns = processedColumns.values().toArray()

const tabularData = getTabularDataStoreFromContext();
const processedColumnStore = $tabularData.processedColumns;
/** Columns which are not already used as a filtering entry */
$: availableColumns = [...$processedColumnStore.values()].filter(
(column) => !$internalFiltering.hasColumn(column.id),
);

function checkAndSetExternalFiltering() {
const validFilters = $internalFiltering.entries.filter((filter) => {
Expand Down Expand Up @@ -84,10 +94,11 @@
checkAndSetExternalFiltering();
}

function setCombination(combination: FilterCombination) {
internalFiltering.update((f) => f.withCombination(combination));
checkAndSetExternalFiltering();
}
// This code has been commented due to not being used in current code. But may be used in the future.
// function setCombination(combination: FilterCombination) {
// internalFiltering.update((f) => f.withCombination(combination));
// checkAndSetExternalFiltering();
// }

function updateFilter() {
checkAndSetExternalFiltering();
Expand All @@ -114,40 +125,75 @@
activateLastFilterInput,
),
);

const dispatch = createEventDispatcher<{
remove: number;
update: number;
updateCombination: FilterCombination;
}>();
</script>

<div class="filters" class:filtered={filterCount} bind:this={element}>
<div class="header">{$_('filter_records')}</div>
<div class="content">
{#if filterCount}
<FilterEntries
{processedColumns}
{recordSummaries}
bind:entries={$internalFiltering.entries}
bind:filterCombination={$internalFiltering.combination}
on:remove={(e) => removeFilter(e.detail)}
on:update={updateFilter}
on:updateCombination={(e) => setCombination(e.detail)}
/>
{#each $internalFiltering.entries as entry, index}
<FilterEntry
columns={processedColumns}
getColumnLabel={(column) =>
processedColumns.get(column.id)?.column.name ?? ''}
disableColumnChange
getColumnConstraintType={(column) =>
getColumnConstraintTypeByColumnId(column.id, processedColumns)}
bind:columnIdentifier={entry.columnId}
bind:conditionIdentifier={entry.conditionId}
bind:value={entry.value}
numberOfFilters={filterCount}
on:update={() => {
updateFilter();
dispatch('update');
}}
on:removeFilter={() => {
removeFilter(index);
dispatch('remove');
}}
recordSummaryStore={recordSummaries}
/>
{/each}
{:else}
<span class="muted">{$_('no_filters_added')}</span>
{/if}
</div>
{#if processedColumns.size}
<div class="footer">
<Button
appearance="secondary"
on:click={async () => {
addFilter();
await tick();
activateLastFilterInput();
}}
>
<Icon {...iconAddNew} />
<span>{$_('add_new_filter')}</span>
</Button>
</div>
{/if}
<div class="footer">
<DropdownMenu
label={$_('add_new_filter')}
disabled={!availableColumns}
triggerAppearance="secondary"
>
{#each availableColumns as column (column.id)}
<ButtonMenuItem
on:click={async () => {
addFilter(column.id);
await tick();
activateLastFilterInput();
}}
>
<ColumnName
column={{
name: processedColumns.get(column.id)?.column.name ?? '',
type: processedColumns.get(column.id)?.column.type ?? '',
type_options:
processedColumns.get(column.id)?.column.type_options ?? null,
constraintsType: getColumnConstraintTypeByColumnId(
column.id,
processedColumns,
),
}}
/>
</ButtonMenuItem>
{/each}
</DropdownMenu>
</div>
</div>

<style lang="scss">
Expand All @@ -164,6 +210,10 @@
font-weight: bolder;
}

.footer {
margin-top: 1rem;
}

.muted {
color: var(--slate-400);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@
import type { Writable } from 'svelte/store';
import { _ } from 'svelte-i18n';

import ColumnName from '@mathesar/components/column/ColumnName.svelte';
import SortEntry from '@mathesar/components/sort-entry/SortEntry.svelte';
import type { SortDirection } from '@mathesar/components/sort-entry/utils';
import {
sortableContainer,
sortableItem,
sortableTrigger,
} from '@mathesar/components/sortable/sortable';
import { iconAddNew, iconGrip } from '@mathesar/icons';
import { iconGrip } from '@mathesar/icons';
import {
Sorting,
getTabularDataStoreFromContext,
} from '@mathesar/stores/table-data';
import { getColumnConstraintTypeByColumnId } from '@mathesar/utils/columnUtils';
import { Button, Icon } from '@mathesar-component-library';
import {
ButtonMenuItem,
DropdownMenu,
Icon,
} from '@mathesar-component-library';

const tabularData = getTabularDataStoreFromContext();

Expand All @@ -27,6 +32,10 @@
.filter((column) => !$sorting.has(column.id))
.map((entry) => entry.id);

$: availableColumns = [...$processedColumns.values()].filter(
(column) => !$sorting.has(column.id),
);

function addSortColumn() {
const [newSortColumnId] = availableColumnIds;
sorting.update((s) => s.with(newSortColumnId, 'ASCENDING'));
Expand Down Expand Up @@ -97,10 +106,32 @@
</div>
{#if availableColumnIds.length > 0}
<div class="footer">
<Button appearance="secondary" on:click={addSortColumn}>
<Icon {...iconAddNew} />
<span>{$_('add_new_sort_condition')}</span>
</Button>
<DropdownMenu
label={$_('add_new_sort_condition')}
disabled={availableColumnIds.length === 0}
triggerAppearance="secondary"
>
<!-- <Button appearance="secondary" on:click={addSortColumn}>
<Icon {...iconAddNew} />
<span>{$_('add_new_sort_condition')}</span>
</Button> -->
{#each availableColumns as column (column.id)}
<ButtonMenuItem on:click={addSortColumn}>
<ColumnName
column={{
name: $processedColumns.get(column.id)?.column.name ?? '',
type: $processedColumns.get(column.id)?.column.type ?? '',
type_options:
$processedColumns.get(column.id)?.column.type_options ?? null,
constraintsType: getColumnConstraintTypeByColumnId(
column.id,
$processedColumns,
),
}}
/>
</ButtonMenuItem>
{/each}
</DropdownMenu>
</div>
{/if}
</div>
Expand Down
Loading