Skip to content

Commit 67d6378

Browse files
committed
Renamed "Dataset filters" to "Current selection"; added confirmation dialog to dataset filters editing
1 parent 28e6f35 commit 67d6378

File tree

9 files changed

+206
-106
lines changed

9 files changed

+206
-106
lines changed

src/lib/components/common/ConfirmActionButton.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
export let buttonIcon = undefined;
1111
export let modalId = undefined;
1212
export let message = '';
13+
export let disabled = false;
1314
/** @type {Modal} */
1415
let modal;
1516
let loading = false;
@@ -65,6 +66,7 @@
6566
data-bs-toggle="modal"
6667
data-bs-target="#{modalId}"
6768
aria-label={ariaLabel}
69+
{disabled}
6870
>
6971
{#if buttonIcon}
7072
<i class="bi bi-{buttonIcon}" />
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<script>
2+
import { onDestroy, onMount } from 'svelte';
3+
4+
/** @type {string} */
5+
export let id;
6+
/** @type {string} */
7+
export let title;
8+
9+
export let placement = 'top';
10+
11+
let tooltip;
12+
13+
/**
14+
* @param {boolean} enabled
15+
*/
16+
export function setEnabled(enabled) {
17+
if (!tooltip) {
18+
return;
19+
}
20+
if (enabled) {
21+
tooltip.enable();
22+
} else {
23+
tooltip.disable();
24+
}
25+
}
26+
27+
onMount(() => {
28+
const element = document.getElementById(id);
29+
if (!element) {
30+
console.warn(`Unable to find element for id ${id}`);
31+
return;
32+
}
33+
// @ts-expect-error
34+
// eslint-disable-next-line no-undef
35+
tooltip = new bootstrap.Tooltip(element, { title });
36+
});
37+
38+
onDestroy(() => {
39+
tooltip?.dispose();
40+
tooltip = undefined;
41+
});
42+
</script>
43+
44+
<span {id} data-bs-placement={placement} style="display: inline-block;">
45+
<slot />
46+
</span>

src/lib/components/v2/projects/datasets/DatasetImagesTable.svelte

Lines changed: 84 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import CreateUpdateImageModal from '$lib/components/v2/projects/datasets/CreateUpdateImageModal.svelte';
55
import Paginator from '$lib/components/common/Paginator.svelte';
66
import BooleanIcon from 'fractal-components/common/BooleanIcon.svelte';
7-
import { objectChanged } from '$lib/common/component_utilities';
7+
import { deepCopy, objectChanged } from '$lib/common/component_utilities';
88
import SlimSelect from 'slim-select';
99
import { onDestroy, tick } from 'svelte';
10+
import Tooltip from '$lib/components/common/Tooltip.svelte';
1011
1112
/** @type {import('fractal-components/types/api').DatasetV2} */
1213
export let dataset;
@@ -16,7 +17,6 @@
1617
export let vizarrViewerUrl;
1718
/** @type {boolean} */
1819
export let useDatasetFilters;
19-
export let datasetFiltersChanged = false;
2020
/**
2121
* Set to true if the table is displayed inside the "Run workflow" modal.
2222
* Used to disable some buttons.
@@ -27,13 +27,21 @@
2727
/** @type {{ attribute_filters: { [key: string]: Array<string | number | boolean> | null }, type_filters: { [key: string]: boolean | null }} | null} */
2828
export let initialFilterValues = null;
2929
30+
let datasetFiltersChanged = false;
31+
/** @type {(dataset: import('fractal-components/types/api').DatasetV2) => void} */
32+
export let onDatasetsUpdated = () => {};
33+
3034
let showTable = imagePage.total_count > 0;
3135
36+
/** @type {Tooltip|undefined} */
37+
let currentSelectionTooltip;
38+
3239
/** @type {CreateUpdateImageModal|undefined} */
3340
let imageModal = undefined;
3441
3542
let searching = false;
3643
let resetting = false;
44+
let savingDatasetFilters = false;
3745
3846
let reloading = false;
3947
/** @type {{ [key: string]: Array<string | number | boolean> | null}} */
@@ -129,18 +137,24 @@
129137
async function resetSearchFields() {
130138
resetBtnActive = false;
131139
resetting = true;
132-
attributeFilters = getAttributeFilterBaseValues(imagePage);
133-
typeFilters = getTypeFilterBaseValues(imagePage);
140+
if (useDatasetFilters) {
141+
attributeFilters = deepCopy(dataset.attribute_filters);
142+
typeFilters = deepCopy(dataset.type_filters);
143+
} else {
144+
attributeFilters = getAttributeFilterBaseValues(imagePage);
145+
typeFilters = getTypeFilterBaseValues(imagePage);
146+
}
134147
await tick();
135148
await searchImages();
136149
resetting = false;
137150
}
138151
139152
export async function reload() {
140153
reloading = true;
154+
currentSelectionTooltip?.setEnabled(!useDatasetFilters);
141155
if (useDatasetFilters) {
142-
attributeFilters = dataset.attribute_filters;
143-
typeFilters = dataset.type_filters;
156+
attributeFilters = deepCopy(dataset.attribute_filters);
157+
typeFilters = deepCopy(dataset.type_filters);
144158
} else {
145159
attributeFilters = getAttributeFilterBaseValues(imagePage);
146160
typeFilters = getTypeFilterBaseValues(imagePage);
@@ -387,7 +401,7 @@
387401
} else {
388402
errorAlert = displayStandardErrorAlert(
389403
await getAlertErrorFromResponse(response),
390-
'searchError'
404+
'datasetImagesError'
391405
);
392406
}
393407
}
@@ -476,6 +490,7 @@
476490
}
477491
478492
$: if (attributeFilters && typeFilters) {
493+
//console.log(dataset.attribute_filters, removeNullValues(attributeFilters))
479494
datasetFiltersChanged =
480495
!applyBtnActive &&
481496
(attributesChanged(dataset.attribute_filters, removeNullValues(attributeFilters)) ||
@@ -510,8 +525,8 @@
510525
if (oldValue.length !== newValue.length) {
511526
return true;
512527
}
513-
for (let i = 0; i < oldValue.length; i++) {
514-
if (oldValue[i] !== newValue[i]) {
528+
for (const ov of oldValue) {
529+
if (!newValue.includes(ov)) {
515530
return true;
516531
}
517532
}
@@ -521,6 +536,33 @@
521536
}
522537
return false;
523538
}
539+
540+
async function saveDatasetFilters() {
541+
errorAlert?.hide();
542+
savingDatasetFilters = true;
543+
const headers = new Headers();
544+
headers.set('Content-Type', 'application/json');
545+
const response = await fetch(`/api/v2/project/${dataset.project_id}/dataset/${dataset.id}`, {
546+
method: 'PATCH',
547+
credentials: 'include',
548+
headers,
549+
body: JSON.stringify({
550+
attribute_filters: getAttributeFilters(),
551+
type_filters: getTypeFilters()
552+
})
553+
});
554+
if (response.ok) {
555+
const result = await response.json();
556+
dataset = result;
557+
onDatasetsUpdated(dataset);
558+
} else {
559+
errorAlert = displayStandardErrorAlert(
560+
await getAlertErrorFromResponse(response),
561+
'datasetImagesError'
562+
);
563+
}
564+
savingDatasetFilters = false;
565+
}
524566
</script>
525567
526568
{#if !showTable}
@@ -535,7 +577,7 @@
535577
<div>
536578
<div class="row">
537579
<div class="col">
538-
<div id="searchError" class="mt-2 mb-2" />
580+
<div id="datasetImagesError" class="mt-2 mb-2" />
539581
</div>
540582
</div>
541583
@@ -624,6 +666,17 @@
624666
{/if}
625667
Reset
626668
</button>
669+
{#if !runWorkflowModal && useDatasetFilters}
670+
<ConfirmActionButton
671+
modalId="confirmSaveDatasetFilters"
672+
label="Save"
673+
message="Save dataset filters"
674+
disabled={!datasetFiltersChanged || savingDatasetFilters}
675+
callbackAction={async () => {
676+
await saveDatasetFilters();
677+
}}
678+
/>
679+
{/if}
627680
</th>
628681
</tr>
629682
</thead>
@@ -704,20 +757,27 @@
704757
disabled={reloading || searching || resetting}
705758
/>
706759
<label class="btn btn-white btn-outline-primary" for="all-images">All images</label>
707-
<input
708-
type="radio"
709-
class="btn-check"
710-
name="filters-switch"
711-
id="dataset-filters"
712-
autocomplete="off"
713-
value={true}
714-
bind:group={useDatasetFilters}
715-
on:change={reload}
716-
disabled={reloading || searching || resetting}
717-
/>
718-
<label class="btn btn-white btn-outline-primary" for="dataset-filters">
719-
Dataset filters
720-
</label>
760+
<Tooltip
761+
id="current-selection-label"
762+
title="These are default selection for images on which a workflow will be run"
763+
placement="bottom"
764+
bind:this={currentSelectionTooltip}
765+
>
766+
<input
767+
type="radio"
768+
class="btn-check"
769+
name="filters-switch"
770+
id="current-selection"
771+
autocomplete="off"
772+
value={true}
773+
bind:group={useDatasetFilters}
774+
on:change={reload}
775+
disabled={reloading || searching || resetting}
776+
/>
777+
<label class="btn btn-white btn-outline-primary" for="current-selection">
778+
Current selection
779+
</label>
780+
</Tooltip>
721781
{#if reloading}
722782
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" />
723783
{/if}

src/lib/components/v2/projects/datasets/DatasetInfoModal.svelte

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { page } from '$app/stores';
33
import { FormErrorHandler } from '$lib/common/errors';
44
import Modal from '$lib/components/common/Modal.svelte';
5+
import BooleanIcon from 'fractal-components/common/BooleanIcon.svelte';
56
67
/** @type {import('fractal-components/types/api').DatasetV2} */
78
export let dataset;
@@ -212,6 +213,35 @@
212213
</span>
213214
{/if}
214215
</li>
216+
<li class="list-group-item text-bg-light">
217+
<strong>Dataset filters</strong>
218+
</li>
219+
<li class="list-group-item">
220+
<div>
221+
<p class="fw-medium">Attribute filters:</p>
222+
{#if Object.entries(dataset.attribute_filters).length > 0}
223+
<ul>
224+
{#each Object.entries(dataset.attribute_filters) as [k, v]}
225+
<li>{k}: <code>{v}</code></li>
226+
{/each}
227+
</ul>
228+
{:else}
229+
<p class="ms-3">No attribute filters</p>
230+
{/if}
231+
</div>
232+
<div class="mt-3">
233+
<p class="fw-medium">Type filters:</p>
234+
{#if Object.entries(dataset.type_filters).length > 0}
235+
<ul>
236+
{#each Object.entries(dataset.type_filters) as [k, v]}
237+
<li>{k}: <BooleanIcon value={v} /></li>
238+
{/each}
239+
</ul>
240+
{:else}
241+
<p class="ms-3">No type filters</p>
242+
{/if}
243+
</div>
244+
</li>
215245
</ul>
216246
</svelte:fragment>
217247
</Modal>

src/routes/v2/projects/[projectId]/datasets/[datasetId]/+page.svelte

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import DatasetHistoryModal from '$lib/components/v2/projects/datasets/DatasetHistoryModal.svelte';
55
import { env } from '$env/dynamic/public';
66
import DatasetImagesTable from '$lib/components/v2/projects/datasets/DatasetImagesTable.svelte';
7-
import { displayStandardErrorAlert, getAlertErrorFromResponse } from '$lib/common/errors';
87
import { onMount } from 'svelte';
98
109
const vizarrViewerUrl = env.PUBLIC_FRACTAL_VIZARR_VIEWER_URL
@@ -18,11 +17,6 @@
1817
/** @type {import('fractal-components/types/api').ImagePage} */
1918
let imagePage = $page.data.imagePage;
2019
let useDatasetFilters = false;
21-
let datasetFiltersChanged = false;
22-
let savingDatasetFilters = false;
23-
24-
/** @type {import('$lib/components/common/StandardErrorAlert.svelte').default|undefined} */
25-
let errorAlert = undefined;
2620
2721
/** @type {DatasetImagesTable} */
2822
let imagesTable;
@@ -97,33 +91,6 @@
9791
return result.images[0];
9892
}
9993
100-
async function saveDatasetFilters() {
101-
errorAlert?.hide();
102-
savingDatasetFilters = true;
103-
const projectId = $page.params.projectId;
104-
const headers = new Headers();
105-
headers.set('Content-Type', 'application/json');
106-
const response = await fetch(`/api/v2/project/${projectId}/dataset/${dataset.id}`, {
107-
method: 'PATCH',
108-
credentials: 'include',
109-
headers,
110-
body: JSON.stringify({
111-
attribute_filters: imagesTable.getAttributeFilters(),
112-
type_filters: imagesTable.getTypeFilters()
113-
})
114-
});
115-
if (response.ok) {
116-
const result = await response.json();
117-
dataset = result;
118-
} else {
119-
errorAlert = displayStandardErrorAlert(
120-
await getAlertErrorFromResponse(response),
121-
'datasetUpdateError'
122-
);
123-
}
124-
savingDatasetFilters = false;
125-
}
126-
12794
async function handleExportDataset() {
12895
const response = await fetch(
12996
`/api/v2/project/${dataset.project_id}/dataset/${dataset.id}/export`,
@@ -180,22 +147,6 @@
180147
<button class="btn btn-light" data-bs-target="#datasetInfoModal" data-bs-toggle="modal">
181148
Info
182149
</button>
183-
{#if useDatasetFilters}
184-
<button
185-
class="btn btn-primary"
186-
disabled={!datasetFiltersChanged || savingDatasetFilters}
187-
on:click={saveDatasetFilters}
188-
>
189-
Save filters
190-
{#if savingDatasetFilters}
191-
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" />
192-
{/if}
193-
</button>
194-
{:else}
195-
<button class="btn btn-light" on:click={() => (useDatasetFilters = true)}>
196-
Edit filters
197-
</button>
198-
{/if}
199150
<button class="btn btn-light" data-bs-target="#datasetHistoryModal" data-bs-toggle="modal">
200151
History
201152
</button>
@@ -256,9 +207,9 @@
256207
bind:imagePage
257208
{vizarrViewerUrl}
258209
bind:useDatasetFilters
259-
bind:datasetFiltersChanged
260210
runWorkflowModal={false}
261211
bind:this={imagesTable}
212+
onDatasetsUpdated={(updated) => (dataset = updated)}
262213
/>
263214

264215
<DatasetInfoModal {dataset} updateDatasetCallback={(d) => (dataset = d)} />

0 commit comments

Comments
 (0)