11<script setup lang="ts">
2+ import {
3+ type FileAllowedObjects ,
4+ resolveAllowedObjects ,
5+ resolveAllowedObjectsPerFile ,
6+ } from " @ogw_shared/utils/response_handlers/load.js" ;
27import FetchingData from " @ogw_front/components/FetchingData.vue" ;
38import { geode_objects } from " @ogw_front/assets/geode_objects" ;
4- import { resolveAllowedObjects } from " @ogw_shared/utils/response_handlers/load.js" ;
59import schemas from " @geode/opengeodeweb-back/opengeodeweb_back_typed_schemas.js" ;
610import { useBackStore } from " @ogw_front/stores/back" ;
711
@@ -14,8 +18,15 @@ type AllowedObjectMap = Record<string, AllowedObject>;
1418
1519const schema = schemas .opengeodeweb_back .allowed_objects ;
1620
21+ interface PendingGroup {
22+ filenames: string [];
23+ candidates: string [];
24+ selected: string ;
25+ }
26+
1727const emit = defineEmits <{
18- update_values: [value : { geode_object_type: string }];
28+ // Filename -> geode object type, files that cannot be loaded are left out
29+ update_values: [value : { geode_object_types: Record <string , string > }];
1930 increment_step: [];
2031}>();
2132
@@ -31,6 +42,14 @@ const loading = ref<boolean>(false);
3142const allowedGeodeObjects = ref <AllowedObjectMap >({});
3243const toggleLoading = useToggle (loading );
3344const multipleFilesNoCommon = ref <boolean >(false );
45+ // Types already known, files left to choose for, and files that cannot be loaded
46+ const resolvedTypes = ref <Record <string , string >>({});
47+ const pendingFilenames = ref <string []>([]);
48+ const pendingGroups = ref <PendingGroup []>([]);
49+ const skippedFiles = ref <string []>([]);
50+ const showPerFile = ref <boolean >(false );
51+ // Every file got its type without the user, nothing to display
52+ const autoResolved = ref <boolean >(false );
3453
3554async function fetchAllowedObjectsList(): Promise <AllowedObjectMap []> {
3655 const promiseArray = filenames .map ((filename ): Promise <{ allowed_objects: AllowedObjectMap }> => {
@@ -41,11 +60,43 @@ async function fetchAllowedObjectsList(): Promise<AllowedObjectMap[]> {
4160 return responses .map ((response ) => response .allowed_objects );
4261}
4362
63+ function emitGeodeObjectTypes(geode_object_types : Record <string , string >): void {
64+ emit (" update_values" , { geode_object_types });
65+ emit (" increment_step" );
66+ }
67+
4468function setGeodeObject(geode_object_type : string | undefined ): void {
45- if (geode_object_type ) {
46- emit (" update_values" , { geode_object_type });
47- emit (" increment_step" );
69+ if (! geode_object_type ) {
70+ return ;
71+ }
72+ const chosen = pendingFilenames .value .map ((filename ) => [filename , geode_object_type ]);
73+ emitGeodeObjectTypes ({ ... resolvedTypes .value , ... Object .fromEntries (chosen ) });
74+ }
75+
76+ function confirmPerFile(): void {
77+ const chosen = pendingGroups .value .flatMap ((group ) =>
78+ group .filenames .map ((filename ) => [filename , group .selected ]),
79+ );
80+ emitGeodeObjectTypes ({ ... resolvedTypes .value , ... Object .fromEntries (chosen ) });
81+ }
82+
83+ // Files with the same candidate types share one choice
84+ function groupPendingFiles(pending : readonly FileAllowedObjects []): PendingGroup [] {
85+ const groups = new Map <string , PendingGroup >();
86+ for (const file of pending ) {
87+ const key = file .candidates .join (" ," );
88+ const group = groups .get (key );
89+ if (group ) {
90+ group .filenames .push (file .filename );
91+ } else {
92+ groups .set (key , {
93+ filenames: [file .filename ],
94+ candidates: file .candidates ,
95+ selected: file .candidates [0 ] ?? " " ,
96+ });
97+ }
4898 }
99+ return [... groups .values ()];
49100}
50101
51102// The geode_objects lookup is fixed and keyed by known object type names; a missing entry only happens if the backend reports a type this table doesn't know about.
@@ -59,19 +110,63 @@ function geodeObjectImage(key: string): string | undefined {
59110 return geode_objects [key ]?.image ;
60111}
61112
113+ function showCommonGeodeObjects(
114+ pendingNames : readonly string [],
115+ allowedObjectsList : readonly AllowedObjectMap [],
116+ ): string | undefined {
117+ const resolved = resolveAllowedObjects (pendingNames , allowedObjectsList );
118+ allowedGeodeObjects .value = resolved .mergedAllowedObjects ;
119+ multipleFilesNoCommon .value = resolved .multipleFilesNoCommon ;
120+ return resolved .selectedGeodeObject ;
121+ }
122+
123+ // Each file gets its own type. Returns true when the per file panel is needed, i.e. the files cannot be handled by a single choice
124+ function resolvePerFile(allowedObjectsList : readonly AllowedObjectMap []): boolean {
125+ const files = resolveAllowedObjectsPerFile (filenames , allowedObjectsList );
126+ const loadable = files .filter ((file ) => file .candidates .length > 0 );
127+ if (loadable .length === 0 ) {
128+ // Nothing can be loaded: show why with the common types
129+ showCommonGeodeObjects (filenames , allowedObjectsList );
130+ return false ;
131+ }
132+ const pending = loadable .filter ((file ) => file .selectedGeodeObject === undefined );
133+ resolvedTypes .value = Object .fromEntries (
134+ loadable .flatMap ((file ) =>
135+ file .selectedGeodeObject === undefined ? [] : [[file .filename , file .selectedGeodeObject ]],
136+ ),
137+ );
138+ pendingFilenames .value = pending .map ((file ) => file .filename );
139+ skippedFiles .value = files
140+ .filter ((file ) => file .candidates .length === 0 )
141+ .map ((file ) => file .filename );
142+ if (skippedFiles .value .length === 0 ) {
143+ if (pending .length === 0 ) {
144+ autoResolved .value = true ;
145+ emitGeodeObjectTypes (resolvedTypes .value );
146+ return false ;
147+ }
148+ const pendingAllowedObjects = pending .map (
149+ (file ) => allowedObjectsList [filenames .indexOf (file .filename )] ?? {},
150+ );
151+ // Pending files sharing a loadable type: one choice for all of them
152+ const selected = showCommonGeodeObjects (pendingFilenames .value , pendingAllowedObjects );
153+ if (Object .values (allowedGeodeObjects .value ).some ((object ) => object .is_loadable > 0 )) {
154+ setGeodeObject (selected );
155+ return false ;
156+ }
157+ }
158+ pendingGroups .value = groupPendingFiles (pending );
159+ return true ;
160+ }
161+
62162async function getAllowedGeodeObjects(): Promise <void > {
63163 toggleLoading ();
64164 allowedGeodeObjects .value = {};
65165 multipleFilesNoCommon .value = false ;
166+ showPerFile .value = false ;
66167
67168 const allowedGeodeObjectsList = await fetchAllowedObjectsList ();
68- const resolved = resolveAllowedObjects (filenames , allowedGeodeObjectsList );
69-
70- allowedGeodeObjects .value = resolved .mergedAllowedObjects ;
71- multipleFilesNoCommon .value = resolved .multipleFilesNoCommon ;
72- if (resolved .selectedGeodeObject ) {
73- setGeodeObject (resolved .selectedGeodeObject );
74- }
169+ showPerFile .value = resolvePerFile (allowedGeodeObjectsList );
75170 toggleLoading ();
76171}
77172// oxlint-disable-next-line no-top-level-await
@@ -80,7 +175,46 @@ await getAllowedGeodeObjects();
80175
81176<template >
82177 <FetchingData v-if =" loading " />
178+ <v-container v-else-if =" showPerFile" data-testid =" perFileObjectSelector" >
179+ <v-row v-for =" group in pendingGroups" :key =" group.filenames.join()" align =" center" >
180+ <v-col cols =" auto" class =" pa-1" >
181+ <v-chip v-for =" filename in group.filenames" :key =" filename" class =" ma-1" size =" small" >
182+ {{ filename }}
183+ </v-chip >
184+ </v-col >
185+ <v-col class =" pa-1" >
186+ <v-select
187+ v-model =" group.selected"
188+ :items =" group.candidates"
189+ density =" compact"
190+ hide-details
191+ variant =" outlined"
192+ />
193+ </v-col >
194+ </v-row >
195+ <v-row v-for =" (type, filename) in resolvedTypes" :key =" filename" align =" center" >
196+ <v-chip class =" ma-1" size =" small" >{{ filename }}</v-chip >
197+ <v-chip class =" ma-1" size =" small" variant =" tonal" >{{ type }}</v-chip >
198+ </v-row >
199+ <v-row v-if =" skippedFiles.length" align =" center" >
200+ <v-icon color =" warning" icon =" mdi-file-alert-outline" class =" ma-1" />
201+ <p class =" pa-1" >Not supported, will be skipped:</p >
202+ <v-chip v-for =" filename in skippedFiles" :key =" filename" class =" ma-1" size =" small" >
203+ {{ filename }}
204+ </v-chip >
205+ </v-row >
206+ <v-row >
207+ <v-col cols =" auto" >
208+ <v-btn color =" primary" data-testid =" confirmDataTypesButton" @click =" confirmPerFile" >
209+ Continue
210+ </v-btn >
211+ </v-col >
212+ </v-row >
213+ </v-container >
83214 <v-row v-else-if =" Object.keys(allowedGeodeObjects).length" class =" justify-left" >
215+ <v-col v-if =" pendingFilenames.length && pendingFilenames.length < filenames.length" cols =" 12" >
216+ Choose the data type for: {{ pendingFilenames.join(", ") }}
217+ </v-col >
84218 <v-col v-for =" (value, key) in allowedGeodeObjects" :key =" key" cols =" 3" md =" 4" >
85219 <v-tooltip :text =" geodeObjectTooltip(key, Boolean(value['is_loadable']))" location =" bottom" >
86220 <template v-slot :activator =" { props } " >
@@ -112,7 +246,7 @@ await getAllowedGeodeObjects();
112246 </v-card-text >
113247 </v-card >
114248 </v-row >
115- <v-row v-else class =" pa-5" >
249+ <v-row v-else-if = " !autoResolved " class =" pa-5" >
116250 <v-card class =" card" variant =" tonal" rounded >
117251 <v-card-text >
118252 This file format isn't supported! Please check the
0 commit comments