diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-02-06 11:50:39 +0100 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-02-11 12:19:05 +0100 |
commit | 8003b270c59a07aae106f215c76a8fd62510896e (patch) | |
tree | 581ed504feed5d03a6b418a259df4473f3ac1115 | |
parent | 53b79b7f1e5da6d55fd1871c950e0fbdae3d686a (diff) | |
download | nextcloud-server-8003b270c59a07aae106f215c76a8fd62510896e.tar.gz nextcloud-server-8003b270c59a07aae106f215c76a8fd62510896e.zip |
feat(sharing): Allow to set default view mode for public shares
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
-rw-r--r-- | apps/files_sharing/src/eventbus.d.ts | 15 | ||||
-rw-r--r-- | apps/files_sharing/src/init-public.ts | 34 | ||||
-rw-r--r-- | apps/files_sharing/src/views/SharingDetailsTab.vue | 73 | ||||
-rw-r--r-- | cypress/e2e/files_sharing/public-share/default-view.cy.ts | 102 |
4 files changed, 205 insertions, 19 deletions
diff --git a/apps/files_sharing/src/eventbus.d.ts b/apps/files_sharing/src/eventbus.d.ts new file mode 100644 index 00000000000..cc10ff8468f --- /dev/null +++ b/apps/files_sharing/src/eventbus.d.ts @@ -0,0 +1,15 @@ +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import type { Folder, Node } from '@nextcloud/files' + +declare module '@nextcloud/event-bus' { + export interface NextcloudEvents { + // mapping of 'event name' => 'event type' + 'files:list:updated': { folder: Folder, contents: Node[] } + 'files:config:updated': { key: string, value: boolean } + } +} + +export {} diff --git a/apps/files_sharing/src/init-public.ts b/apps/files_sharing/src/init-public.ts index 79aab58bd1d..72a3098a0e6 100644 --- a/apps/files_sharing/src/init-public.ts +++ b/apps/files_sharing/src/init-public.ts @@ -2,13 +2,16 @@ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { getNavigation } from '@nextcloud/files' +import type { ShareAttribute } from './sharing.d.ts' +import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus' +import { Folder, getNavigation } from '@nextcloud/files' import { loadState } from '@nextcloud/initial-state' import registerFileDropView from './files_views/publicFileDrop.ts' import registerPublicShareView from './files_views/publicShare.ts' import registerPublicFileShareView from './files_views/publicFileShare.ts' -import RouterService from '../../files/src/services/RouterService' -import router from './router' +import RouterService from '../../files/src/services/RouterService.ts' +import router from './router/index.ts' +import logger from './services/logger.ts' registerFileDropView() registerPublicShareView() @@ -33,3 +36,28 @@ if (fileId !== null) { { ...window.OCP.Files.Router.query, openfile: 'true' }, ) } + +// When the file list is loaded we need to apply the "userconfig" setup on the share +subscribe('files:list:updated', loadShareConfig) + +/** + * Event handler to load the view config for the current share. + * This is done on the `files:list:updated` event to ensure the list and especially the config store was correctly initialized. + * + * @param context The event context + * @param context.folder The current folder + */ +function loadShareConfig({ folder }: { folder: Folder }) { + // Only setup config once + unsubscribe('files:list:updated', loadShareConfig) + + // Share attributes (the same) are set on all folders of a share + if (folder.attributes['share-attributes']) { + const shareAttributes = JSON.parse(folder.attributes['share-attributes'] || '[]') as Array<ShareAttribute> + const gridViewAttribute = shareAttributes.find(({ scope, key }: ShareAttribute) => scope === 'config' && key === 'grid_view') + if (gridViewAttribute !== undefined) { + logger.debug('Loading share attributes', { gridViewAttribute }) + emit('files:config:updated', { key: 'grid_view', value: gridViewAttribute.value === true }) + } + } +} diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue index df420cf7af2..b0369df6339 100644 --- a/apps/files_sharing/src/views/SharingDetailsTab.vue +++ b/apps/files_sharing/src/views/SharingDetailsTab.vue @@ -169,7 +169,7 @@ @update:checked="queueUpdate('hideDownload')"> {{ t('files_sharing', 'Hide download') }} </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch v-if="!isPublicShare" + <NcCheckboxRadioSwitch v-else :disabled="!canSetDownload" :checked.sync="canDownload" data-cy-files-sharing-share-permissions-checkbox="download"> @@ -183,6 +183,10 @@ :placeholder="t('files_sharing', 'Enter a note for the share recipient')" :value.sync="share.note" /> </template> + <NcCheckboxRadioSwitch v-if="isPublicShare && isFolder" + :checked.sync="showInGridView"> + {{ t('files_sharing', 'Show files in grid view') }} + </NcCheckboxRadioSwitch> <ExternalShareAction v-for="action in externalLinkActions" :id="action.id" ref="externalLinkActions" @@ -439,28 +443,29 @@ export default { this.updateAtomicPermissions({ isReshareChecked: checked }) }, }, + + /** + * Change the default view for public shares from "list" to "grid" + */ + showInGridView: { + get() { + return this.getShareAttribute('config', 'grid_view', false) + }, + /** @param {boolean} value If the default view should be changed to "grid" */ + set(value) { + this.setShareAttribute('config', 'grid_view', value) + }, + }, + /** * Can the sharee download files or only view them ? */ canDownload: { get() { - return this.share.attributes?.find(attr => attr.key === 'download')?.value ?? true + return this.getShareAttribute('permissions', 'download', true) }, set(checked) { - // Find the 'download' attribute and update its value - const downloadAttr = this.share.attributes?.find(attr => attr.key === 'download') - if (downloadAttr) { - downloadAttr.value = checked - } else { - if (this.share.attributes === null) { - this.$set(this.share, 'attributes', []) - } - this.share.attributes.push({ - scope: 'permissions', - key: 'download', - value: checked, - }) - } + this.setShareAttribute('permissions', 'download', checked) }, }, /** @@ -783,6 +788,42 @@ export default { }, methods: { + /** + * Set a share attribute on the current share + * @param {string} scope The attribute scope + * @param {string} key The attribute key + * @param {boolean} value The value + */ + setShareAttribute(scope, key, value) { + if (!this.share.attributes) { + this.$set(this.share, 'attributes', []) + } + + const attribute = this.share.attributes + .find((attr) => attr.scope === scope || attr.key === key) + + if (attribute) { + attribute.value = value + } else { + this.share.attributes.push({ + scope, + key, + value, + }) + } + }, + + /** + * Get the value of a share attribute + * @param {string} scope The attribute scope + * @param {string} key The attribute key + * @param {undefined|boolean} fallback The fallback to return if not found + */ + getShareAttribute(scope, key, fallback = undefined) { + const attribute = this.share.attributes?.find((attr) => attr.scope === scope && attr.key === key) + return attribute?.value ?? fallback + }, + async generateNewToken() { if (this.loadingToken) { return diff --git a/cypress/e2e/files_sharing/public-share/default-view.cy.ts b/cypress/e2e/files_sharing/public-share/default-view.cy.ts new file mode 100644 index 00000000000..62796a6420a --- /dev/null +++ b/cypress/e2e/files_sharing/public-share/default-view.cy.ts @@ -0,0 +1,102 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import type { User } from '@nextcloud/cypress' +import { getRowForFile } from '../../files/FilesUtils.ts' +import { createShare, setupData } from './setup-public-share.ts' + +describe('files_sharing: Public share - setting the default view mode', () => { + + let user: User + + beforeEach(() => { + cy.createRandomUser() + .then(($user) => (user = $user)) + .then(() => setupData(user, 'shared')) + }) + + it('is by default in list view', () => { + const context = { user } + createShare(context, 'shared') + .then((url) => { + cy.logout() + cy.visit(url!) + + // See file is visible + getRowForFile('foo.txt').should('be.visible') + // See we are in list view + cy.findByRole('button', { name: 'Switch to grid view' }) + .should('be.visible') + .and('not.be.disabled') + }) + }) + + it('can be toggled by user', () => { + const context = { user } + createShare(context, 'shared') + .then((url) => { + cy.logout() + cy.visit(url!) + + // See file is visible + getRowForFile('foo.txt') + .should('be.visible') + // See we are in list view + .find('.files-list__row-icon') + .should(($el) => expect($el.outerWidth()).to.be.lessThan(99)) + + // See the grid view toggle + cy.findByRole('button', { name: 'Switch to grid view' }) + .should('be.visible') + .and('not.be.disabled') + // And can change to grid view + .click() + + // See we are in grid view + getRowForFile('foo.txt') + .find('.files-list__row-icon') + .should(($el) => expect($el.outerWidth()).to.be.greaterThan(99)) + + // See the grid view toggle is now the list view toggle + cy.findByRole('button', { name: 'Switch to list view' }) + .should('be.visible') + .and('not.be.disabled') + }) + }) + + it('can be changed to default grid view', () => { + const context = { user } + createShare(context, 'shared') + .then((url) => { + // Can set the "grid" view checkbox + cy.findByRole('list', { name: 'Link shares' }) + .findAllByRole('listitem') + .first() + .findByRole('button', { name: /Actions/i }) + .click() + cy.findByRole('menuitem', { name: /Customize link/i }).click() + cy.findByRole('checkbox', { name: /Show files in grid view/i }) + .scrollIntoView() + cy.findByRole('checkbox', { name: /Show files in grid view/i }) + .should('not.be.checked') + .check({ force: true }) + + // Wait for the share update + cy.intercept('PUT', '**/ocs/v2.php/apps/files_sharing/api/v1/shares/*').as('updateShare') + cy.findByRole('button', { name: 'Update share' }).click() + cy.wait('@updateShare').its('response.statusCode').should('eq', 200) + + // Logout and visit the share + cy.logout() + cy.visit(url!) + + // See file is visible + getRowForFile('foo.txt').should('be.visible') + // See we are in list view + cy.findByRole('button', { name: 'Switch to list view' }) + .should('be.visible') + .and('not.be.disabled') + }) + }) +}) |