From af742df3d23120be003fe71e9d90eb6a503357ac Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Wed, 20 Nov 2024 10:23:14 +0100 Subject: [PATCH 1/3] refactor: move folder loaders to web-pkg Moves the folder loaders from the files app to `web-pkg` so they can be used from other packages. --- .../useResourcesViewDefaults.ts | 4 ++-- .../src/views/spaces/GenericSpace.vue | 4 ++-- .../src/services/folder/folderService.ts} | 8 +++----- packages/web-pkg/src/services/folder/index.ts | 2 ++ .../src/services/folder/loaders}/index.ts | 0 .../services/folder/loaders}/loaderFavorites.ts | 4 ++-- .../folder/loaders}/loaderSharedViaLink.ts | 4 ++-- .../services/folder/loaders}/loaderSharedWithMe.ts | 4 ++-- .../folder/loaders}/loaderSharedWithOthers.ts | 4 ++-- .../src/services/folder/loaders}/loaderSpace.ts | 14 ++++---------- .../src/services/folder/loaders}/loaderTrashbin.ts | 4 ++-- .../src/services/folder/loaders}/types.ts | 0 packages/web-pkg/src/services/index.ts | 1 + 13 files changed, 24 insertions(+), 29 deletions(-) rename packages/{web-app-files/src/services/folder.ts => web-pkg/src/services/folder/folderService.ts} (94%) create mode 100644 packages/web-pkg/src/services/folder/index.ts rename packages/{web-app-files/src/services/folder => web-pkg/src/services/folder/loaders}/index.ts (100%) rename packages/{web-app-files/src/services/folder => web-pkg/src/services/folder/loaders}/loaderFavorites.ts (93%) rename packages/{web-app-files/src/services/folder => web-pkg/src/services/folder/loaders}/loaderSharedViaLink.ts (95%) rename packages/{web-app-files/src/services/folder => web-pkg/src/services/folder/loaders}/loaderSharedWithMe.ts (95%) rename packages/{web-app-files/src/services/folder => web-pkg/src/services/folder/loaders}/loaderSharedWithOthers.ts (95%) rename packages/{web-app-files/src/services/folder => web-pkg/src/services/folder/loaders}/loaderSpace.ts (96%) rename packages/{web-app-files/src/services/folder => web-pkg/src/services/folder/loaders}/loaderTrashbin.ts (93%) rename packages/{web-app-files/src/services/folder => web-pkg/src/services/folder/loaders}/types.ts (100%) diff --git a/packages/web-app-files/src/composables/resourcesViewDefaults/useResourcesViewDefaults.ts b/packages/web-app-files/src/composables/resourcesViewDefaults/useResourcesViewDefaults.ts index edb9cbd0f69..9985e96bc23 100644 --- a/packages/web-app-files/src/composables/resourcesViewDefaults/useResourcesViewDefaults.ts +++ b/packages/web-app-files/src/composables/resourcesViewDefaults/useResourcesViewDefaults.ts @@ -1,5 +1,4 @@ import { nextTick, computed, unref, Ref } from 'vue' -import { folderService } from '../../services/folder' import { fileList } from '../../helpers/ui' import { usePagination, @@ -7,7 +6,8 @@ import { SortDir, SortField, useRouteName, - useResourcesStore + useResourcesStore, + folderService } from '@ownclouders/web-pkg' import { useSideBar } from '@ownclouders/web-pkg' import { queryItemAsString, useRouteQuery } from '@ownclouders/web-pkg' diff --git a/packages/web-app-files/src/views/spaces/GenericSpace.vue b/packages/web-app-files/src/views/spaces/GenericSpace.vue index 7c642d20cff..ccef01732ab 100644 --- a/packages/web-app-files/src/views/spaces/GenericSpace.vue +++ b/packages/web-app-files/src/views/spaces/GenericSpace.vue @@ -161,7 +161,8 @@ import { useOpenWithDefaultApp, useKeyboardActions, useRoute, - useRouteQuery + useRouteQuery, + FolderLoaderOptions } from '@ownclouders/web-pkg' import CreateAndUpload from '../../components/AppBar/CreateAndUpload.vue' import FilesViewWrapper from '../../components/FilesViewWrapper.vue' @@ -174,7 +175,6 @@ import SpaceHeader from '../../components/Spaces/SpaceHeader.vue' import WhitespaceContextMenu from '../../components/Spaces/WhitespaceContextMenu.vue' import { eventBus } from '@ownclouders/web-pkg' import { useResourcesViewDefaults } from '../../composables' -import { FolderLoaderOptions } from '../../services/folder' import { BreadcrumbItem } from '@ownclouders/design-system/helpers' import { v4 as uuidV4 } from 'uuid' import { diff --git a/packages/web-app-files/src/services/folder.ts b/packages/web-pkg/src/services/folder/folderService.ts similarity index 94% rename from packages/web-app-files/src/services/folder.ts rename to packages/web-pkg/src/services/folder/folderService.ts index 600f806eaf5..9b619f04c1d 100644 --- a/packages/web-app-files/src/services/folder.ts +++ b/packages/web-pkg/src/services/folder/folderService.ts @@ -17,9 +17,9 @@ import { useSharesStore, useAuthService, AuthServiceInterface -} from '@ownclouders/web-pkg' +} from '../../composables' import { unref } from 'vue' -import { ClientService } from '@ownclouders/web-pkg' +import { ClientService } from '../../services' import { FolderLoaderSpace, @@ -28,9 +28,7 @@ import { FolderLoaderSharedWithMe, FolderLoaderSharedWithOthers, FolderLoaderTrashbin -} from './folder/index' - -export * from './folder/types' +} from './loaders' export type FolderLoaderTask = any diff --git a/packages/web-pkg/src/services/folder/index.ts b/packages/web-pkg/src/services/folder/index.ts new file mode 100644 index 00000000000..47e467c1ab9 --- /dev/null +++ b/packages/web-pkg/src/services/folder/index.ts @@ -0,0 +1,2 @@ +export * from './folderService' +export * from './loaders' diff --git a/packages/web-app-files/src/services/folder/index.ts b/packages/web-pkg/src/services/folder/loaders/index.ts similarity index 100% rename from packages/web-app-files/src/services/folder/index.ts rename to packages/web-pkg/src/services/folder/loaders/index.ts diff --git a/packages/web-app-files/src/services/folder/loaderFavorites.ts b/packages/web-pkg/src/services/folder/loaders/loaderFavorites.ts similarity index 93% rename from packages/web-app-files/src/services/folder/loaderFavorites.ts rename to packages/web-pkg/src/services/folder/loaders/loaderFavorites.ts index 478283d42c8..953be1398cb 100644 --- a/packages/web-app-files/src/services/folder/loaderFavorites.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderFavorites.ts @@ -1,8 +1,8 @@ -import { FolderLoader, FolderLoaderTask, TaskContext } from '../folder' +import { FolderLoader, FolderLoaderTask, TaskContext } from '../folderService' import { Router } from 'vue-router' import { useTask } from 'vue-concurrency' import { buildResource } from '@ownclouders/web-client' -import { isLocationCommonActive } from '@ownclouders/web-pkg' +import { isLocationCommonActive } from '../../../router' export class FolderLoaderFavorites implements FolderLoader { public isEnabled(): boolean { diff --git a/packages/web-app-files/src/services/folder/loaderSharedViaLink.ts b/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts similarity index 95% rename from packages/web-app-files/src/services/folder/loaderSharedViaLink.ts rename to packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts index d23626e335c..5b9a446a1d0 100644 --- a/packages/web-app-files/src/services/folder/loaderSharedViaLink.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts @@ -1,7 +1,7 @@ -import { FolderLoader, FolderLoaderTask, TaskContext } from '../folder' +import { FolderLoader, FolderLoaderTask, TaskContext } from '../folderService' import { Router } from 'vue-router' import { useTask } from 'vue-concurrency' -import { isLocationSharesActive } from '@ownclouders/web-pkg' +import { isLocationSharesActive } from '../../../router' import { buildOutgoingShareResource, call } from '@ownclouders/web-client' export class FolderLoaderSharedViaLink implements FolderLoader { diff --git a/packages/web-app-files/src/services/folder/loaderSharedWithMe.ts b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts similarity index 95% rename from packages/web-app-files/src/services/folder/loaderSharedWithMe.ts rename to packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts index ab506a05c02..f4b9b8b5e66 100644 --- a/packages/web-app-files/src/services/folder/loaderSharedWithMe.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts @@ -1,8 +1,8 @@ -import { FolderLoader, FolderLoaderTask, TaskContext } from '../folder' +import { FolderLoader, FolderLoaderTask, TaskContext } from '../folderService' import { Router } from 'vue-router' import { useTask } from 'vue-concurrency' import { buildIncomingShareResource, call } from '@ownclouders/web-client' -import { isLocationSharesActive } from '@ownclouders/web-pkg' +import { isLocationSharesActive } from '../../../router' export class FolderLoaderSharedWithMe implements FolderLoader { public isEnabled(): boolean { diff --git a/packages/web-app-files/src/services/folder/loaderSharedWithOthers.ts b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts similarity index 95% rename from packages/web-app-files/src/services/folder/loaderSharedWithOthers.ts rename to packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts index 7dd4ab82a83..396c6b98928 100644 --- a/packages/web-app-files/src/services/folder/loaderSharedWithOthers.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts @@ -1,7 +1,7 @@ -import { FolderLoader, FolderLoaderTask, TaskContext } from '../folder' +import { FolderLoader, FolderLoaderTask, TaskContext } from '../folderService' import { Router } from 'vue-router' import { useTask } from 'vue-concurrency' -import { isLocationSharesActive } from '@ownclouders/web-pkg' +import { isLocationSharesActive } from '../../../router' import { buildOutgoingShareResource, call } from '@ownclouders/web-client' export class FolderLoaderSharedWithOthers implements FolderLoader { diff --git a/packages/web-app-files/src/services/folder/loaderSpace.ts b/packages/web-pkg/src/services/folder/loaders/loaderSpace.ts similarity index 96% rename from packages/web-app-files/src/services/folder/loaderSpace.ts rename to packages/web-pkg/src/services/folder/loaders/loaderSpace.ts index a1a7bc6a0fc..9a6bea8fdc7 100644 --- a/packages/web-app-files/src/services/folder/loaderSpace.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSpace.ts @@ -1,14 +1,7 @@ -import { FolderLoader, FolderLoaderTask, TaskContext } from '../folder' +import { FolderLoader, FolderLoaderTask, TaskContext } from '../folderService' import { Router } from 'vue-router' import { useTask } from 'vue-concurrency' import isEmpty from 'lodash-es/isEmpty' -import { - isLocationPublicActive, - isLocationSpacesActive, - SharesStore, - SpacesStore, - UserStore -} from '@ownclouders/web-pkg' import { buildIncomingShareResource, call, @@ -20,10 +13,11 @@ import { } from '@ownclouders/web-client' import { unref } from 'vue' import { FolderLoaderOptions } from './types' -import { useFileRouteReplace } from '@ownclouders/web-pkg' -import { getIndicators } from '@ownclouders/web-pkg' import { Graph } from '@ownclouders/web-client/graph' import { DriveItem } from '@ownclouders/web-client/graph/generated' +import { isLocationSpacesActive, isLocationPublicActive } from '../../../router' +import { SharesStore, SpacesStore, useFileRouteReplace, UserStore } from '../../../composables' +import { getIndicators } from '../../../helpers' export class FolderLoaderSpace implements FolderLoader { public isEnabled(): boolean { diff --git a/packages/web-app-files/src/services/folder/loaderTrashbin.ts b/packages/web-pkg/src/services/folder/loaders/loaderTrashbin.ts similarity index 93% rename from packages/web-app-files/src/services/folder/loaderTrashbin.ts rename to packages/web-pkg/src/services/folder/loaders/loaderTrashbin.ts index 9fceb5d5134..dc52492e060 100644 --- a/packages/web-app-files/src/services/folder/loaderTrashbin.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderTrashbin.ts @@ -1,8 +1,8 @@ -import { FolderLoader, FolderLoaderTask, TaskContext } from '../folder' +import { FolderLoader, FolderLoaderTask, TaskContext } from '../folderService' import { Router } from 'vue-router' import { useTask } from 'vue-concurrency' import { DavProperties } from '@ownclouders/web-client/webdav' -import { isLocationTrashActive } from '@ownclouders/web-pkg' +import { isLocationTrashActive } from '../../../router' import { SpaceResource } from '@ownclouders/web-client' export class FolderLoaderTrashbin implements FolderLoader { diff --git a/packages/web-app-files/src/services/folder/types.ts b/packages/web-pkg/src/services/folder/loaders/types.ts similarity index 100% rename from packages/web-app-files/src/services/folder/types.ts rename to packages/web-pkg/src/services/folder/loaders/types.ts diff --git a/packages/web-pkg/src/services/index.ts b/packages/web-pkg/src/services/index.ts index 519fe9edb4f..6b2a45388dd 100644 --- a/packages/web-pkg/src/services/index.ts +++ b/packages/web-pkg/src/services/index.ts @@ -3,6 +3,7 @@ export * from './archiver' export * from './cache' export * from './client' export * from './eventBus' +export * from './folder' export * from './loadingService' export * from './preview' export * from './passwordPolicy' From 0872c2844d66033bd64943db8ea13ffce7afeede Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Thu, 28 Nov 2024 11:34:41 +0100 Subject: [PATCH 2/3] refactor: move build search term method to useSearch composable Moves the method to build the search term to the `useSearch` composable so it can be used in other packages. --- .../src/components/Search/List.vue | 60 +++++++---------- .../src/composables/search/useSearch.ts | 66 ++++++++++++++++++- 2 files changed, 87 insertions(+), 39 deletions(-) diff --git a/packages/web-app-files/src/components/Search/List.vue b/packages/web-app-files/src/components/Search/List.vue index c72afe95c74..3e8069b8480 100644 --- a/packages/web-app-files/src/components/Search/List.vue +++ b/packages/web-app-files/src/components/Search/List.vue @@ -153,7 +153,8 @@ import { SearchResult, useCapabilityStore, useConfigStore, - useResourcesStore + useResourcesStore, + useSearch } from '@ownclouders/web-pkg' import { NoContentMessage } from '@ownclouders/web-pkg' import { ResourceTable } from '@ownclouders/web-pkg' @@ -247,6 +248,7 @@ export default defineComponent({ const { y: fileListHeaderY } = useFileListHeaderPosition() const clientService = useClientService() const { getMatchingSpace } = useGetMatchingSpace() + const { buildSearchTerm } = useSearch() const resourcesStore = useResourcesStore() const { initResourceList, clearResourceList, setAncestorMetaData } = resourcesStore @@ -342,28 +344,21 @@ export default defineComponent({ return { type: 'file', extension: item.icon, isFolder: item.icon == 'folder' } as Resource } - const buildSearchTerm = (manuallyUpdateFilterChip = false) => { - const query: string[] = [] - - const humanSearchTerm = unref(searchTerm) + const doSearch = (manuallyUpdateFilterChip = false) => { const isTitleOnlySearch = queryItemAsString(unref(titleOnlyParam)) == 'true' - const useFullTextSearch = unref(fullTextSearchEnabled) && !isTitleOnlySearch - - if (!!humanSearchTerm) { - let nameQuery = `name:"*${humanSearchTerm}*"` - - if (useFullTextSearch) { - nameQuery = `(name:"*${humanSearchTerm}*" OR content:"${humanSearchTerm}")` - } - - query.push(nameQuery) - } - - const humanScopeQuery = unref(scopeQuery) - const isScopedSearch = unref(doUseScope) === 'true' - if (isScopedSearch && humanScopeQuery) { - query.push(`scope:${humanScopeQuery}`) - } + const tags = queryItemAsString(unref(tagParam)) + const lastModified = queryItemAsString(unref(lastModifiedParam)) + const mediaType = queryItemAsString(unref(mediaTypeParam)) + + const query = buildSearchTerm({ + term: unref(searchTerm), + isTitleOnlySearch, + tags, + lastModified, + mediaType, + scope: queryItemAsString(unref(scopeQuery)), + useScope: unref(doUseScope) === 'true' + }) const updateFilter = (v: Ref>) => { if (manuallyUpdateFilterChip && unref(v)) { @@ -377,28 +372,19 @@ export default defineComponent({ } } - const humanTagsParams = queryItemAsString(unref(tagParam)) - if (humanTagsParams) { - const tags = humanTagsParams.split('+').map((t) => `"${t}"`) - query.push(`tag:(${tags.join(' OR ')})`) + if (tags) { updateFilter(tagFilter) } - const lastModifiedParams = queryItemAsString(unref(lastModifiedParam)) - if (lastModifiedParams) { - query.push(`mtime:${lastModifiedParams}`) + if (lastModified) { updateFilter(lastModifiedFilter) } - const mediaTypeParams = queryItemAsString(unref(mediaTypeParam)) - if (mediaTypeParams) { - const mediatypes = mediaTypeParams.split('+').map((t) => `"${t}"`) - query.push(`mediatype:(${mediatypes.join(' OR ')})`) + if (mediaType) { updateFilter(mediaTypeFilter) } + return query - .sort((a, b) => Number(a.startsWith('scope:')) - Number(b.startsWith('scope:'))) - .join(' AND ') } const breadcrumbs = computed(() => { @@ -429,7 +415,7 @@ export default defineComponent({ if (capabilityStore.filesTags) { await loadAvailableTagsTask.perform() } - emit('search', buildSearchTerm()) + emit('search', doSearch()) }) watch( @@ -458,7 +444,7 @@ export default defineComponent({ } } - emit('search', buildSearchTerm(true)) + emit('search', doSearch(true)) }, { deep: true } ) diff --git a/packages/web-pkg/src/composables/search/useSearch.ts b/packages/web-pkg/src/composables/search/useSearch.ts index 6ce5ca4cc9a..dcf2ee0b6d2 100644 --- a/packages/web-pkg/src/composables/search/useSearch.ts +++ b/packages/web-pkg/src/composables/search/useSearch.ts @@ -4,7 +4,12 @@ import { DavProperties } from '@ownclouders/web-client/webdav' import { call, urlJoin } from '@ownclouders/web-client' import { useClientService } from '../clientService' import { isProjectSpaceResource } from '@ownclouders/web-client' -import { useConfigStore, useResourcesStore, useSpacesStore } from '../piniaStores' +import { + useCapabilityStore, + useConfigStore, + useResourcesStore, + useSpacesStore +} from '../piniaStores' import { SearchResource } from '@ownclouders/web-client' import { useTask } from 'vue-concurrency' @@ -13,7 +18,9 @@ export const useSearch = () => { const clientService = useClientService() const spacesStore = useSpacesStore() const resourcesStore = useResourcesStore() + const capabilityStore = useCapabilityStore() + const fullTextSearchEnabled = computed(() => capabilityStore.searchContent?.enabled) const areHiddenFilesShown = computed(() => resourcesStore.areHiddenFilesShown) const projectSpaces = computed(() => spacesStore.spaces.filter(isProjectSpaceResource)) const getProjectSpace = (id: string) => { @@ -64,8 +71,63 @@ export const useSearch = () => { return await searchTask.perform(term, searchLimit) } + const buildSearchTerm = ({ + term, + isTitleOnlySearch, + tags, + lastModified, + mediaType, + scope, + useScope + }: { + term: string + isTitleOnlySearch?: boolean + tags?: string + lastModified?: string + mediaType?: string + scope?: string + useScope?: boolean + }) => { + const query: string[] = [] + + const humanSearchTerm = term + const useFullTextSearch = unref(fullTextSearchEnabled) && !isTitleOnlySearch + + if (!!humanSearchTerm) { + let nameQuery = `name:"*${humanSearchTerm}*"` + + if (useFullTextSearch) { + nameQuery = `(name:"*${humanSearchTerm}*" OR content:"${humanSearchTerm}")` + } + + query.push(nameQuery) + } + + if (useScope && scope) { + query.push(`scope:${scope}`) + } + + if (tags) { + const tagArr = tags.split('+').map((t) => `"${t}"`) + query.push(`tag:(${tagArr.join(' OR ')})`) + } + + if (lastModified) { + query.push(`mtime:${lastModified}`) + } + + if (mediaType) { + const mediatypes = mediaType.split('+').map((t) => `"${t}"`) + query.push(`mediatype:(${mediatypes.join(' OR ')})`) + } + return query + .sort((a, b) => Number(a.startsWith('scope:')) - Number(b.startsWith('scope:'))) + .join(' AND ') + } + return { - search + search, + buildSearchTerm } } From d1a248cde90bc6d9450cb4aff29bd802e2169420 Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Thu, 28 Nov 2024 11:35:41 +0100 Subject: [PATCH 3/3] fix: preview app in flat file lists Fixes the image loading of the preview app in flat file lists (Shared with me page, search results etc.) so all images of the current file list are being loaded. Before, the preview app loaded the images of the folder the files are located in. --- .../bugfix-preview-app-flat-file-lists | 6 +++ packages/web-app-preview/src/App.vue | 52 +++++++++---------- .../web-app-preview/tests/unit/app.spec.ts | 28 +++++++--- .../web-app-search/src/portals/SearchBar.vue | 1 + .../src/helpers/resource/functions.ts | 12 ++++- .../src/components/Search/ResourcePreview.vue | 21 +++++++- .../appDefaults/useAppFolderHandling.ts | 45 +++++++++++----- .../folder/loaders/loaderFavorites.ts | 7 ++- .../folder/loaders/loaderSharedViaLink.ts | 7 ++- .../folder/loaders/loaderSharedWithMe.ts | 7 ++- .../folder/loaders/loaderSharedWithOthers.ts | 7 ++- 11 files changed, 137 insertions(+), 56 deletions(-) create mode 100644 changelog/unreleased/bugfix-preview-app-flat-file-lists diff --git a/changelog/unreleased/bugfix-preview-app-flat-file-lists b/changelog/unreleased/bugfix-preview-app-flat-file-lists new file mode 100644 index 00000000000..8c352a095be --- /dev/null +++ b/changelog/unreleased/bugfix-preview-app-flat-file-lists @@ -0,0 +1,6 @@ +Bugfix: Preview app flat file lists + +We've fixed the image loading of the preview app in flat file lists ("Shared with me", "Shared with others", "Shared via link", favorites, search result page) so all images of the current file list are being loaded. + +https://github.com/owncloud/web/pull/11960 +https://github.com/owncloud/web/issues/8932 diff --git a/packages/web-app-preview/src/App.vue b/packages/web-app-preview/src/App.vue index 0bef76d939a..cb10f1eab9f 100644 --- a/packages/web-app-preview/src/App.vue +++ b/packages/web-app-preview/src/App.vue @@ -80,8 +80,7 @@ import { watch, Ref } from 'vue' -import { RouteLocationRaw } from 'vue-router' -import { isShareSpaceResource, Resource } from '@ownclouders/web-client' +import { Resource } from '@ownclouders/web-client' import { AppFileHandlingResult, AppFolderHandlingResult, @@ -94,7 +93,9 @@ import { useRoute, useRouteQuery, useRouter, - usePreviewService + usePreviewService, + useGetMatchingSpace, + isLocationSharesActive } from '@ownclouders/web-pkg' import MediaControls from './components/MediaControls.vue' import MediaAudio from './components/Sources/MediaAudio.vue' @@ -145,6 +146,7 @@ export default defineComponent({ const { isFileTypeAudio, isFileTypeImage, isFileTypeVideo } = useFileTypes() const previewService = usePreviewService() const { dimensions } = usePreviewDimensions() + const { getMatchingSpace } = useGetMatchingSpace() const activeIndex = ref() const cachedFiles = ref>({}) @@ -152,6 +154,10 @@ export default defineComponent({ const isAutoPlayEnabled = ref(true) const preview = ref() + const space = computed(() => { + return getMatchingSpace(unref(activeFilteredFile)) + }) + const sortBy = computed(() => { if (!unref(contextRouteQuery)) { return 'name' @@ -209,7 +215,7 @@ export default defineComponent({ if (cachedFile.isImage) { cachedFile.url = await previewService.loadPreview( { - space: unref(props.currentFileContext.space), + space: unref(space), resource: file, dimensions: unref(dimensions), processor: ProcessorType.enum.fit @@ -219,7 +225,7 @@ export default defineComponent({ ) return } - cachedFile.url = await props.getUrlForResource(unref(props.currentFileContext.space), file) + cachedFile.url = await props.getUrlForResource(unref(space), file) } catch (e) { console.error(e) cachedFile.isError.value = true @@ -235,10 +241,7 @@ export default defineComponent({ return } - const { params, query } = createFileRouteOptions( - unref(props.currentFileContext.space), - unref(activeFilteredFile) - ) + const { params, query } = createFileRouteOptions(unref(space), unref(activeFilteredFile)) router.replace({ ...unref(route), params: { ...unref(route).params, ...params }, @@ -259,7 +262,7 @@ export default defineComponent({ folderLoaded.value = true } - ;(instance.proxy as any).setActiveFile(unref(props.currentFileContext.driveAliasAndItem)) + ;(instance.proxy as any).setActiveFile() }, { immediate: true } ) @@ -302,7 +305,8 @@ export default defineComponent({ isAutoPlayEnabled, preview, isFileTypeImage, - loadFileIntoCache + loadFileIntoCache, + space } }, @@ -336,32 +340,24 @@ export default defineComponent({ }, methods: { - setActiveFile(driveAliasAndItem: string) { + setActiveFile() { for (let i = 0; i < this.filteredFiles.length; i++) { - if (isShareSpaceResource(unref(this.currentFileContext.space))) { - // with share space resources, we don't have an underlying space, so match the file id - if (this.filteredFiles[i].remoteItemId === this.fileId) { - this.activeIndex = i - return - } - - this.activeIndex = 0 - continue - } + const filterAttr = isLocationSharesActive(this.$router, 'files-shares-with-me') + ? 'remoteItemId' + : 'fileId' - if ( - unref(this.currentFileContext.space)?.getDriveAliasAndItem(this.filteredFiles[i]) === - driveAliasAndItem - ) { + // match the given file id with the filtered files to get the current index + if (this.filteredFiles[i][filterAttr] === this.fileId) { this.activeIndex = i return } + + this.activeIndex = 0 } }, // react to PopStateEvent () handleLocalHistoryEvent() { - const result = this.$router.resolve(document.location as unknown as RouteLocationRaw) - this.setActiveFile(queryItemAsString(result.params.driveAliasAndItem)) + this.setActiveFile() }, goToNext() { if (this.activeIndex + 1 >= this.filteredFiles.length) { diff --git a/packages/web-app-preview/tests/unit/app.spec.ts b/packages/web-app-preview/tests/unit/app.spec.ts index bf52dee202a..78809623a6f 100644 --- a/packages/web-app-preview/tests/unit/app.spec.ts +++ b/packages/web-app-preview/tests/unit/app.spec.ts @@ -1,12 +1,19 @@ import App from '../../src/App.vue' import { nextTick } from 'vue' import { defaultComponentMocks, defaultPlugins, shallowMount } from '@ownclouders/web-test-helpers' -import { FileContext } from '@ownclouders/web-pkg' +import { FileContext, queryItemAsString } from '@ownclouders/web-pkg' import { mock } from 'vitest-mock-extended' +vi.mock('@ownclouders/web-pkg', async (importOriginal) => ({ + ...(await importOriginal()), + queryItemAsString: vi.fn(), + createFileRouteOptions: vi.fn(() => ({ params: {}, query: {} })) +})) + const activeFiles = [ { id: '1', + fileId: '1', name: 'bear.png', mimeType: 'image/png', path: 'personal/admin/bear.png', @@ -14,6 +21,7 @@ const activeFiles = [ }, { id: '2', + fileId: '2', name: 'elephant.png', mimeType: 'image/png', path: 'personal/admin/elephant.png', @@ -21,6 +29,7 @@ const activeFiles = [ }, { id: '3', + fileId: '3', name: 'wale_sounds.flac', mimeType: 'audio/flac', path: 'personal/admin/wale_sounds.flac', @@ -28,6 +37,7 @@ const activeFiles = [ }, { id: '4', + fileId: '4', name: 'lonely_sloth_very_sad.gif', mimeType: 'image/gif', path: 'personal/admin/lonely_sloth_very_sad.gif', @@ -35,6 +45,7 @@ const activeFiles = [ }, { id: '5', + fileId: '5', name: 'tiger_eats_plants.mp4', mimeType: 'video/mp4', path: 'personal/admin/tiger_eats_plants.mp4', @@ -42,6 +53,7 @@ const activeFiles = [ }, { id: '6', + fileId: '6', name: 'happy_hippo.gif', mimeType: 'image/gif', path: 'personal/admin/happy_hippo.gif', @@ -49,6 +61,7 @@ const activeFiles = [ }, { id: '7', + fileId: '7', name: 'sleeping_dog.gif', mimeType: 'image/gif', path: 'personal/admin/sleeping_dog.gif', @@ -56,6 +69,7 @@ const activeFiles = [ }, { id: '8', + fileId: '8', name: 'cat_murr_murr.gif', mimeType: 'image/gif', path: 'personal/admin/cat_murr_murr.gif', @@ -63,6 +77,7 @@ const activeFiles = [ }, { id: '9', + fileId: '9', name: 'labrador.gif', mimeType: 'image/gif', path: 'personal/admin/labrador.gif', @@ -77,7 +92,7 @@ describe('Preview app', () => { await nextTick() wrapper.vm.cachedFiles = {} - wrapper.vm.setActiveFile('personal/admin/sleeping_dog.gif') + wrapper.vm.goToNext() await nextTick() @@ -94,16 +109,13 @@ describe('Preview app', () => { function createShallowMountWrapper() { const mocks = defaultComponentMocks() mocks.$previewService.loadPreview.mockResolvedValue('') + vi.mocked(queryItemAsString).mockImplementationOnce(() => '1') + return { wrapper: shallowMount(App, { props: { currentFileContext: mock({ - path: 'personal/admin/bear.png', - space: { - getDriveAliasAndItem: vi.fn().mockImplementation((file) => { - return activeFiles.find((filteredFile) => filteredFile.id == file.id)?.path - }) - } + path: 'personal/admin/bear.png' }), activeFiles, isFolderLoading: true, diff --git a/packages/web-app-search/src/portals/SearchBar.vue b/packages/web-app-search/src/portals/SearchBar.vue index 0e2db72d318..4168503a156 100644 --- a/packages/web-app-search/src/portals/SearchBar.vue +++ b/packages/web-app-search/src/portals/SearchBar.vue @@ -90,6 +90,7 @@ class="preview-component" :provider="provider" :search-result="providerSearchResultValue" + :term="term" /> diff --git a/packages/web-client/src/helpers/resource/functions.ts b/packages/web-client/src/helpers/resource/functions.ts index ca8067b4f07..dc2375b5210 100644 --- a/packages/web-client/src/helpers/resource/functions.ts +++ b/packages/web-client/src/helpers/resource/functions.ts @@ -1,7 +1,13 @@ import path, { basename, dirname } from 'path' import { urlJoin } from '../../utils' import { DavPermission, DavProperty } from '../../webdav/constants' -import { Resource, ResourceIndicator, TrashResource, WebDavResponseResource } from './types' +import { + Resource, + ResourceIndicator, + SearchResource, + TrashResource, + WebDavResponseResource +} from './types' import { camelCase } from 'lodash-es' const fileExtensions = { @@ -12,6 +18,10 @@ export const isTrashResource = (resource: Resource): resource is TrashResource = return Object.hasOwn(resource, 'ddate') } +export const isSearchResource = (resource: Resource): resource is SearchResource => { + return Object.hasOwn(resource, 'highlights') +} + export const extractDomSelector = (str: string): string => { return str.replace(/[^A-Za-z0-9\-_]/g, '') } diff --git a/packages/web-pkg/src/components/Search/ResourcePreview.vue b/packages/web-pkg/src/components/Search/ResourcePreview.vue index 84a2f0e1e6a..05e8a8e79c9 100644 --- a/packages/web-pkg/src/components/Search/ResourcePreview.vue +++ b/packages/web-pkg/src/components/Search/ResourcePreview.vue @@ -29,6 +29,7 @@ import { isSpaceResource, Resource } from '@ownclouders/web-client' import ResourceListItem from '../FilesList/ResourceListItem.vue' import { SearchResultValue } from './types' import { storeToRefs } from 'pinia' +import { RouteLocationPathRaw } from 'vue-router' const visibilityObserver = new VisibilityObserver() @@ -44,6 +45,10 @@ export default defineComponent({ isClickable: { type: Boolean, default: true + }, + term: { + type: String, + default: '' } }, setup(props) { @@ -115,7 +120,21 @@ export default defineComponent({ return null } - return action.route({ space: unref(space), resources: [unref(resource)] }) + const route = action.route({ + space: unref(space), + resources: [unref(resource)] + }) as RouteLocationPathRaw + + // add search term to query param + route.query = { + ...route.query, + contextRouteQuery: { + ...((route.query?.contextRouteQuery as any) || {}), + term: props.term + } + } + + return route }) return { diff --git a/packages/web-pkg/src/composables/appDefaults/useAppFolderHandling.ts b/packages/web-pkg/src/composables/appDefaults/useAppFolderHandling.ts index b876ff42528..367e83999fb 100644 --- a/packages/web-pkg/src/composables/appDefaults/useAppFolderHandling.ts +++ b/packages/web-pkg/src/composables/appDefaults/useAppFolderHandling.ts @@ -1,16 +1,18 @@ import { Ref, ref, unref, MaybeRef } from 'vue' import { dirname } from 'path' -import { ClientService } from '../../services' +import { ClientService, folderService } from '../../services' import { useAppFileHandling } from './useAppFileHandling' -import { buildIncomingShareResource, Resource } from '@ownclouders/web-client' +import { isSearchResource, Resource } from '@ownclouders/web-client' import { FileContext } from './types' import { RouteLocationNormalizedLoaded } from 'vue-router' import { useFileRouteReplace } from '../router/useFileRouteReplace' import { DavProperty } from '@ownclouders/web-client/webdav' import { useAuthService } from '../authContext/useAuthService' import { isMountPointSpaceResource } from '@ownclouders/web-client' -import { useResourcesStore, useSharesStore, useSpacesStore } from '../piniaStores' +import { useResourcesStore, useSpacesStore } from '../piniaStores' import { storeToRefs } from 'pinia' +import { useRouteQuery } from '../router' +import { useSearch } from '../search' interface AppFolderHandlingOptions { currentRoute: Ref @@ -29,12 +31,13 @@ export function useAppFolderHandling({ clientService }: AppFolderHandlingOptions): AppFolderHandlingResult { const isFolderLoading = ref(false) - const { webdav, graphAuthenticated } = clientService + const { webdav } = clientService const { replaceInvalidFileRoute } = useFileRouteReplace() const { getFileInfo } = useAppFileHandling({ clientService }) const authService = useAuthService() const spacesStore = useSpacesStore() - const sharesStore = useSharesStore() + const { buildSearchTerm, search } = useSearch() + const currentRouteQuery = useRouteQuery('contextRouteQuery') const resourcesStore = useResourcesStore() const { activeResources } = storeToRefs(resourcesStore) @@ -45,21 +48,35 @@ export function useAppFolderHandling({ try { context = unref(context) - if (context.routeName === 'files-shares-with-me') { - // FIXME: this is a somewhat hacky solution to load the shared with me files. - // ideally we should check if there is a current folder and if not, use the - // folder loader to load files. unfortunately, it currently lives in the files app. - const driveItems = await graphAuthenticated.driveItems.listSharedWithMe() - - const resources = driveItems.map((driveItem) => - buildIncomingShareResource({ driveItem, graphRoles: sharesStore.graphRoles }) - ) + if ((unref(currentRouteQuery) as any)?.term) { + // run search query to load all results + // TODO: add filters from query params + const searchTerm = buildSearchTerm({ term: (unref(currentRouteQuery) as any)?.term }) + const { values } = await search(searchTerm, 200) + const resources = values + .filter(({ data }) => isSearchResource(data as Resource)) + .map((v) => v.data as Resource) resourcesStore.initResourceList({ currentFolder: null, resources }) isFolderLoading.value = false return } + const flatFileLists = [ + 'files-shares-with-me', + 'files-shares-with-others', + 'files-shares-via-link', + 'files-common-favorites' + ] + + if (flatFileLists.includes(unref(context.routeName))) { + // use the folder loader to load the resources for flat file lists + const loaderTask = folderService.getTask() + await loaderTask.perform() + isFolderLoading.value = false + return + } + resourcesStore.clearResourceList() const space = unref(context.space) const pathResource = await getFileInfo(context, { diff --git a/packages/web-pkg/src/services/folder/loaders/loaderFavorites.ts b/packages/web-pkg/src/services/folder/loaders/loaderFavorites.ts index 953be1398cb..e2cf97dff5b 100644 --- a/packages/web-pkg/src/services/folder/loaders/loaderFavorites.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderFavorites.ts @@ -3,6 +3,7 @@ import { Router } from 'vue-router' import { useTask } from 'vue-concurrency' import { buildResource } from '@ownclouders/web-client' import { isLocationCommonActive } from '../../../router' +import { unref } from 'vue' export class FolderLoaderFavorites implements FolderLoader { public isEnabled(): boolean { @@ -10,7 +11,11 @@ export class FolderLoaderFavorites implements FolderLoader { } public isActive(router: Router): boolean { - return isLocationCommonActive(router, 'files-common-favorites') + const currentRoute = unref(router.currentRoute) + return ( + isLocationCommonActive(router, 'files-common-favorites') || + currentRoute?.query?.contextRouteName === 'files-common-favorites' + ) } public getTask(context: TaskContext): FolderLoaderTask { diff --git a/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts b/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts index 5b9a446a1d0..5d1a1d03596 100644 --- a/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts @@ -3,6 +3,7 @@ import { Router } from 'vue-router' import { useTask } from 'vue-concurrency' import { isLocationSharesActive } from '../../../router' import { buildOutgoingShareResource, call } from '@ownclouders/web-client' +import { unref } from 'vue' export class FolderLoaderSharedViaLink implements FolderLoader { public isEnabled(): boolean { @@ -10,7 +11,11 @@ export class FolderLoaderSharedViaLink implements FolderLoader { } public isActive(router: Router): boolean { - return isLocationSharesActive(router, 'files-shares-via-link') + const currentRoute = unref(router.currentRoute) + return ( + isLocationSharesActive(router, 'files-shares-via-link') || + currentRoute?.query?.contextRouteName === 'files-shares-via-link' + ) } public getTask(context: TaskContext): FolderLoaderTask { diff --git a/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts index f4b9b8b5e66..eb0bee7fdef 100644 --- a/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts @@ -3,6 +3,7 @@ import { Router } from 'vue-router' import { useTask } from 'vue-concurrency' import { buildIncomingShareResource, call } from '@ownclouders/web-client' import { isLocationSharesActive } from '../../../router' +import { unref } from 'vue' export class FolderLoaderSharedWithMe implements FolderLoader { public isEnabled(): boolean { @@ -10,7 +11,11 @@ export class FolderLoaderSharedWithMe implements FolderLoader { } public isActive(router: Router): boolean { - return isLocationSharesActive(router, 'files-shares-with-me') + const currentRoute = unref(router.currentRoute) + return ( + isLocationSharesActive(router, 'files-shares-with-me') || + currentRoute?.query?.contextRouteName === 'files-shares-with-me' + ) } public getTask(context: TaskContext): FolderLoaderTask { diff --git a/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts index 396c6b98928..8ee3f6dc715 100644 --- a/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts @@ -1,4 +1,5 @@ import { FolderLoader, FolderLoaderTask, TaskContext } from '../folderService' +import { unref } from 'vue' import { Router } from 'vue-router' import { useTask } from 'vue-concurrency' import { isLocationSharesActive } from '../../../router' @@ -10,7 +11,11 @@ export class FolderLoaderSharedWithOthers implements FolderLoader { } public isActive(router: Router): boolean { - return isLocationSharesActive(router, 'files-shares-with-others') + const currentRoute = unref(router.currentRoute) + return ( + isLocationSharesActive(router, 'files-shares-with-others') || + currentRoute?.query?.contextRouteName === 'files-shares-with-others' + ) } public getTask(context: TaskContext): FolderLoaderTask {