diff --git a/packages/design-system/src/components/OcTable/OcTable.vue b/packages/design-system/src/components/OcTable/OcTable.vue index e9063484e4..06f2da00ee 100644 --- a/packages/design-system/src/components/OcTable/OcTable.vue +++ b/packages/design-system/src/components/OcTable/OcTable.vue @@ -550,7 +550,7 @@ const handleSort = (field: FieldType) => { .oc-table-hover tr { @apply transition-colors duration-200 ease-in-out; } - .oc-table-accentuated, + .item-accentuated, .oc-table-highlighted, .oc-table .highlightedDropTarget { @apply bg-role-secondary-container; diff --git a/packages/web-app-files/src/HandleUpload.ts b/packages/web-app-files/src/HandleUpload.ts index 7642098df7..d497dfeeca 100644 --- a/packages/web-app-files/src/HandleUpload.ts +++ b/packages/web-app-files/src/HandleUpload.ts @@ -363,11 +363,6 @@ export class HandleUpload extends BasePlugin ...uppyFile, meta: { ...uppyFile.meta, fileId: folder?.fileId } }) - - if (isRoot && this.resourcesStore.currentFolder?.id === uploadFolder.id) { - // update file list for top level folders when the current folder is the upload target folder - this.resourcesStore.upsertResource(folder) - } } catch (error) { if (error.statusCode !== 405) { console.error(error) diff --git a/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue b/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue index 72c8016500..70c5ac0602 100644 --- a/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue +++ b/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue @@ -200,13 +200,7 @@ import { import ResourceUpload from './Upload/ResourceUpload.vue' import { computed, onMounted, onBeforeUnmount, unref, watch, ref } from 'vue' -import { eventBus } from '@opencloud-eu/web-pkg' -import { - Resource, - SpaceResource, - isPublicSpaceResource, - isShareSpaceResource -} from '@opencloud-eu/web-client' +import { Resource, SpaceResource, isPublicSpaceResource } from '@opencloud-eu/web-client' import { useService, useUpload, UppyService, UploadResult } from '@opencloud-eu/web-pkg' import { HandleUpload } from '../../HandleUpload' import { useGettext } from 'vue3-gettext' @@ -216,15 +210,10 @@ import { v4 as uuidV4 } from 'uuid' import { storeToRefs } from 'pinia' import { uploadMenuExtensionPoint } from '../../extensionPoints' -const { - space, - item, - itemId, - limitedScreenSpace = false -} = defineProps<{ +const { space, limitedScreenSpace = false } = defineProps<{ space: SpaceResource item?: string - itemId?: string | number + itemId?: string limitedScreenSpace?: boolean }>() @@ -334,39 +323,37 @@ useEventListener(document, 'paste', (event: ClipboardEvent) => { }) const onUploadComplete = async (result: UploadResult) => { - if (result.successful) { - const file = result.successful[0] - - if (!file) { - return - } + const file = result.successful?.[0] + if (!file) { + return + } - const { spaceId, currentFolder, currentFolderId, driveType } = file.meta - if (!isPublicSpaceResource(unref(computedSpace))) { - const isOwnSpace = spacesStore.spaces - .find(({ id }) => id === spaceId) - ?.isOwner(userStore.user) - - if (driveType === 'project' || isOwnSpace) { - const client = clientService.graphAuthenticated - const updatedSpace = await client.drives.getDrive(spaceId) - spacesStore.updateSpaceField({ - id: updatedSpace.id, - field: 'spaceQuota', - value: updatedSpace.spaceQuota - }) - } + const { spaceId, driveType } = file.meta + if (!isPublicSpaceResource(unref(computedSpace))) { + const isOwnSpace = spacesStore.spaces.find(({ id }) => id === spaceId)?.isOwner(userStore.user) + + if (driveType === 'project' || isOwnSpace) { + const client = clientService.graphAuthenticated + const updatedSpace = await client.drives.getDrive(spaceId) + spacesStore.updateSpaceField({ + id: updatedSpace.id, + field: 'spaceQuota', + value: updatedSpace.spaceQuota + }) } + } - const sameFolder = - itemId && !isShareSpaceResource(unref(computedSpace)) - ? itemId.toString().startsWith(currentFolderId.toString()) - : currentFolder === item - const fileIsInCurrentPath = spaceId === unref(computedSpace).id && sameFolder - if (fileIsInCurrentPath) { - eventBus.publish('app.files.list.load') - } + if (!unref(currentFolder) || spaceId !== unref(computedSpace).id) { + return } + + const { children } = await clientService.webdav.listFiles(unref(computedSpace), { + path: unref(currentFolder).path + }) + + const existingIds = new Set(resourcesStore.resources.map((r) => r.id)) + const newResources = children.filter((child) => !existingIds.has(child.id)) + resourcesStore.upsertResources(newResources) } const isMovingIntoSameFolder = computed(() => { diff --git a/packages/web-app-files/src/composables/resourcesViewDefaults/useResourcesViewDefaults.ts b/packages/web-app-files/src/composables/resourcesViewDefaults/useResourcesViewDefaults.ts index af38bb8cd5..f284b65bff 100644 --- a/packages/web-app-files/src/composables/resourcesViewDefaults/useResourcesViewDefaults.ts +++ b/packages/web-app-files/src/composables/resourcesViewDefaults/useResourcesViewDefaults.ts @@ -119,14 +119,21 @@ export const useResourcesViewDefaults = ({ items, perPageStoragePrefix: 'files' }) - const accentuateItem = async (id: string) => { - await nextTick() - fileList.accentuateItem(id) - } resourcesStore.$onAction((action) => { - if (action.name === 'upsertResource') { - accentuateItem(action.args[0].id) - } + action.after(async () => { + switch (action.name) { + case 'upsertResource': + await nextTick() + fileList.accentuateItem(action.args[0].id) + break + case 'upsertResources': + await nextTick() + for (const resource of action.args[0]) { + fileList.accentuateItem(resource.id) + } + break + } + }) }) return { diff --git a/packages/web-app-files/src/helpers/ui/filesList.ts b/packages/web-app-files/src/helpers/ui/filesList.ts index 87538d24e9..68b12c5ac3 100644 --- a/packages/web-app-files/src/helpers/ui/filesList.ts +++ b/packages/web-app-files/src/helpers/ui/filesList.ts @@ -5,8 +5,8 @@ export const accentuateItem = (id: string, clearTimeout = 3500): void => { return } - item.classList.add('oc-table-accentuated') + item.classList.add('item-accentuated') setTimeout(() => { - item.classList.remove('oc-table-accentuated') + item.classList.remove('item-accentuated') }, clearTimeout) } diff --git a/packages/web-app-files/src/views/spaces/GenericSpace.vue b/packages/web-app-files/src/views/spaces/GenericSpace.vue index c7331090b3..85bdaec4ed 100644 --- a/packages/web-app-files/src/views/spaces/GenericSpace.vue +++ b/packages/web-app-files/src/views/spaces/GenericSpace.vue @@ -217,7 +217,7 @@ export default defineComponent({ default: null }, itemId: { - type: [String, Number], + type: String, required: false, default: null } diff --git a/packages/web-app-files/tests/unit/components/AppBar/CreateAndUpload.spec.ts b/packages/web-app-files/tests/unit/components/AppBar/CreateAndUpload.spec.ts index e217a4dc7e..2a8452b5d8 100644 --- a/packages/web-app-files/tests/unit/components/AppBar/CreateAndUpload.spec.ts +++ b/packages/web-app-files/tests/unit/components/AppBar/CreateAndUpload.spec.ts @@ -11,13 +11,14 @@ import { useFileActionsPaste, useExtensionRegistry, OcUppyFile, - ClipboardActions + ClipboardActions, + useResourcesStore } from '@opencloud-eu/web-pkg' -import { eventBus } from '@opencloud-eu/web-pkg' import { defaultPlugins, shallowMount, defaultComponentMocks } from '@opencloud-eu/web-test-helpers' import { RouteLocation } from 'vue-router' import { computed, ref, unref } from 'vue' import { OcButton } from '@opencloud-eu/design-system/components' +import { ListFilesResult } from '@opencloud-eu/web-client/webdav' vi.mock('@opencloud-eu/web-pkg', async (importOriginal) => ({ ...(await importOriginal()), @@ -156,7 +157,9 @@ describe('CreateAndUpload component', () => { { driveType: 'share', updated: 0 }, { driveType: 'public', updated: 0 } ])('updates the space quota for supported drive types: %s', async ({ driveType, updated }) => { - const file = mock({ meta: { driveType, spaceId: '1' } }) + const file = mock({ + meta: { driveType, spaceId: '1', relativeFolder: undefined } + }) const spaces = [ mock({ id: file.meta.spaceId, isOwner: () => driveType === 'personal' }) ] @@ -167,18 +170,48 @@ describe('CreateAndUpload component', () => { const spacesStore = useSpacesStore() expect(spacesStore.updateSpaceField).toHaveBeenCalledTimes(updated) }) - it('reloads the file list if files were uploaded to the current path', async () => { - const eventSpy = vi.spyOn(eventBus, 'publish') - const itemId = 'itemId' - const space = mock({ id: '1' }) - const { mocks } = getWrapper({ itemId, space }) - const file = mock({ - meta: { driveType: 'project', spaceId: space.id, currentFolderId: itemId } - }) + + const itemId = 'itemId' + const space = mock({ id: '1' }) + const file = mock({ + meta: { + driveType: 'project', + spaceId: space.id, + currentFolderId: itemId, + relativeFolder: undefined + } + }) + + it('updates the store with the new files', async () => { + const currentFolder = mock({ id: itemId }) + const uploadedFile = mock({ id: '2' }) + const { mocks } = getWrapper({ itemId, space, currentFolder, uploadedFiles: [uploadedFile] }) const graphMock = mocks.$clientService.graphAuthenticated graphMock.drives.getDrive.mockResolvedValue(mock()) - await unref(mocks.onUploadCompleteCallback)({ successful: [file], failed: [] }) - expect(eventSpy).toHaveBeenCalled() + await unref(mocks.onUploadCompleteCallback)({ successful: [file] }) + const resourcesStore = useResourcesStore() + expect(resourcesStore.upsertResources).toHaveBeenCalledWith([uploadedFile]) + }) + it('does not update the store if no current folder given', async () => { + const currentFolder = mock({ id: '2' }) + const { mocks } = getWrapper({ itemId, space, currentFolder }) + const graphMock = mocks.$clientService.graphAuthenticated + graphMock.drives.getDrive.mockResolvedValue(mock()) + const resourcesStore = useResourcesStore() + resourcesStore.currentFolder = null + await unref(mocks.onUploadCompleteCallback)({ successful: [file] }) + expect(resourcesStore.upsertResources).not.toHaveBeenCalled() + }) + it('does not update the store if user navigated into another space after the upload', async () => { + const currentFolder = mock({ id: '2' }) + const { mocks } = getWrapper({ itemId, space, currentFolder }) + const graphMock = mocks.$clientService.graphAuthenticated + graphMock.drives.getDrive.mockResolvedValue(mock()) + const uploadedFile = { ...file } + uploadedFile.meta.spaceId = 'another-space' + await unref(mocks.onUploadCompleteCallback)({ successful: [uploadedFile] }) + const resourcesStore = useResourcesStore() + expect(resourcesStore.upsertResources).not.toHaveBeenCalled() }) }) describe('drop target', () => { @@ -208,7 +241,8 @@ function getWrapper({ mock({ label: () => 'Mark-down file', ext: 'md' }), mock({ label: () => 'Draw.io document', ext: 'drawio' }) ], - clipboardAction = ClipboardActions.Cut + clipboardAction = ClipboardActions.Cut, + uploadedFiles = [] }: { clipboardResources?: Resource[] files?: Resource[] @@ -220,6 +254,7 @@ function getWrapper({ areFileExtensionsShown?: boolean createActions?: FileAction[] clipboardAction?: ClipboardActions + uploadedFiles?: Resource[] } = {}) { const capabilities = { spaces: { enabled: true }, @@ -261,6 +296,9 @@ function getWrapper({ onUploadCompleteCallback.value = callback return null }) + defaultMocks.$clientService.webdav.listFiles.mockResolvedValue( + mock({ children: uploadedFiles }) + ) const mocks = { ...defaultMocks, diff --git a/packages/web-app-files/tests/unit/helpers/ui/filesList.spec.ts b/packages/web-app-files/tests/unit/helpers/ui/filesList.spec.ts index 5f6e7d1fab..fcc73de33f 100644 --- a/packages/web-app-files/tests/unit/helpers/ui/filesList.spec.ts +++ b/packages/web-app-files/tests/unit/helpers/ui/filesList.spec.ts @@ -15,16 +15,16 @@ describe('accentuateItem', () => { const trs = document.getElementsByTagName('tr') accentuateItem('1', 50) - expect(trs[0].classList.contains('oc-table-accentuated')).toBe(true) - expect(trs[1].classList.contains('oc-table-accentuated')).toBe(false) + expect(trs[0].classList.contains('item-accentuated')).toBe(true) + expect(trs[1].classList.contains('item-accentuated')).toBe(false) vi.advanceTimersByTime(100) - expect(trs[0].classList.contains('oc-table-accentuated')).toBe(false) - expect(trs[1].classList.contains('oc-table-accentuated')).toBe(false) + expect(trs[0].classList.contains('item-accentuated')).toBe(false) + expect(trs[1].classList.contains('item-accentuated')).toBe(false) accentuateItem('2', 50) - expect(trs[0].classList.contains('oc-table-accentuated')).toBe(false) - expect(trs[1].classList.contains('oc-table-accentuated')).toBe(true) + expect(trs[0].classList.contains('item-accentuated')).toBe(false) + expect(trs[1].classList.contains('item-accentuated')).toBe(true) // do not fail in setTimeout if element went away trs[1].remove() diff --git a/packages/web-pkg/src/components/FilesList/ResourceTile.vue b/packages/web-pkg/src/components/FilesList/ResourceTile.vue index 56fce31cc8..07929c8400 100644 --- a/packages/web-pkg/src/components/FilesList/ResourceTile.vue +++ b/packages/web-pkg/src/components/FilesList/ResourceTile.vue @@ -2,7 +2,7 @@ +"
@@ -26,7 +26,7 @@ exports[`OcTile component > renders default space correctly 1`] = ` `; exports[`OcTile component > renders disabled space correctly 1`] = ` -" +"
diff --git a/packages/web-pkg/tests/unit/components/FilesList/__snapshots__/ResourceTiles.spec.ts.snap b/packages/web-pkg/tests/unit/components/FilesList/__snapshots__/ResourceTiles.spec.ts.snap index 2d188703d3..8d9f99040e 100644 --- a/packages/web-pkg/tests/unit/components/FilesList/__snapshots__/ResourceTiles.spec.ts.snap +++ b/packages/web-pkg/tests/unit/components/FilesList/__snapshots__/ResourceTiles.spec.ts.snap @@ -18,7 +18,7 @@ exports[`ResourceTiles component > renders an array of spaces correctly 1`] = `
  • -
    +
    @@ -65,7 +65,7 @@ exports[`ResourceTiles component > renders an array of spaces correctly 1`] = `
  • -
    +