Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 2.11.0-privacyPolicy
  • APIv2
  • Docs/Setup
  • Experiment/fix-debugging
  • Experimental/Heinrichs-cypress
  • Feature/xxxx-turnOffDataPub
  • Fix/xxxx-ToS400Error
  • Fix/xxxx-migrateLogin
  • Fix/xxxx-tokenUploadButton
  • Hotfix/0038-correctDownload
  • Hotfix/1917-PublicFilesVisibility
  • Hotfix/1963-fixOrganizationField
  • Hotfix/2015-PublicFilesVisibility
  • Hotfix/2130-uiv2ContactChange
  • Hotfix/2144-invitationCall
  • Hotfix/2150-fixUpload
  • Hotfix/2160-userOrgsInst
  • Hotfix/2190-requiredFieldsForUserProfile
  • Hotfix/2196-RCVTableTranslation
  • Hotfix/2212-fixFiles
  • Hotfix/2226-userProfileSaveButton
  • Hotfix/2232-dependencyFix
  • Hotfix/2233-fixMe
  • Hotfix/2258-saveButtonWorksAsExpected
  • Hotfix/2296-selectedValuesNotReturned
  • Hotfix/2308-defaultLicense
  • Hotfix/2335-fixingSearchRCV
  • Hotfix/2353-dropShape
  • Hotfix/2370-fixDeleteButton
  • Hotfix/2378-linkedFix
  • Hotfix/2379-filesDragAndDrop
  • Hotfix/2382-guestStillBuggy
  • Hotfix/2384-guestsAndLinked
  • Hotfix/2427-adminTrouble
  • Hotfix/2459-EncodingPath
  • Hotfix/2465-orcidLink
  • Hotfix/2465-orcidLink-v1.25.1
  • Hotfix/2504-formGen
  • Hotfix/2541-resCreate
  • Hotfix/2601-correctMetadataIdentity
  • Hotfix/2611-feedback
  • Hotfix/2618-turtle
  • Hotfix/2681-validationErrors
  • Hotfix/2684-correctEncoding
  • Hotfix/2684-fixSubMetadata
  • Hotfix/2713-validateEntryName
  • Hotfix/2734-allowEmptyLicense
  • Hotfix/2765-encodingAgain
  • Hotfix/2852-adaptTextForToSUi
  • Hotfix/2853-optimizationV4
  • Hotfix/2943-reloadingResources
  • Hotfix/2943-searchHighlighting
  • Hotfix/2957-styleAndUpgrade
  • Hotfix/2971-fixTextInDataPub
  • Hotfix/2989-cookieLength
  • Hotfix/662-keepSidebarExpanded
  • Hotfix/xxxx-correctLinking
  • Hotfix/xxxx-folderRecursive
  • Hotfix/xxxx-fullscreenCss
  • Hotfix/xxxx-homepageDisplay
  • Hotfix/xxxx-liveReleaseFixes
  • Hotfix/xxxx-partnerProjects
  • Hotfix/xxxx-workingFileIndex
  • Issue/1782-structualDataIntegration
  • Issue/1792-newMetadataStructure
  • Issue/1822-coscineUIv2App
  • Issue/1824-componentsUIv2
  • Issue/1824-routerAdditions
  • Issue/1825-codeQualityPipelines
  • Issue/1833-newLogin
  • Issue/1843-multipleFilesValidation
  • Issue/1860-searchScoping
  • Issue/1861-searchMetadata
  • Issue/1862-searchFacets
  • Issue/1863-paginationForSearch
  • Issue/1926-userProfile
  • Issue/1927-projectAppMigration
  • Issue/1928-sidebarmenuAddition
  • Issue/1929-vuexToPinia
  • Issue/1938-internalHandling
  • Issue/1951-quotaImplementation
  • Issue/1953-owlImports
  • Issue/1957-resourceAppMigration
  • Issue/1957-resourceAppMigrationNew
  • Issue/1962-SearchAppUI2
  • Issue/1964-tokenExpiryUIv2
  • Issue/1965-userListMigration
  • Issue/1970-breadcrumbs
  • Issue/1971-projectEditCreateMigration
  • Issue/1972-homeDepot
  • Issue/1974-shibbolethLogout
  • Issue/1976-resouceCreationVaildEmail
  • Issue/1979-supportAdminUIv2Migration
  • Issue/1980-userManagement
  • Issue/1985-adaptSidebar
  • Issue/2002-migrateResourceCreate
  • Issue/2003-resourceSettings
  • Issue/2008-quotaManagement
  • Issue/2011-pathConfig
  • Issue/2016-BannerMigration
  • 1.28.0-pilot
  • v1.0.0
  • v1.1.0
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.10.3
  • v1.11.0
  • v1.11.1
  • v1.11.2
  • v1.11.3
  • v1.11.4
  • v1.11.5
  • v1.11.6
  • v1.11.7
  • v1.12.0
  • v1.13.0
  • v1.14.0
  • v1.14.1
  • v1.14.2
  • v1.14.3
  • v1.15.0
  • v1.15.1
  • v1.16.0
  • v1.16.1
  • v1.16.2
  • v1.16.3
  • v1.17.0
  • v1.17.1
  • v1.17.2
  • v1.18.0
  • v1.18.1
  • v1.19.0
  • v1.2.0
  • v1.20.0
  • v1.20.1
  • v1.20.2
  • v1.20.3
  • v1.20.4
  • v1.20.5
  • v1.21.0
  • v1.22.0
  • v1.22.1
  • v1.22.2
  • v1.23.0
  • v1.23.1
  • v1.23.2
  • v1.23.3
  • v1.23.4
  • v1.23.5
  • v1.23.6
  • v1.23.6-patch-2417-2427
  • v1.24.0
  • v1.24.1
  • v1.25.0
  • v1.25.1
  • v1.26.0
  • v1.26.1
  • v1.27.0
  • v1.27.1
  • v1.27.1-pilot
  • v1.28.0
  • v1.29.0
  • v1.29.1
  • v1.29.2
  • v1.3.0
  • v1.30.0
  • v1.30.1
  • v1.30.2
  • v1.31.0
  • v1.32.0
  • v1.4.0
  • v1.4.1
  • v1.5.0
  • v1.6.0
  • v1.6.1
  • v1.6.2
  • v1.7.0
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.9.0
  • v2.0.0
  • v2.1.0
  • v2.10.0
  • v2.10.1
  • v2.11.0
  • v2.12.0
  • v2.12.1
  • v2.12.2
  • v2.12.3
  • v2.12.4
  • v2.12.5
  • v2.13.0
  • v2.13.1
  • v2.13.2
  • v2.13.3
  • v2.13.4
  • v2.14.0
  • v2.15.0
