Skip to content

Commit be76881

Browse files
committed
add ability to associate simulation dataset to external datasets
1 parent 89016f5 commit be76881

File tree

3 files changed

+154
-119
lines changed

3 files changed

+154
-119
lines changed

src/components/ResourceList.svelte

Lines changed: 131 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
<svelte:options immutable={true} />
22

33
<script lang="ts">
4+
import CloseIcon from '@nasa-jpl/stellar/icons/close.svg?component';
45
import { plan } from '../stores/plan';
5-
import { resourceTypes } from '../stores/simulation';
6+
import { resourceTypes, simulationDatasetId } from '../stores/simulation';
67
import type { User } from '../types/app';
78
import type { ResourceType } from '../types/simulation';
89
import type { TimelineItemType } from '../types/timeline';
910
import effects from '../utilities/effects';
11+
import { permissionHandler } from '../utilities/permissionHandler';
1012
import { featurePermissions } from '../utilities/permissions';
1113
import ResourceListPrefix from './ResourceListPrefix.svelte';
1214
import TimelineItemList from './TimelineItemList.svelte';
1315
1416
export let user: User | null;
1517
18+
const uploadPermissionError: string = `You do not have permission to upload resources.`;
19+
1620
let resourceDataTypes: string[] = [];
1721
let hasUploadPermission: boolean = false;
22+
let isUploadVisible: boolean = false;
23+
let useSelectedSimulation: boolean = false;
24+
let uploadFiles: FileList | undefined;
25+
let uploadFileInput: HTMLInputElement;
1826
1927
$: resourceDataTypes = [...new Set($resourceTypes.map(t => t.schema.type))];
2028
$: if (user !== null && $plan !== null) {
@@ -25,10 +33,26 @@
2533
return (item as ResourceType).schema.type;
2634
}
2735
28-
async function onUploadFile(event: CustomEvent<FileList>) {
29-
const { detail: uploadFiles } = event;
30-
if ($plan && uploadFiles?.length) {
31-
await effects.uploadExternalDataset($plan, uploadFiles, user);
36+
function onShowUpload() {
37+
isUploadVisible = true;
38+
}
39+
40+
function onHideUpload() {
41+
isUploadVisible = false;
42+
}
43+
44+
async function onUpload() {
45+
if (uploadFiles !== undefined) {
46+
if ($plan && uploadFiles?.length) {
47+
await effects.uploadExternalDataset(
48+
$plan,
49+
uploadFiles,
50+
user,
51+
useSelectedSimulation ? $simulationDatasetId : undefined,
52+
);
53+
}
54+
uploadFileInput.value = '';
55+
uploadFiles = undefined;
3256
}
3357
}
3458
</script>
@@ -40,11 +64,110 @@
4064
typeNamePlural="Resources"
4165
filterOptions={resourceDataTypes.map(t => ({ label: t, value: t }))}
4266
filterName="Data Type"
43-
shouldShowUploadOption
44-
{hasUploadPermission}
4567
{getFilterValueFromItem}
4668
let:prop={item}
47-
on:upload={onUploadFile}
4869
>
70+
<div slot="header" class="upload-container" hidden={!isUploadVisible}>
71+
<button class="close-upload" type="button" on:click={onHideUpload}>
72+
<CloseIcon />
73+
</button>
74+
<div class="upload-rows">
75+
<label for="file">Resources File</label>
76+
<input
77+
class="w-100"
78+
name="file"
79+
type="file"
80+
accept="application/json,.csv,.txt"
81+
bind:files={uploadFiles}
82+
bind:this={uploadFileInput}
83+
use:permissionHandler={{
84+
hasPermission: hasUploadPermission,
85+
permissionError: uploadPermissionError,
86+
}}
87+
/>
88+
<label class="st-typography-body timeline-item-list-filter-option-label" for="simulation-association">
89+
Associate selected simulation
90+
</label>
91+
<input
92+
bind:checked={useSelectedSimulation}
93+
class="simulation-checkbox"
94+
type="checkbox"
95+
name="simulation-association"
96+
/>
97+
</div>
98+
<div class="upload-button-container">
99+
<button
100+
class="st-button secondary"
101+
disabled={!uploadFiles?.length}
102+
on:click={onUpload}
103+
use:permissionHandler={{
104+
hasPermission: hasUploadPermission,
105+
permissionError: uploadPermissionError,
106+
}}
107+
>
108+
Upload
109+
</button>
110+
</div>
111+
</div>
112+
<div slot="button">
113+
<button
114+
class="st-button secondary"
115+
on:click={onShowUpload}
116+
use:permissionHandler={{
117+
hasPermission: hasUploadPermission,
118+
permissionError: uploadPermissionError,
119+
}}
120+
>
121+
Upload Resources
122+
</button>
123+
</div>
49124
<ResourceListPrefix {item} />
50125
</TimelineItemList>
126+
127+
<style>
128+
.upload-container {
129+
background: var(--st-gray-15);
130+
border-radius: 5px;
131+
margin: 5px;
132+
padding: 8px 11px 8px;
133+
position: relative;
134+
}
135+
136+
.upload-container[hidden] {
137+
display: none;
138+
}
139+
140+
.upload-container :global(.upload-rows) {
141+
display: grid;
142+
grid-template-columns: max-content auto;
143+
justify-content: space-between;
144+
row-gap: 8px;
145+
}
146+
147+
.st-button {
148+
gap: 4px;
149+
height: 20px;
150+
}
151+
152+
.upload-container :global(.simulation-checkbox) {
153+
justify-self: left;
154+
margin: 0;
155+
}
156+
157+
.upload-container :global(.upload-button-container) {
158+
display: flex;
159+
flex-flow: row-reverse;
160+
margin-top: 8px;
161+
}
162+
163+
.upload-container :global(.close-upload) {
164+
background: none;
165+
border: 0;
166+
cursor: pointer;
167+
height: 1.3rem;
168+
padding: 0;
169+
position: absolute;
170+
right: 3px;
171+
top: 3px;
172+
}
173+
</style>

src/components/TimelineItemList.svelte

Lines changed: 12 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
<script lang="ts">
44
import ChevronDownIcon from '@nasa-jpl/stellar/icons/chevron_down.svg?component';
5-
import CloseIcon from '@nasa-jpl/stellar/icons/close.svg?component';
5+
66
import GripVerticalIcon from 'bootstrap-icons/icons/grip-vertical.svg?component';
77
import { capitalize } from 'lodash-es';
8-
import { createEventDispatcher } from 'svelte';
98
import PlusCircledIcon from '../assets/plus-circled.svg?component';
109
import { view, viewAddFilterToRow } from '../stores/views';
1110
import type {
@@ -16,7 +15,6 @@
1615
TimelineItemListFilterOption,
1716
TimelineItemType,
1817
} from '../types/timeline';
19-
import { permissionHandler } from '../utilities/permissionHandler';
2018
import { tooltip } from '../utilities/tooltip';
2119
import Input from './form/Input.svelte';
2220
import LayerPicker from './LayerPicker.svelte';
@@ -30,15 +28,8 @@
3028
export let items: TimelineItemType[] = [];
3129
export let filterOptions: TimelineItemListFilterOption[] = [];
3230
export let filterName: string = 'Filter';
33-
export let shouldShowUploadOption: boolean = false;
34-
export let hasUploadPermission: boolean = false;
3531
export let getFilterValueFromItem: (item: TimelineItemType) => string;
3632
37-
const dispatch = createEventDispatcher<{
38-
upload: FileList;
39-
}>();
40-
const uploadPermissionError: string = `You do not have permission to upload ${typeNamePlural}`;
41-
4233
let menu: Menu;
4334
let filteredItems: TimelineItemType[] = [];
4435
let textFilters: string[] = [];
@@ -47,9 +38,6 @@
4738
let layerPicker: LayerPicker;
4839
let layerPickerIndividual: LayerPicker;
4940
let timelines: Timeline[] = [];
50-
let isUploadVisible: boolean = false;
51-
let uploadFiles: FileList | undefined;
52-
let uploadFileInput: HTMLInputElement;
5341
5442
$: filteredItems = filterItems(items, filterText ? textFilters.concat(filterText) : textFilters, selectedFilters);
5543
$: timelines = $view?.definition.plan.timelines || [];
@@ -146,22 +134,6 @@
146134
e.stopPropagation();
147135
layerPickerIndividual.toggle(e, item);
148136
}
149-
150-
function onShowUpload() {
151-
isUploadVisible = true;
152-
}
153-
154-
function onHideUpload() {
155-
isUploadVisible = false;
156-
}
157-
158-
function onUpload() {
159-
if (uploadFiles !== undefined) {
160-
dispatch('upload', uploadFiles);
161-
uploadFileInput.value = '';
162-
uploadFiles = undefined;
163-
}
164-
}
165137
</script>
166138

167139
<div class="timeline-item-list">
@@ -216,57 +188,16 @@
216188
</Menu>
217189
</div>
218190
</div>
219-
{#if isUploadVisible}
220-
<fieldset class="upload-container" hidden={!isUploadVisible}>
221-
<button class="close-upload" type="button" on:click={onHideUpload}>
222-
<CloseIcon />
223-
</button>
224-
<label for="file">{typeName.charAt(0).toUpperCase() + typeName.slice(1)} File</label>
225-
<div class="upload-input-container">
226-
<input
227-
class="w-100"
228-
name="file"
229-
type="file"
230-
accept="application/json"
231-
bind:files={uploadFiles}
232-
bind:this={uploadFileInput}
233-
use:permissionHandler={{
234-
hasPermission: hasUploadPermission,
235-
permissionError: uploadPermissionError,
236-
}}
237-
/>
238-
<button
239-
class="st-button secondary"
240-
on:click={onUpload}
241-
disabled={!uploadFiles?.length}
242-
use:permissionHandler={{
243-
hasPermission: hasUploadPermission,
244-
permissionError: uploadPermissionError,
245-
}}
246-
>
247-
Upload
248-
</button>
249-
</div>
250-
</fieldset>
251-
{/if}
252191

253192
<div class="controls">
193+
<slot name="header" />
254194
<div class="controls-header st-typography-medium">
255195
<div>{typeNamePlural} ({filteredItems.length})</div>
256196
<div>
257-
{#if shouldShowUploadOption}
258-
<button
259-
class="st-button secondary"
260-
on:click={onShowUpload}
261-
use:permissionHandler={{
262-
hasPermission: hasUploadPermission,
263-
permissionError: uploadPermissionError,
264-
}}
265-
>
266-
Upload {typeNamePlural}
267-
</button>
268-
{/if}
269-
<button class="st-button secondary" on:click={onBulkAddToRow}> Add Filter to Row </button>
197+
<div class="buttons">
198+
<slot name="button" />
199+
<button class="st-button secondary" on:click={onBulkAddToRow}> Add Filter to Row </button>
200+
</div>
270201
<LayerPicker
271202
bind:this={layerPicker}
272203
rows={timelines[0]?.rows || []}
@@ -371,36 +302,6 @@
371302
flex: 1;
372303
}
373304
374-
.upload-container {
375-
background: var(--st-gray-15);
376-
border-radius: 5px;
377-
margin: 5px;
378-
padding: 8px 11px 8px;
379-
position: relative;
380-
}
381-
382-
.upload-container[hidden] {
383-
display: none;
384-
}
385-
386-
.upload-input-container {
387-
align-items: center;
388-
column-gap: 0.5rem;
389-
display: grid;
390-
grid-template-columns: auto min-content;
391-
}
392-
393-
.close-upload {
394-
background: none;
395-
border: 0;
396-
cursor: pointer;
397-
height: 1.3rem;
398-
padding: 0;
399-
position: absolute;
400-
right: 3px;
401-
top: 3px;
402-
}
403-
404305
.filters {
405306
display: flex;
406307
gap: 8px;
@@ -498,7 +399,12 @@
498399
flex: 1;
499400
}
500401
501-
.controls-header .st-button {
402+
.controls-header .buttons {
403+
display: flex;
404+
gap: 4px;
405+
}
406+
407+
.controls-header .buttons .st-button {
502408
gap: 4px;
503409
height: 20px;
504410
}

src/utilities/effects.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5726,7 +5726,12 @@ const effects = {
57265726
}
57275727
},
57285728

5729-
async uploadExternalDataset(plan: Plan, files: FileList, user: User | null): Promise<PlanDataset | null> {
5729+
async uploadExternalDataset(
5730+
plan: Plan,
5731+
files: FileList,
5732+
user: User | null,
5733+
simulationDatasetId?: number,
5734+
): Promise<number | null> {
57305735
try {
57315736
if (!gatewayPermissions.ADD_EXTERNAL_DATASET(user, plan)) {
57325737
throwPermissionError('add external datasets');
@@ -5736,12 +5741,13 @@ const effects = {
57365741

57375742
const body = new FormData();
57385743
body.append('plan_id', `${plan.id}`);
5739-
body.append('file', file, file.name);
5744+
body.append('simulation_dataset_id', `${simulationDatasetId}`);
5745+
body.append('external_dataset', file, file.name);
57405746

5741-
const uploadedDataset = await reqGateway<PlanDataset | null>('/uploadDataset', 'POST', body, user, true);
5747+
const uploadedDatasetId = await reqGateway<number | null>('/uploadDataset', 'POST', body, user, true);
57425748

5743-
if (uploadedDataset != null) {
5744-
return uploadedDataset;
5749+
if (uploadedDatasetId != null) {
5750+
return uploadedDatasetId;
57455751
}
57465752

57475753
return null;

0 commit comments

Comments
 (0)