Skip to content

Commit

Permalink
feat(sharing): Allow to set default view mode for public shares
Browse files Browse the repository at this point in the history
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
  • Loading branch information
susnux committed Feb 8, 2025
1 parent c3f19da commit 1f4a776
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 17 deletions.
15 changes: 15 additions & 0 deletions apps/files_sharing/src/eventbus.d.ts
Original file line number Diff line number Diff line change
@@ -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 {}
22 changes: 21 additions & 1 deletion apps/files_sharing/src/init-public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { 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 type { ShareAttribute } from './sharing'
import logger from './services/logger.ts'

registerFileDropView()
registerPublicShareView()
Expand All @@ -33,3 +36,20 @@ 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)

function loadShareConfig({ folder }: { folder: Folder }) {
// Only setup config once
unsubscribe('files:list:updated', loadShareConfig)

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 })
}
}
}
73 changes: 57 additions & 16 deletions apps/files_sharing/src/views/SharingDetailsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand All @@ -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"
Expand Down Expand Up @@ -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)
},
},
/**
Expand Down Expand Up @@ -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
Expand Down
102 changes: 102 additions & 0 deletions cypress/e2e/files_sharing/public-share/default-view.cy.ts
Original file line number Diff line number Diff line change
@@ -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')
})
})
})

0 comments on commit 1f4a776

Please sign in to comment.