200 results

Target

Select target project
  • coscine/frontend/apps/ui
1 result
Select Git revision
  • 2.11.0-privacyPolicy
  • APIv2
  • Docs/Setup
  • Experiment/fix-debugging
  • Experimental/Heinrichs-cypress
  • Feature/xxxx-turnOffDataPub
  • Fix/xxxx-ToS400Error
  • Fix/xxxx-migrateLogin
  • Fix/xxxx-tokenUploadButton
  • Hotfix/0038-correctDownload
  • Hotfix/1917-PublicFilesVisibility
  • Hotfix/1963-fixOrganizationField
  • Hotfix/2015-PublicFilesVisibility
  • Hotfix/2130-uiv2ContactChange
  • Hotfix/2144-invitationCall
  • Hotfix/2150-fixUpload
  • Hotfix/2160-userOrgsInst
  • Hotfix/2190-requiredFieldsForUserProfile
  • Hotfix/2196-RCVTableTranslation
  • Hotfix/2212-fixFiles
  • Hotfix/2226-userProfileSaveButton
  • Hotfix/2232-dependencyFix
  • Hotfix/2233-fixMe
  • Hotfix/2258-saveButtonWorksAsExpected
  • Hotfix/2296-selectedValuesNotReturned
  • Hotfix/2308-defaultLicense
  • Hotfix/2335-fixingSearchRCV
  • Hotfix/2353-dropShape
  • Hotfix/2370-fixDeleteButton
  • Hotfix/2378-linkedFix
  • Hotfix/2379-filesDragAndDrop
  • Hotfix/2382-guestStillBuggy
  • Hotfix/2384-guestsAndLinked
  • Hotfix/2427-adminTrouble
  • Hotfix/2459-EncodingPath
  • Hotfix/2465-orcidLink
  • Hotfix/2465-orcidLink-v1.25.1
  • Hotfix/2504-formGen
  • Hotfix/2541-resCreate
  • Hotfix/2601-correctMetadataIdentity
  • Hotfix/2611-feedback
  • Hotfix/2618-turtle
  • Hotfix/2681-validationErrors
  • Hotfix/2684-correctEncoding
  • Hotfix/2684-fixSubMetadata
  • Hotfix/2713-validateEntryName
  • Hotfix/2734-allowEmptyLicense
  • Hotfix/2765-encodingAgain
  • Hotfix/2852-adaptTextForToSUi
  • Hotfix/2853-optimizationV4
  • Hotfix/2943-reloadingResources
  • Hotfix/2943-searchHighlighting
  • Hotfix/2957-styleAndUpgrade
  • Hotfix/2971-fixTextInDataPub
  • Hotfix/2989-cookieLength
  • Hotfix/662-keepSidebarExpanded
  • Hotfix/xxxx-correctLinking
  • Hotfix/xxxx-folderRecursive
  • Hotfix/xxxx-fullscreenCss
  • Hotfix/xxxx-homepageDisplay
  • Hotfix/xxxx-liveReleaseFixes
  • Hotfix/xxxx-partnerProjects
  • Hotfix/xxxx-workingFileIndex
  • Issue/1782-structualDataIntegration
  • Issue/1792-newMetadataStructure
  • Issue/1822-coscineUIv2App
  • Issue/1824-componentsUIv2
  • Issue/1824-routerAdditions
  • Issue/1825-codeQualityPipelines
  • Issue/1833-newLogin
  • Issue/1843-multipleFilesValidation
  • Issue/1860-searchScoping
  • Issue/1861-searchMetadata
  • Issue/1862-searchFacets
  • Issue/1863-paginationForSearch
  • Issue/1926-userProfile
  • Issue/1927-projectAppMigration
  • Issue/1928-sidebarmenuAddition
  • Issue/1929-vuexToPinia
  • Issue/1938-internalHandling
  • Issue/1951-quotaImplementation
  • Issue/1953-owlImports
  • Issue/1957-resourceAppMigration
  • Issue/1957-resourceAppMigrationNew
  • Issue/1962-SearchAppUI2
  • Issue/1964-tokenExpiryUIv2
  • Issue/1965-userListMigration
  • Issue/1970-breadcrumbs
  • Issue/1971-projectEditCreateMigration
  • Issue/1972-homeDepot
  • Issue/1974-shibbolethLogout
  • Issue/1976-resouceCreationVaildEmail
  • Issue/1979-supportAdminUIv2Migration
  • Issue/1980-userManagement
  • Issue/1985-adaptSidebar
  • Issue/2002-migrateResourceCreate
  • Issue/2003-resourceSettings
  • Issue/2008-quotaManagement
  • Issue/2011-pathConfig
  • Issue/2016-BannerMigration
  • 1.28.0-pilot
  • v1.0.0
  • v1.1.0
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.10.3
  • v1.11.0
  • v1.11.1
  • v1.11.2
  • v1.11.3
  • v1.11.4
  • v1.11.5
  • v1.11.6
  • v1.11.7
  • v1.12.0
  • v1.13.0
  • v1.14.0
  • v1.14.1
  • v1.14.2
  • v1.14.3
  • v1.15.0
  • v1.15.1
  • v1.16.0
  • v1.16.1
  • v1.16.2
  • v1.16.3
  • v1.17.0
  • v1.17.1
  • v1.17.2
  • v1.18.0
  • v1.18.1
  • v1.19.0
  • v1.2.0
  • v1.20.0
  • v1.20.1
  • v1.20.2
  • v1.20.3
  • v1.20.4
  • v1.20.5
  • v1.21.0
  • v1.22.0
  • v1.22.1
  • v1.22.2
  • v1.23.0
  • v1.23.1
  • v1.23.2
  • v1.23.3
  • v1.23.4
  • v1.23.5
  • v1.23.6
  • v1.23.6-patch-2417-2427
  • v1.24.0
  • v1.24.1
  • v1.25.0
  • v1.25.1
  • v1.26.0
  • v1.26.1
  • v1.27.0
  • v1.27.1
  • v1.27.1-pilot
  • v1.28.0
  • v1.29.0
  • v1.29.1
  • v1.29.2
  • v1.3.0
  • v1.30.0
  • v1.30.1
  • v1.30.2
  • v1.31.0
  • v1.32.0
  • v1.4.0
  • v1.4.1
  • v1.5.0
  • v1.6.0
  • v1.6.1
  • v1.6.2
  • v1.7.0
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.9.0
  • v2.0.0
  • v2.1.0
  • v2.10.0
  • v2.10.1
  • v2.11.0
  • v2.12.0
  • v2.12.1
  • v2.12.2
  • v2.12.3
  • v2.12.4
  • v2.12.5
  • v2.13.0
  • v2.13.1
  • v2.13.2
  • v2.13.3
  • v2.13.4
  • v2.14.0
  • v2.15.0
