diff --git a/databrowser/package-lock.json b/databrowser/package-lock.json index 8a13417fe..7c022bb2d 100644 --- a/databrowser/package-lock.json +++ b/databrowser/package-lock.json @@ -1,12 +1,12 @@ { "name": "databrowser", - "version": "2.2.0", + "version": "2.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "databrowser", - "version": "2.2.0", + "version": "2.3.0", "dependencies": { "@floating-ui/dom": "^1.3.0", "@headlessui/vue": "^1.7.7", diff --git a/databrowser/src/components/checkbox/CheckboxCustom.vue b/databrowser/src/components/checkbox/CheckboxCustom.vue index d7df691be..48f9f3ece 100644 --- a/databrowser/src/components/checkbox/CheckboxCustom.vue +++ b/databrowser/src/components/checkbox/CheckboxCustom.vue @@ -11,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later v-model="checked" type="checkbox" class="h-5 w-5 cursor-pointer rounded border-gray-400 text-green-700" + :disabled="disabled" :tabindex="tabbable ? undefined : -1" /> {{ label }} @@ -28,10 +29,12 @@ const props = withDefaults( modelValue: boolean; label?: string; tabbable?: boolean; + disabled?: boolean; }>(), { label: undefined, tabbable: true, + disabled: false, } ); diff --git a/databrowser/src/components/dialog/DialogCustom.vue b/databrowser/src/components/dialog/DialogCustom.vue index 2756169bf..bca90dbb1 100644 --- a/databrowser/src/components/dialog/DialogCustom.vue +++ b/databrowser/src/components/dialog/DialogCustom.vue @@ -7,50 +7,25 @@ SPDX-License-Identifier: AGPL-3.0-or-later @@ -61,10 +36,12 @@ import { DialogDescription, DialogPanel, DialogTitle, - TransitionChild, TransitionRoot, } from '@headlessui/vue'; +import DialogOverlay from './DialogOverlay.vue'; +import DialogOverlayContainer from './DialogOverlayContainer.vue'; + const emit = defineEmits(['close']); defineProps<{ isOpen: boolean }>(); diff --git a/databrowser/src/components/dialog/DialogOverlay.vue b/databrowser/src/components/dialog/DialogOverlay.vue new file mode 100644 index 000000000..1a7519258 --- /dev/null +++ b/databrowser/src/components/dialog/DialogOverlay.vue @@ -0,0 +1,11 @@ + + + diff --git a/databrowser/src/components/dialog/DialogOverlayContainer.vue b/databrowser/src/components/dialog/DialogOverlayContainer.vue new file mode 100644 index 000000000..e30536db2 --- /dev/null +++ b/databrowser/src/components/dialog/DialogOverlayContainer.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/databrowser/src/components/dialog/DialogOverlayTransitionChild.vue b/databrowser/src/components/dialog/DialogOverlayTransitionChild.vue new file mode 100644 index 000000000..6335694f3 --- /dev/null +++ b/databrowser/src/components/dialog/DialogOverlayTransitionChild.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/databrowser/src/components/dialog/DialogPanelTransitionChild.vue b/databrowser/src/components/dialog/DialogPanelTransitionChild.vue new file mode 100644 index 000000000..ea3788ebc --- /dev/null +++ b/databrowser/src/components/dialog/DialogPanelTransitionChild.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/databrowser/src/domain/cellComponents/components/cells/eventDocumentCell/EventDocumentCell.vue b/databrowser/src/domain/cellComponents/components/cells/eventDocumentCell/EventDocumentCell.vue index cf4c382b6..31c83da4b 100644 --- a/databrowser/src/domain/cellComponents/components/cells/eventDocumentCell/EventDocumentCell.vue +++ b/databrowser/src/domain/cellComponents/components/cells/eventDocumentCell/EventDocumentCell.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later @@ -36,10 +36,12 @@ const { useApiParameter } = useApiQuery(); const updateWithCurrentLanguage = ({ value }: { value?: FileEntry[] }) => { const currentLanguage = useApiParameter('language'); - const updatedFiles = value?.map((file) => ({ - ...file, - language: currentLanguage.value, - })); + const updatedFiles = + value?.map((file) => ({ + ...file, + language: currentLanguage.value, + })) || []; + emit('update', { prop: 'items', value: updatedFiles }); }; diff --git a/databrowser/src/domain/cellComponents/components/cells/eventDocumentCell/EventDocumentTab.vue b/databrowser/src/domain/cellComponents/components/cells/eventDocumentCell/EventDocumentTab.vue index 08dbda21d..2c8b4d85f 100644 --- a/databrowser/src/domain/cellComponents/components/cells/eventDocumentCell/EventDocumentTab.vue +++ b/databrowser/src/domain/cellComponents/components/cells/eventDocumentCell/EventDocumentTab.vue @@ -5,91 +5,112 @@ SPDX-License-Identifier: AGPL-3.0-or-later --> diff --git a/databrowser/src/domain/cellComponents/components/cells/eventDocumentCell/EventDocumentTable.vue b/databrowser/src/domain/cellComponents/components/cells/eventDocumentCell/EventDocumentTable.vue index d1fc607d3..d1331e4f0 100644 --- a/databrowser/src/domain/cellComponents/components/cells/eventDocumentCell/EventDocumentTable.vue +++ b/databrowser/src/domain/cellComponents/components/cells/eventDocumentCell/EventDocumentTable.vue @@ -22,7 +22,10 @@ SPDX-License-Identifier: AGPL-3.0-or-later - + (); diff --git a/databrowser/src/domain/cellComponents/components/cells/stringCell/StringCell.vue b/databrowser/src/domain/cellComponents/components/cells/stringCell/StringCell.vue index ac49ad1b7..aebc785fa 100644 --- a/databrowser/src/domain/cellComponents/components/cells/stringCell/StringCell.vue +++ b/databrowser/src/domain/cellComponents/components/cells/stringCell/StringCell.vue @@ -11,7 +11,11 @@ SPDX-License-Identifier: AGPL-3.0-or-later :model-value="text" @update:model-value="update($event)" /> - {{ text }} + {{ text }} diff --git a/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EditListDocumentLanguagesTab.vue b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EditListDocumentLanguagesTab.vue new file mode 100644 index 000000000..c03fb7c06 --- /dev/null +++ b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EditListDocumentLanguagesTab.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EditListDocumentLanguagesTabBody.vue b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EditListDocumentLanguagesTabBody.vue new file mode 100644 index 000000000..c36d1f141 --- /dev/null +++ b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EditListDocumentLanguagesTabBody.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EditListDocumentLanguagesTabHeader.vue b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EditListDocumentLanguagesTabHeader.vue new file mode 100644 index 000000000..3885c859f --- /dev/null +++ b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EditListDocumentLanguagesTabHeader.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EditListDocumentLanguagesTable.vue b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EditListDocumentLanguagesTable.vue new file mode 100644 index 000000000..8aacb6fee --- /dev/null +++ b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EditListDocumentLanguagesTable.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EventDocumentInputDialogTable.vue b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EventDocumentInputDialogTable.vue new file mode 100644 index 000000000..16bf249a3 --- /dev/null +++ b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/EventDocumentInputDialogTable.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/dialogStore.ts b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/dialogStore.ts new file mode 100644 index 000000000..2a77a1d06 --- /dev/null +++ b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/dialogStore.ts @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: NOI Techpark +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { acceptHMRUpdate, defineStore } from 'pinia'; +import { MultipleFilesLanguages, FileLanguageUpdate } from './types'; + +export const useDialogStore = defineStore('dialogStore', { + state: () => ({ _items: [] as MultipleFilesLanguages, _activeTab: 0 }), + getters: { + items(state) { + return JSON.parse(JSON.stringify(state._items)) as MultipleFilesLanguages; + }, + activeTab(state) { + return state._activeTab; + }, + }, + actions: { + setItems(items: MultipleFilesLanguages) { + this._items = JSON.parse(JSON.stringify(items)); + }, + + updateItem(index: number, update: FileLanguageUpdate) { + this._items[this._activeTab].data[index] = { + ...this._items[this._activeTab].data[index], + ...update, + }; + }, + + setActiveTab(index: number) { + this._activeTab = index; + }, + + setAvailableItemLanguage(index: number, available: boolean) { + this._items[this._activeTab].data[index].available = available; + }, + }, +}); + +// Add support for hot-module-reload +if (import.meta.hot) { + import.meta.hot.accept(acceptHMRUpdate(useDialogStore, import.meta.hot)); +} diff --git a/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/types.ts b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/types.ts new file mode 100644 index 000000000..c96bcac55 --- /dev/null +++ b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/types.ts @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: NOI Techpark +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { FileEntry } from '../../../cells/eventDocumentCell/types'; + +export interface FileEntryWithLanguageAvailability extends FileEntry { + available?: boolean; + disableAvailabilityChange?: boolean; +} + +type MultipleFileLanguages = { + name?: string; + src?: string; + data: FileEntryWithLanguageAvailability[]; +}; + +export type FileLanguageUpdate = { + documentName?: string; + available?: boolean; + language?: string; +}; + +export type MultipleFilesLanguages = Array; diff --git a/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/utils.ts b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/utils.ts new file mode 100644 index 000000000..06f9faa4c --- /dev/null +++ b/databrowser/src/domain/cellComponents/components/utils/editList/dialogMultipleFilesLanguage/utils.ts @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: NOI Techpark +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { useEventBus } from '@vueuse/core'; +import { FileEntry } from '../../../cells/eventDocumentCell/types'; +import { useEditStore } from '../../../../../datasets/editView/store/editStore'; +import { FileEntryWithLanguageAvailability } from './types'; +import { useDialogStore } from './dialogStore'; +import { FilterLanguage } from '../../../../../datasets/language'; +import { ParameterValue } from '../../../../../api/service/types'; + +export const useEventSaveChanges = useEventBus('saveChanges'); +export const useEventDiscardChanges = useEventBus('discardChanges'); + +export const getCurrentDocumentLanguageAvailability = (item: FileEntry) => { + const { Documents } = JSON.parse(useEditStore().currentAsJson); + + const foundLanguages = []; + for (const lang in Documents) { + const currentLanguageDocuments = Documents[lang]; + for (const currentDocument of currentLanguageDocuments) { + if (currentDocument.DocumentURL === item.src) { + foundLanguages.push(lang); + } + } + } + + return foundLanguages.join(', '); +}; + +export const setDataForDocumentEdit = (item: FileEntry) => { + const { Documents } = JSON.parse(useEditStore().currentAsJson); + + const dialogStore = useDialogStore(); + const dialogData = [] as FileEntryWithLanguageAvailability[]; + + for (const lang in Documents) { + const currentLanguageDocuments = Documents[lang]; + for (const currentDocument of currentLanguageDocuments) { + if (currentDocument.DocumentURL === item.src) { + dialogData.push({ + documentName: currentDocument.DocumentName, + language: lang, + // FIXME + available: true, + }); + } + } + } + + dialogStore.setItems([ + { + name: item.src, + src: item.src, + data: dialogData, + }, + ]); +}; + +export const clearDialogStore = () => { + const dialogStore = useDialogStore(); + dialogStore.setItems([]); +}; + +export const setDialogItems = ( + items: any[], + currentLanguage?: ParameterValue +) => { + const dialogStore = useDialogStore(); + + const data = []; + + const supportedLanguages = Object.values(FilterLanguage); + + for (const item of items) { + const itemData = []; + + for (const language of supportedLanguages) { + itemData.push({ + documentName: '', + language, + available: currentLanguage === language, + disableAvailabilityChange: currentLanguage === language, + }); + } + + data.push({ + name: item.name, + src: item.src, + data: itemData, + }); + } + + dialogStore.setItems(data); +}; + +export const updateItemsInModalAndSave = () => { + const editStore = useEditStore(); + const currentState = JSON.parse(editStore.currentAsJson); + const dialogStore = useDialogStore(); + const items = dialogStore.items; + + const { Documents } = currentState; + + for (const lang in Documents) { + const currentLanguageDocuments = Documents[lang]; + for (const currentDocument of currentLanguageDocuments) { + const sameDocumentInModal = items.find( + (item) => item.src === currentDocument.DocumentURL + ); + if (!sameDocumentInModal) continue; + + const itemToUpdate = sameDocumentInModal.data.find( + (item) => item.language === lang + ); + + if (!itemToUpdate) continue; + + currentDocument.DocumentName = itemToUpdate.documentName; + } + } + + editStore.setCurrent({ ...currentState, Documents }); +}; + +export const addItemsInModalAndSave = () => { + const editStore = useEditStore(); + const currentState = JSON.parse(editStore.currentAsJson); + const dialogStore = useDialogStore(); + const items = dialogStore.items; + + const { Documents } = currentState; + + for (const item of items) { + for (const itemData of item.data.filter((v) => v.available)) { + if (!itemData.language) continue; + + Documents[itemData.language] ??= []; + const uploadedDocumentIndex = Documents[itemData.language].findIndex( + (documentData: any) => documentData.DocumentURL === item.src + ); + + if (uploadedDocumentIndex >= 0) { + Documents[itemData.language][uploadedDocumentIndex].DocumentName = + itemData.documentName; + } else { + Documents[itemData.language].push({ + DocumentName: itemData.documentName, + Language: itemData.language, + DocumentURL: item.src, + }); + } + } + } + + editStore.setCurrent({ ...currentState, Documents }); +}; diff --git a/databrowser/src/domain/cellComponents/components/utils/editList/upload/EditListUpload.vue b/databrowser/src/domain/cellComponents/components/utils/editList/upload/EditListUpload.vue index 91f708384..5642e3ca7 100644 --- a/databrowser/src/domain/cellComponents/components/utils/editList/upload/EditListUpload.vue +++ b/databrowser/src/domain/cellComponents/components/utils/editList/upload/EditListUpload.vue @@ -6,6 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/databrowser/src/domain/cellComponents/components/utils/upload/FileUpload.vue b/databrowser/src/domain/cellComponents/components/utils/upload/FileUpload.vue index 2897328fc..3720fc04e 100644 --- a/databrowser/src/domain/cellComponents/components/utils/upload/FileUpload.vue +++ b/databrowser/src/domain/cellComponents/components/utils/upload/FileUpload.vue @@ -99,9 +99,14 @@ const onDrop = (filesFromDropZone: File[] | null) => { } fileTypesNotAccepted.value = undefined; + + fileNames.value = filesFromDropZone.map((item) => item.name); + uploadFiles(filesFromDropZone); }; +const fileNames = ref([] as string[]); + const { isOverDropZone } = useDropZone(dropZoneRef, onDrop); const { files, open } = useFileDialogForType({ @@ -126,12 +131,15 @@ watch(files, (filesFromFileDialog) => { } const filesToUpload = Array.from(filesFromFileDialog); + fileNames.value = filesToUpload.map((item) => item.name); // Trigger file upload uploadFiles(filesToUpload); }); -onUploadSuccess((urls: string[]) => emit('uploadSuccess', urls)); +onUploadSuccess((urls: string[]) => + emit('uploadSuccess', urls, fileNames.value) +); onUploadError((message: string) => emit('uploadError', message)); diff --git a/databrowser/src/domain/datasets/editView/EditView.vue b/databrowser/src/domain/datasets/editView/EditView.vue index 8fa3c74bf..26c42467d 100644 --- a/databrowser/src/domain/datasets/editView/EditView.vue +++ b/databrowser/src/domain/datasets/editView/EditView.vue @@ -91,7 +91,10 @@ import { useEventListener } from '@vueuse/core'; import AlertError from '../../../components/alert/AlertError.vue'; import MainAndSubCategories from '../common/MainAndSubCategories.vue'; import LoadingError from '../../../components/loading/LoadingError.vue'; - +import { + useEventSaveChanges, + useEventDiscardChanges, +} from '../../cellComponents/components/utils/editList/dialogMultipleFilesLanguage/utils'; const { t } = useI18n(); const showAll = ref(true); @@ -102,6 +105,18 @@ const editStore = useEditStore(); const datasetConfigStore = useDatasetConfigStore(); +useEventSaveChanges.on((value: boolean) => { + if (value) { + saveChanges(); + } +}); + +useEventDiscardChanges.on((value: boolean) => { + if (value) { + resetAndCleanup(); + } +}); + const { slug, categories, subcategories, currentCategory } = useCategories(); const { isError, isStartOrFetch, data, error, url } = diff --git a/databrowser/src/locales/en.json b/databrowser/src/locales/en.json index f9cacf4a7..e39b88586 100644 --- a/databrowser/src/locales/en.json +++ b/databrowser/src/locales/en.json @@ -90,6 +90,12 @@ "buttonSave": "Save changes", "description": "The changes you made have not been saved. By switching to another section your changes will be lost!", "title": "Unsaved Data!" + }, + "multilpleFilesLanguage": { + "buttonSaveAndNext": "Save & next", + "buttonDiscardChanges": "Discard changes", + "title": "Choose language", + "description": "Which language would you like to assign the uploaded document to?" } }, "footer": {