Skip to content

Commit 6d0f945

Browse files
committed
Implemented editing of a dataset image
1 parent a94820b commit 6d0f945

File tree

4 files changed

+119
-24
lines changed

4 files changed

+119
-24
lines changed

src/lib/components/v2/projects/datasets/AddImageModal.svelte renamed to src/lib/components/v2/projects/datasets/CreateUpdateImageModal.svelte

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,36 @@
1313
1414
let zarr_url = '';
1515
let saving = false;
16+
let isNew = false;
1617
1718
/** @type {AttributesTypesForm} */
1819
let attributesTypesForm;
1920
20-
function onOpen() {
21-
zarr_url = '';
21+
export function openForCreate() {
22+
isNew = true;
2223
saving = false;
24+
zarr_url = '';
2325
attributesTypesForm.init({}, {});
26+
modal.show();
27+
}
28+
29+
/**
30+
* @param {import('$lib/types-v2').Image} image
31+
*/
32+
export function openForEditing(image) {
33+
isNew = false;
34+
saving = false;
35+
zarr_url = image.zarr_url;
36+
attributesTypesForm.init(image.attributes, image.types);
37+
modal.show();
38+
}
39+
40+
async function saveImage() {
41+
if (isNew) {
42+
await createNewImage();
43+
} else {
44+
await updateImage();
45+
}
2446
}
2547
2648
async function createNewImage() {
@@ -56,31 +78,76 @@
5678
}
5779
);
5880
}
81+
82+
async function updateImage() {
83+
let success = false;
84+
modal.confirmAndHide(
85+
async () => {
86+
saving = true;
87+
const headers = new Headers();
88+
headers.set('Content-Type', 'application/json');
89+
const response = await fetch(
90+
`/api/v2/project/${dataset.project_id}/dataset/${dataset.id}/images`,
91+
{
92+
method: 'PATCH',
93+
headers,
94+
body: JSON.stringify({
95+
zarr_url,
96+
attributes: attributesTypesForm.getAttributes(),
97+
types: attributesTypesForm.getTypes()
98+
})
99+
}
100+
);
101+
if (!response.ok) {
102+
saving = false;
103+
throw new AlertError(await response.json());
104+
}
105+
success = true;
106+
},
107+
async () => {
108+
if (success) {
109+
await onImageSave();
110+
saving = false;
111+
}
112+
}
113+
);
114+
}
59115
</script>
60116