200 results
Show changes
Commits on Source (11)
Showing
with 235 additions and 59 deletions
{ {
"name": "ui", "name": "ui",
"version": "2.13.4", "version": "2.14.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
......
...@@ -240,6 +240,7 @@ ...@@ -240,6 +240,7 @@
<b-form-input <b-form-input
:id="'resourceQuotaSlider-' + data.item.resource.id" :id="'resourceQuotaSlider-' + data.item.resource.id"
v-model="data.item.reserved.value" v-model="data.item.reserved.value"
class="resourceQuotaSlider"
type="range" type="range"
:min="minGiB(data.item.resource.id)" :min="minGiB(data.item.resource.id)"
:max="maxGiB(data.item.resource.id)" :max="maxGiB(data.item.resource.id)"
...@@ -627,7 +628,7 @@ export default defineComponent({ ...@@ -627,7 +628,7 @@ export default defineComponent({
.list-group { .list-group {
width: 20%; width: 20%;
} }
#resourceQuotaSlider { .resourceQuotaSlider {
width: calc(100% - 8em); width: calc(100% - 8em);
padding-top: 0.6rem; padding-top: 0.6rem;
} }
......
...@@ -187,8 +187,18 @@ ...@@ -187,8 +187,18 @@
track-by="displayName" track-by="displayName"
:show-labels="false" :show-labels="false"
:placeholder="$t('form.project.projectOrganization')" :placeholder="$t('form.project.projectOrganization')"
@search-change="retrieveOrganizations" @search-change="retrieveMoreOrganizations"
> >
<template slot="afterList">
<div
v-if="organizations.length && !organizationsComplete"
v-observe-visibility="retrieveMoreOrganizations"
>
<div class="d-flex justify-content-left my-3 pl-3">
<b-spinner v-if="isLoadingOrganizations" small />
</div>
</div>
</template>
<template #noOptions> <template #noOptions>
{{ $t("form.project.projectOrganizationNoOptions") }} {{ $t("form.project.projectOrganizationNoOptions") }}
</template> </template>
...@@ -320,6 +330,7 @@ import { ...@@ -320,6 +330,7 @@ import {
} from "@/mapping/project"; } from "@/mapping/project";
import moment from "moment"; import moment from "moment";
import "@/plugins/deprecated/vue-multiselect"; import "@/plugins/deprecated/vue-multiselect";
import "@/plugins/vue-observe-visibility";
// import the store for current module // import the store for current module
import useProjectStore from "../../store"; import useProjectStore from "../../store";
...@@ -407,6 +418,9 @@ export default defineComponent({ ...@@ -407,6 +418,9 @@ export default defineComponent({
this.projectStore.defaultOrganizations this.projectStore.defaultOrganizations
); );
}, },
organizationsComplete(): boolean {
return this.projectStore.organizationsComplete;
},
visibilities(): VisibilityDto[] | null { visibilities(): VisibilityDto[] | null {
return this.projectStore.visibilities; return this.projectStore.visibilities;
}, },
...@@ -498,23 +512,14 @@ export default defineComponent({ ...@@ -498,23 +512,14 @@ export default defineComponent({
this.v$.projectForManipulation.keywords?.$touch(); this.v$.projectForManipulation.keywords?.$touch();
}, },
async retrieveOrganizations(search: string) { async retrieveMoreOrganizations(search: string | boolean) {
clearTimeout(this.queryTimer); clearTimeout(this.queryTimer);
// Replace the list of available organizations // Add delay to API organization retrieval
// with the default one after the search field was cleared this.queryTimer = window.setTimeout(async () => {
if (search.trim() === "") { this.isLoadingOrganizations = true;
// See computed property organizations' definition await this.projectStore.retrieveMoreOrganizations(search);
this.projectStore.organizations = null; this.isLoadingOrganizations = false;
} }, 300);
// Fetch organizations based on the search query
else {
// Add delay to API organization retrieval
this.queryTimer = window.setTimeout(async () => {
this.isLoadingOrganizations = true;
await this.projectStore.retrieveOrganizations(search);
this.isLoadingOrganizations = false;
}, 1000);
}
}, },
copyMetadataFromParent() { copyMetadataFromParent() {
......
...@@ -69,6 +69,8 @@ export const useProjectStore = defineStore({ ...@@ -69,6 +69,8 @@ export const useProjectStore = defineStore({
disciplines: null, disciplines: null,
licenses: null, licenses: null,
organizations: null, organizations: null,
organizationsComplete: false,
organizationsFilter: "",
roles: null, roles: null,
visibilities: null, visibilities: null,
}), }),
...@@ -512,17 +514,32 @@ export const useProjectStore = defineStore({ ...@@ -512,17 +514,32 @@ export const useProjectStore = defineStore({
} }
}, },
async retrieveOrganizations(filter = "") { async retrieveMoreOrganizations(filter: string | boolean) {
const notificationStore = useNotificationStore(); const notificationStore = useNotificationStore();
try { try {
const organizationDtos = await wrapListRequest((pageNumber: number) => if (typeof filter === "boolean") {
OrganizationApi.getOrganizations({ filter = this.organizationsFilter;
searchTerm: filter, }
pageNumber, if (this.organizationsComplete && this.organizationsFilter === filter) {
pageSize: 50, return;
}), }
); if (this.organizationsFilter !== filter) {
this.organizations = organizationDtos; this.organizations = [];
this.organizationsFilter = filter;
}
if (this.organizations === null) {
this.organizations = [];
}
const organizationResponse = await OrganizationApi.getOrganizations({
searchTerm: filter,
pageSize: 10,
pageNumber: Math.trunc(this.organizations.length / 10) + 1,
});
if (organizationResponse.data.data) {
this.organizationsComplete =
organizationResponse.data.pagination?.hasNext === false;
this.organizations.push(...organizationResponse.data.data);
}
} catch (error) { } catch (error) {
// Handle other Status Codes // Handle other Status Codes
notificationStore.postApiErrorNotification(error as AxiosError); notificationStore.postApiErrorNotification(error as AxiosError);
......
...@@ -40,6 +40,8 @@ export interface ProjectState { ...@@ -40,6 +40,8 @@ export interface ProjectState {
disciplines: DisciplineDto[] | null; disciplines: DisciplineDto[] | null;
licenses: LicenseDto[] | null; licenses: LicenseDto[] | null;
organizations: OrganizationDto[] | null; organizations: OrganizationDto[] | null;
organizationsComplete: boolean;
organizationsFilter: string;
roles: RoleDto[] | null; roles: RoleDto[] | null;
visibilities: VisibilityDto[] | null; visibilities: VisibilityDto[] | null;
} }
...@@ -109,12 +109,12 @@ ...@@ -109,12 +109,12 @@
/> />
<!-- Icon --> <!-- Icon -->
<b-icon <b-icon
:icon="row.item.isFolder ? 'folder' : 'file-earmark'" :icon="row.item.type === 'Tree' ? 'folder' : 'file-earmark'"
class="ml-1" class="ml-1"
/> />
</span> </span>
<a <a
v-if="!editableDataUrl || row.item.isFolder" v-if="!editableDataUrl || row.item.type === 'Tree'"
class="fileViewEntry" class="fileViewEntry"
@click="triggerNavigation(row.item)" @click="triggerNavigation(row.item)"
> >
...@@ -139,7 +139,7 @@ ...@@ -139,7 +139,7 @@
<!-- Option "Download" --> <!-- Option "Download" -->
<b-dropdown-item <b-dropdown-item
v-if="!editableDataUrl && !row.item.isFolder" v-if="!editableDataUrl && row.item.type === 'Leaf'"
@click="openFile(row.item)" @click="openFile(row.item)"
> >
{{ $t("page.resource.metadataManagerBtnDownload") }} {{ $t("page.resource.metadataManagerBtnDownload") }}
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
@downloadExtractedMetadata="downloadExtractedMetadata" @downloadExtractedMetadata="downloadExtractedMetadata"
@downloadMetadata="downloadMetadata" @downloadMetadata="downloadMetadata"
@selectFiles="selectFiles" @selectFiles="selectFiles"
@selectFolders="selectFolders"
@showModalDeleteFolderContents="showModalDeleteFolderContents" @showModalDeleteFolderContents="showModalDeleteFolderContents"
/> />
...@@ -248,6 +249,7 @@ export default defineComponent({ ...@@ -248,6 +249,7 @@ export default defineComponent({
emits: { emits: {
clickFileSelect: () => true, clickFileSelect: () => true,
clickFolderSelect: () => true,
emptyFileLists: (_: boolean) => true, emptyFileLists: (_: boolean) => true,
isUploading: (_: boolean) => true, isUploading: (_: boolean) => true,
navigateTree: () => true, navigateTree: () => true,
...@@ -399,7 +401,8 @@ export default defineComponent({ ...@@ -399,7 +401,8 @@ export default defineComponent({
return ( return (
this.isGuest || this.isGuest ||
this.resource?.archived || this.resource?.archived ||
this.currentVersion !== this.currentFolderContent?.latestVersion || (this.currentFileId !== -1 &&
this.currentVersion !== this.currentFolderContent?.latestVersion) ||
this.loadingVersionMetadata this.loadingVersionMetadata
); );
}, },
...@@ -441,7 +444,8 @@ export default defineComponent({ ...@@ -441,7 +444,8 @@ export default defineComponent({
!this.valid || !this.valid ||
this.isUploading || this.isUploading ||
this.currentView === "Extracted" || this.currentView === "Extracted" ||
this.currentFolderContent?.latestVersion !== this.currentVersion || (this.currentFileId !== -1 &&
this.currentVersion !== this.currentFolderContent?.latestVersion) ||
(this.currentFolderContent?.type === "Leaf" && (this.currentFolderContent?.type === "Leaf" &&
((this.editableDataUrl && !this.currentFolderContent.dataUrl) || ((this.editableDataUrl && !this.currentFolderContent.dataUrl) ||
(this.editableKey && !this.currentFolderContent.name))) (this.editableKey && !this.currentFolderContent.name)))
...@@ -472,17 +476,20 @@ export default defineComponent({ ...@@ -472,17 +476,20 @@ export default defineComponent({
watch: { watch: {
currentVersion() { currentVersion() {
this.setExtractedMetadata(); if (this.fileListUpload.length === 0) {
this.setExtractedMetadata();
}
}, },
currentViewedMetadata() { currentViewedMetadata() {
this.currentUsedMetadata = this.currentViewedMetadata; this.currentUsedMetadata = this.currentViewedMetadata;
}, },
fileListUpload() { fileListUpload() {
this.currentView = "Metadata";
this.getOptions(); this.getOptions();
}, },
fileListEdit(newVal, oldVal) { fileListEdit(newVal: FolderContent[], oldVal: FolderContent[]) {
// Trigger only the value really changed. Otherwise double API calls are triggered. // Trigger only the value really changed. Otherwise double API calls are triggered.
if (newVal !== oldVal) { if (newVal !== oldVal && newVal.length !== 0) {
this.getOptions(); this.getOptions();
this.setExtractedMetadata(); this.setExtractedMetadata();
} }
...@@ -737,7 +744,7 @@ export default defineComponent({ ...@@ -737,7 +744,7 @@ export default defineComponent({
for (const currentFile of this.shownFiles) { for (const currentFile of this.shownFiles) {
if ( if (
(this.uploadDuplicates || (this.uploadDuplicates ||
this.folderContents.find((x) => x.name === currentFile.name) === this.folderContents.find((x) => x.path === currentFile.path) ===
undefined) && undefined) &&
currentFile.type === "Leaf" currentFile.type === "Leaf"
) { ) {
...@@ -777,7 +784,7 @@ export default defineComponent({ ...@@ -777,7 +784,7 @@ export default defineComponent({
this.uploadFileListReplaceFiles = []; this.uploadFileListReplaceFiles = [];
for (const fileToUpload of this.fileListUpload) { for (const fileToUpload of this.fileListUpload) {
if ( if (
this.folderContents.find((x) => x.name === fileToUpload.name) === this.folderContents.find((x) => x.path === fileToUpload.path) ===
undefined undefined
) { ) {
this.uploadFileListNewFiles.push(fileToUpload); this.uploadFileListNewFiles.push(fileToUpload);
...@@ -901,7 +908,7 @@ export default defineComponent({ ...@@ -901,7 +908,7 @@ export default defineComponent({
async handleUploadContent(contents: File, file: FileInformation) { async handleUploadContent(contents: File, file: FileInformation) {
if (this.project?.id && this.resource?.id) { if (this.project?.id && this.resource?.id) {
const fileExists = this.folderContents.some( const fileExists = this.folderContents.some(
(x) => x.name === file.name, (x) => x.path === file.path,
); );
this.progressStatus = 0; this.progressStatus = 0;
...@@ -936,7 +943,7 @@ export default defineComponent({ ...@@ -936,7 +943,7 @@ export default defineComponent({
} }
// Update or emit the updated folder contents // Update or emit the updated folder contents
const entry = this.folderContents.find((x) => x.name === file.name); const entry = this.folderContents.find((x) => x.path === file.path);
if (entry === undefined) { if (entry === undefined) {
const identifier = uuidv4(); const identifier = uuidv4();
const version = +new Date(); const version = +new Date();
...@@ -1036,6 +1043,13 @@ export default defineComponent({ ...@@ -1036,6 +1043,13 @@ export default defineComponent({
this.$emit("clickFileSelect"); this.$emit("clickFileSelect");
}, },
/**
* Emits an event signaling the selection of folder.
*/
selectFolders() {
this.$emit("clickFolderSelect");
},
/** /**
* Retrieves the current extracted metadata. * Retrieves the current extracted metadata.
* *
...@@ -1139,7 +1153,7 @@ export default defineComponent({ ...@@ -1139,7 +1153,7 @@ export default defineComponent({
} }
// Find the corresponding file entry from folder contents // Find the corresponding file entry from folder contents
const tmp = this.folderContents.find((x) => x.name === file.name); const tmp = this.folderContents.find((x) => x.path === file.path);
// If the file has a data URL and is editable, handle its content upload // If the file has a data URL and is editable, handle its content upload
if ( if (
......
<template> <template>
<b-row id="metadataManagerButtonRowTop" align-h="between"> <b-row id="metadataManagerButtonRowTop" align-h="between">
<b-col> <b-col>
<!-- Select Files Button --> <!-- Input Group with Upload button -->
<span id="buttonSelectFilesWrapper"> <b-input-group id="metadataManagerUploadMenu">
<b-dropdown
id="buttonSelectFoldersWrapper"
size="sm"
left
:disabled="uploadDisabled || editableDataUrl"
>
<b-dropdown-item
id="buttonSelectFolder"
@click="$emit('selectFolders')"
>
{{ $t("page.resource.metadataManagerBtnSelectFolders") }}
</b-dropdown-item>
</b-dropdown>
<!-- Select Files Button -->
<b-button <b-button
v-if="!isGuest" v-if="!isGuest"
id="buttonSelectFiles" id="buttonSelectFiles"
variant="secondary" variant="secondary"
:placeholder="$t('page.resource.metadataManagerBtnSelectFiles')" :placeholder="$t('page.resource.metadataManagerBtnSelectFiles')"
:disabled="uploadDisabled" :disabled="uploadDisabled"
autofocus
@click="$emit('selectFiles')" @click="$emit('selectFiles')"
> >
{{ $t("page.resource.metadataManagerBtnSelectFiles") }} {{ $t("page.resource.metadataManagerBtnSelectFiles") }}
</b-button> </b-button>
</span> </b-input-group>
<b-tooltip
v-if="uploadDisabled && uploadDisabledReason"
target="metadataManagerUploadMenu"
triggers="hover"
placement="bottom"
boundary="viewport"
>
{{ uploadDisabledReason }}
</b-tooltip>
<b-tooltip <b-tooltip
v-if="uploadDisabled && uploadDisabledReason" v-if="uploadDisabled && uploadDisabledReason"
target="buttonSelectFilesWrapper" target="buttonSelectFoldersWrapper"
triggers="hover" triggers="hover"
placement="bottom" placement="bottom"
boundary="viewport" boundary="viewport"
...@@ -201,6 +223,13 @@ export default defineComponent({ ...@@ -201,6 +223,13 @@ export default defineComponent({
#metadataManagerButtonRowTop { #metadataManagerButtonRowTop {
margin-bottom: 5px; margin-bottom: 5px;
} }
#metadataManagerUploadMenu {
margin-right: 5px;
width: auto;
}
#metadataManagerUploadMenu button {
border-radius: 0px;
}
#metadataManagerDropDownMenu { #metadataManagerDropDownMenu {
margin-right: 5px; margin-right: 5px;
width: auto; width: auto;
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
:pressed="false" :pressed="false"
variant="outline-secondary" variant="outline-secondary"
@click="$emit('changeMetadata', index)" @click="$emit('changeMetadata', index)"
>{{ item.name >{{ item.path
}}<b-spinner }}<b-spinner
v-show="item.type === 'Leaf' && item.uploading" v-show="item.type === 'Leaf' && item.uploading"
label="Spinning" label="Spinning"
......
...@@ -127,6 +127,7 @@ export default { ...@@ -127,6 +127,7 @@ export default {
metadataManagerBtnUpload: "Hochladen", metadataManagerBtnUpload: "Hochladen",
metadataManagerBtnSelectFiles: "Dateien auswählen", metadataManagerBtnSelectFiles: "Dateien auswählen",
metadataManagerBtnSelectFolders: "Ordner auswählen",
metadataManagerBtnCantUploadArchived: metadataManagerBtnCantUploadArchived:
"Uploads sind deaktiviert, weil die aktuelle Ressource archiviert ist.", "Uploads sind deaktiviert, weil die aktuelle Ressource archiviert ist.",
...@@ -441,6 +442,7 @@ export default { ...@@ -441,6 +442,7 @@ export default {
metadataManagerBtnDownload: "Öffnen", metadataManagerBtnDownload: "Öffnen",
metadataManagerBtnUpload: "Speichern", metadataManagerBtnUpload: "Speichern",
metadataManagerBtnSelectFiles: "Neuer Eintrag", metadataManagerBtnSelectFiles: "Neuer Eintrag",
metadataManagerBtnSelectFolders: "Neuer Ordner",
infoFileType: "Eintrag", infoFileType: "Eintrag",
infoFileTypeFolder: "Ordner", infoFileTypeFolder: "Ordner",
......
...@@ -125,6 +125,7 @@ export default { ...@@ -125,6 +125,7 @@ export default {
metadataManagerBtnUpload: "Upload", metadataManagerBtnUpload: "Upload",
metadataManagerBtnSelectFiles: "Select Files", metadataManagerBtnSelectFiles: "Select Files",
metadataManagerBtnSelectFolders: "Select Folders",
metadataManagerBtnCantUploadArchived: metadataManagerBtnCantUploadArchived:
"Upload disabled because the current resource is archived.", "Upload disabled because the current resource is archived.",
...@@ -431,6 +432,7 @@ export default { ...@@ -431,6 +432,7 @@ export default {
metadataManagerBtnDownload: "Open", metadataManagerBtnDownload: "Open",
metadataManagerBtnUpload: "Save", metadataManagerBtnUpload: "Save",
metadataManagerBtnSelectFiles: "New Entry", metadataManagerBtnSelectFiles: "New Entry",
metadataManagerBtnSelectFolders: "New folder",
infoFileType: "Entry Type", infoFileType: "Entry Type",
infoFileTypeFolder: "Folder", infoFileTypeFolder: "Folder",
......
...@@ -29,6 +29,17 @@ ...@@ -29,6 +29,17 @@
@input="fileListUploadSelected" @input="fileListUploadSelected"
/> />
<!-- Form File Window -->
<b-form-file
ref="folderTrigger"
multiple
class="mt-3"
plain
directory
no-traverse
@input="fileListUploadSelected"
/>
<!-- Files View Column --> <!-- Files View Column -->
<span id="filesViewSpan"> <span id="filesViewSpan">
<div <div
...@@ -90,6 +101,7 @@ ...@@ -90,6 +101,7 @@
@isUploading="setIsUploading" @isUploading="setIsUploading"
@showModalDelete="showModalDelete" @showModalDelete="showModalDelete"
@clickFileSelect="clickFileSelect" @clickFileSelect="clickFileSelect"
@clickFolderSelect="clickFolderSelect"
/> />
<!-- Toggle Fullscreen Button --> <!-- Toggle Fullscreen Button -->
...@@ -141,6 +153,7 @@ import MetadataManager from "../components/resource-page/MetadataManager.vue"; ...@@ -141,6 +153,7 @@ import MetadataManager from "../components/resource-page/MetadataManager.vue";
import OversizedFilesModal from "../components/resource-page/modals/OversizedFilesModal.vue"; import OversizedFilesModal from "../components/resource-page/modals/OversizedFilesModal.vue";
import DeleteFolderContentsModal from "../components/resource-page/modals/DeleteFolderContentsModal.vue"; import DeleteFolderContentsModal from "../components/resource-page/modals/DeleteFolderContentsModal.vue";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { type ExtendedFile, FileUtil } from "@/modules/resource/utils/FileUtil";
import factory from "rdf-ext"; import factory from "rdf-ext";
import type { BFormFile, BTable } from "bootstrap-vue"; import type { BFormFile, BTable } from "bootstrap-vue";
import type { Dataset } from "@rdfjs/types"; import type { Dataset } from "@rdfjs/types";
...@@ -277,6 +290,29 @@ export default defineComponent({ ...@@ -277,6 +290,29 @@ export default defineComponent({
} }
}, },
/**
* Trigger folder selection, if not editable data URL is provided.
*/
clickFolderSelect() {
this.showDetail = false;
// Check if editableDataUrl is present
if (
this.resourceTypeInformation?.resourceContent?.metadataView
?.editableDataUrl
) {
this.emptyFileLists();
} else {
// Trigger a click event on fileTrigger element
(this.$refs.folderTrigger as BFormFile).$el.dispatchEvent(
new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
}),
);
}
},
/** /**
* Display the modal for file deletion and set the files to be deleted. * Display the modal for file deletion and set the files to be deleted.
* @param {FolderContent[]} files - Files to be deleted. * @param {FolderContent[]} files - Files to be deleted.
...@@ -356,9 +392,9 @@ export default defineComponent({ ...@@ -356,9 +392,9 @@ export default defineComponent({
/** /**
* Add selected files to the fileListUpload list. * Add selected files to the fileListUpload list.
* @param {File[] | File} selectedFiles - Selected files. * @param {ExtendedFile[] | ExtendedFile} selectedFiles - Selected files.
*/ */
fileListUploadSelected(selectedFiles: File[] | File) { fileListUploadSelected(selectedFiles: ExtendedFile[] | ExtendedFile) {
if (!Array.isArray(selectedFiles)) { if (!Array.isArray(selectedFiles)) {
selectedFiles = [selectedFiles]; selectedFiles = [selectedFiles];
} }
...@@ -369,10 +405,23 @@ export default defineComponent({ ...@@ -369,10 +405,23 @@ export default defineComponent({
const identifier = uuidv4(); const identifier = uuidv4();
const version = +new Date(); const version = +new Date();
const $path = file.$path ? file.$path : null;
// Correctly determine the parentDirectory based on multiple inputs
const parentDirectory = $path
? $path.substring(0, $path.lastIndexOf("/"))
: file.path ?? "";
// Correctly determine the path based on multiple inputs (check for .path or $path if .name still needs to be appended)
let path = file.path ?? $path ?? "";
if (!path.endsWith(file.name)) {
path += file.name;
}
const fileInformation: FileInformation = { const fileInformation: FileInformation = {
id: identifier, id: identifier,
path: this.dirTrail + file.name, path: this.dirTrail + path,
parentDirectory: this.dirTrail, parentDirectory: this.dirTrail + parentDirectory,
type: "Leaf", type: "Leaf",
uploading: false, uploading: false,
info: file, info: file,
...@@ -437,19 +486,17 @@ export default defineComponent({ ...@@ -437,19 +486,17 @@ export default defineComponent({
* Handle file drop action for upload. * Handle file drop action for upload.
* @param {DragEvent} ev - Drag event. * @param {DragEvent} ev - Drag event.
*/ */
uploadDrop(ev: DragEvent) { async uploadDrop(ev: DragEvent) {
if (this.fileAddable) { if (this.fileAddable) {
this.dragCounter = 0; this.dragCounter = 0;
// Handling file drops // Handling file drops
if (ev?.dataTransfer?.items) { if (ev?.dataTransfer?.items) {
for (const item of ev.dataTransfer.items) { const files = await FileUtil.getFilesDataTransferItems(
if (item.kind === "file") { ev.dataTransfer.items,
const file = item.getAsFile(); );
if (file !== null) { for (const file of files) {
this.fileListUploadSelected(file); this.fileListUploadSelected(file);
}
}
} }
} }
} }
......
...@@ -4,6 +4,17 @@ import type { ...@@ -4,6 +4,17 @@ import type {
QuotaUnit, QuotaUnit,
} from "@coscine/api-client/dist/types/Coscine.Api"; } from "@coscine/api-client/dist/types/Coscine.Api";
export interface ExtendedFile extends File {
/**
* From Bootstrap-Vue (is the full path)
*/
$path?: string;
/**
* Custom (is the folder path)
*/
path?: string;
}
export class FileUtil { export class FileUtil {
public static formatBytes(bytes: number, decimals = 2) { public static formatBytes(bytes: number, decimals = 2) {
if (bytes === 0) { if (bytes === 0) {
...@@ -42,6 +53,52 @@ export class FileUtil { ...@@ -42,6 +53,52 @@ export class FileUtil {
return input.value * Math.pow(k, input_exponent - output_exponent); return input.value * Math.pow(k, input_exponent - output_exponent);
} }
public static getFilesDataTransferItems(
dataTransferItems: DataTransferItemList,
): Promise<ExtendedFile[]> {
function traverseFileTreePromise(
item: FileSystemEntry | null,
path = "",
folder: ExtendedFile[],
) {
return new Promise((resolve) => {
if (item?.isFile) {
(item as FileSystemFileEntry).file((file) => {
(file as ExtendedFile).path = path || "" + file.name; //save full path
folder.push(file as ExtendedFile);
resolve(file);
});
} else if (item?.isDirectory) {
const dirReader = (item as FileSystemDirectoryEntry).createReader();
dirReader.readEntries((entries) => {
const entriesPromises = [];
for (const entr of entries)
entriesPromises.push(
traverseFileTreePromise(
entr,
path || "" + item.name + "/",
folder,
),
);
resolve(Promise.all(entriesPromises));
});
}
});
}
const files: ExtendedFile[] = [];
return new Promise((resolve, _) => {
const entriesPromises = [];
for (const it of dataTransferItems)
entriesPromises.push(
traverseFileTreePromise(it.webkitGetAsEntry(), "", files),
);
Promise.all(entriesPromises).then((_) => {
resolve(files);
});
});
}
} }
/**@deprecated Unfortunately that is a workaround, since we can't directly use the QuotaUnit Enum */ /**@deprecated Unfortunately that is a workaround, since we can't directly use the QuotaUnit Enum */
......