61117
<Modal
62-
id="datasetAddImageModal"
118+
id="datasetCreateUpdateImageModal"
63119
size="lg"
64120
centered={true}
65121
scrollable={true}
66-
{onOpen}
67122
bind:this={modal}
68123
>
69124
<svelte:fragment slot="header">
70-
<h5 class="modal-title">Add an image list entry</h5>
125+
<h5 class="modal-title">
126+
{#if isNew}
127+
Add an image list entry
128+
{:else}
129+
Edit an image list entry
130+
{/if}
131+
</h5>
71132
</svelte:fragment>
72133
<svelte:fragment slot="body">
73134
<div class="row mb-3">
74-
<label class="col-3 col-lg-2 col-form-label" for="new-image-zarr-url"> Zarr URL </label>
135+
<label class="col-3 col-lg-2 col-form-label" for="image-zarr-url"> Zarr URL </label>
75136
<div class="col col-lg-10">
76-
<input type="text" class="form-control" bind:value={zarr_url} id="new-image-zarr-url" />
137+
<input
138+
type="text"
139+
class="form-control"
140+
bind:value={zarr_url}
141+
disabled={!isNew}
142+
id="image-zarr-url"
143+
/>
77144
</div>
78145
</div>
79146
<AttributesTypesForm bind:this={attributesTypesForm} filters={false} />
80-
<div id="errorAlert-datasetAddImageModal" class="mt-3" />
147+
<div id="errorAlert-datasetCreateUpdateImageModal" class="mt-3" />
81148
</svelte:fragment>
82149
<svelte:fragment slot="footer">
83-
<button class="btn btn-primary" on:click={createNewImage} disabled={saving}>
150+
<button class="btn btn-primary" on:click={saveImage} disabled={saving}>
84151
{#if saving}
85152
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" />
86153
{/if}

src/lib/types-v2.d.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,19 @@ export type DatasetHistoryItemV2 = {
2727
parallelization: object
2828
}
2929

30+
export type Image = {
31+
zarr_url: string
32+
attributes: { [key: string]: string | number | boolean }
33+
types: { [key: string]: boolean }
34+
}
35+
3036
export type ImagePage = {
3137
total_count: number
3238
page_size: number
3339
current_page: number
3440
attributes: { [key: string]: Array<string | number | boolean> }
3541
types: Array<string>
36-
images: Array<{
37-
zarr_url: string
38-
attributes: { [key: string]: string | number | boolean }
39-
types: { [key: string]: boolean }
40-
}>
42+
images: Array<Image>
4143
}
4244

4345
export type TaskV2Type = 'non_parallel' | 'parallel' | 'compound'

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

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import DatasetFiltersModal from '$lib/components/v2/projects/datasets/DatasetFiltersModal.svelte';
77
import DatasetInfoModal from '$lib/components/v2/projects/datasets/DatasetInfoModal.svelte';
88
import DatasetHistoryModal from '$lib/components/v2/projects/datasets/DatasetHistoryModal.svelte';
9-
import AddImageModal from '$lib/components/v2/projects/datasets/AddImageModal.svelte';
9+
import CreateUpdateImageModal from '$lib/components/v2/projects/datasets/CreateUpdateImageModal.svelte';
1010
import BooleanIcon from '$lib/components/common/BooleanIcon.svelte';
1111
import SlimSelect from 'slim-select';
1212
import { onMount, tick } from 'svelte';
@@ -40,6 +40,9 @@
4040
/** @type {{ [key: string]: boolean | null }}} */
4141
let lastAppliedTypeFilters = getTypeFilterBaseValues(imagePage);
4242
43+
/** @type {CreateUpdateImageModal|undefined} */
44+
let imageModal = undefined;
45+
4346
onMount(() => {
4447
loadAttributesSelectors();
4548
loadTypesSelector();
@@ -372,11 +375,7 @@
372375
373376
{#if !showTable}
374377
<p class="fw-bold ms-4 mt-5">No entries in the image list yet</p>
375-
<button
376-
class="btn btn-outline-secondary ms-4"
377-
data-bs-target="#datasetAddImageModal"
378-
data-bs-toggle="modal"
379-
>
378+
<button class="btn btn-outline-secondary ms-4" on:click={() => imageModal?.openForCreate()}>
380379
<i class="bi bi-plus-circle" />
381380
Add an image list entry
382381
</button>
@@ -472,12 +471,20 @@
472471
<tr>
473472
<td>{getRelativePath(image.zarr_url)}</td>
474473
{#each Object.keys(imagePage.attributes) as attribute}
475-
<td>{image.attributes[attribute] || ''}</td>
474+
<td>
475+
{#if image.attributes[attribute] !== null && image.attributes[attribute] !== undefined}
476+
{image.attributes[attribute]}
477+
{/if}
478+
</td>
476479
{/each}
477480
{#each imagePage.types as typeKey}
478481
<td><BooleanIcon value={image.types[typeKey]} /></td>
479482
{/each}
480483
<td class="col-2">
484+
<button class="btn btn-primary" on:click={() => imageModal?.openForEditing(image)}>
485+
<i class="bi bi-pencil" />
486+
Edit
487+
</button>
481488
<ConfirmActionButton
482489
modalId={'deleteConfirmImageModal-' + getIdFromValue(image.zarr_url)}
483490
style={'danger'}
@@ -548,8 +555,7 @@
548555
<div class="col-lg-3">
549556
<button
550557
class="btn btn-outline-secondary float-end"
551-
data-bs-target="#datasetAddImageModal"
552-
data-bs-toggle="modal"
558+
on:click={() => imageModal?.openForCreate()}
553559
>
554560
<i class="bi bi-plus-circle" />
555561
Add an image list entry
@@ -563,7 +569,7 @@
563569
<DatasetInfoModal {dataset} updateDatasetCallback={(d) => (dataset = d)} />
564570
<DatasetFiltersModal {dataset} updateDatasetCallback={updateDatasetFiltersCallback} />
565571
<DatasetHistoryModal {dataset} />
566-
<AddImageModal {dataset} onImageSave={searchImages} />
572+
<CreateUpdateImageModal {dataset} onImageSave={searchImages} bind:this={imageModal} />
567573
568574
<style>
569575
#dataset-images-table td:last-child,

tests/v2/images.spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,26 @@ test('Dataset images [v2]', async ({ page, project }) => {
109109
await expect(page.getByRole('row')).toHaveCount(7);
110110
});
111111

112+
await test.step('Edit image', async () => {
113+
await page.getByRole('button', { name: 'Edit' }).nth(1).click();
114+
const modal = page.locator('.modal.show');
115+
await modal.waitFor();
116+
const zarrUrlInput = modal.getByRole('textbox', { name: 'Zarr URL' });
117+
await expect(zarrUrlInput).toHaveValue('/tmp/img2');
118+
await expect(zarrUrlInput).toBeDisabled();
119+
await expect(modal.getByPlaceholder('Key')).toHaveValue('k1');
120+
await expect(modal.getByPlaceholder('Value')).toHaveValue('v1');
121+
await modal.getByPlaceholder('Value').fill('v1-mod');
122+
await modal.getByRole('button', { name: 'Add attribute' }).click();
123+
await modal.getByPlaceholder('Key').nth(1).fill('k2');
124+
await modal.getByPlaceholder('Value').nth(1).fill('9999');
125+
await modal.getByRole('combobox').nth(1).selectOption('Number');
126+
await modal.getByRole('button', { name: 'Save' }).click();
127+
await waitModalClosed(page);
128+
await page.getByRole('cell', { name: 'v1-mod' }).waitFor();
129+
await page.getByRole('cell', { name: '9999' }).waitFor();
130+
});
131+
112132
await test.step('Delete one image', async () => {
113133
await page.getByRole('button', { name: 'Delete' }).first().click();
114134
const modal = page.locator('.modal.show');

0 commit comments

Comments
 (0)