Skip to content

Commit

Permalink
fix(files): only send config update requests if user is logged in
Browse files Browse the repository at this point in the history
Since we use the files app also for public shares it is not guaranteed
that there is a user logged in, in that case the update for user / view
config will fail.
So ensure there is a user or do not send a request.

Also refactor both stores to setup styles to fix (remove) initialization hack,
which causes Typescript issues.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
  • Loading branch information
susnux committed Feb 9, 2025
1 parent 8144ab6 commit 8ab40e5
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 127 deletions.
3 changes: 2 additions & 1 deletion apps/files/src/eventbus.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import type { IFileListFilter, Node } from '@nextcloud/files'
declare module '@nextcloud/event-bus' {
export interface NextcloudEvents {
// mapping of 'event name' => 'event type'
'files:config:updated': { key: string, value: unknown }
'files:config:updated': { key: string, value: boolean }
'files:view-config:updated': { key: string, value: string|number|boolean, view: string }

'files:favorites:removed': Node
'files:favorites:added': Node
Expand Down
78 changes: 36 additions & 42 deletions apps/files/src/store/userconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,55 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { UserConfig, UserConfigStore } from '../types'
import { defineStore } from 'pinia'
import type { UserConfig } from '../types'
import { getCurrentUser } from '@nextcloud/auth'
import { emit, subscribe } from '@nextcloud/event-bus'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import { generateUrl } from '@nextcloud/router'
import { defineStore } from 'pinia'
import { ref, set } from 'vue'
import axios from '@nextcloud/axios'
import Vue from 'vue'

const userConfig = loadState<UserConfig>('files', 'config', {
const initialUserConfig = loadState<UserConfig>('files', 'config', {
show_hidden: false,
crop_image_previews: true,
sort_favorites_first: true,
sort_folders_first: true,
grid_view: false,
})

export const useUserConfigStore = function(...args) {
const store = defineStore('userconfig', {
state: () => ({
userConfig,
} as UserConfigStore),
export const useUserConfigStore = defineStore('userconfig', () => {
const userConfig = ref<UserConfig>({ ...initialUserConfig })

actions: {
/**
* Update the user config local store
* @param key
* @param value
*/
onUpdate(key: string, value: boolean) {
Vue.set(this.userConfig, key, value)
},
/**
* Update the user config local store
* @param key The config key
* @param value The new value
*/
function onUpdate(key: string, value: boolean): void {
set(userConfig.value, key, value)
}

/**
* Update the user config local store AND on server side
* @param key
* @param value
*/
async update(key: string, value: boolean) {
await axios.put(generateUrl('/apps/files/api/v1/config/' + key), {
value,
})
emit('files:config:updated', { key, value })
},
},
})
/**
* Update the user config local store AND on server side
* @param key The config key
* @param value The new value
*/
async function update(key: string, value: boolean): Promise<void> {
// only update if a user is logged in (not the case for public shares)
if (getCurrentUser() !== null) {
await axios.put(generateUrl('/apps/files/api/v1/config/{key}', { key }), {
value,
})
}
emit('files:config:updated', { key, value })
}

const userConfigStore = store(...args)
// Register the event listener
subscribe('files:config:updated', ({ key, value }) => onUpdate(key, value))

// Make sure we only register the listeners once
if (!userConfigStore._initialized) {
subscribe('files:config:updated', function({ key, value }: { key: string, value: boolean }) {
userConfigStore.onUpdate(key, value)
})
userConfigStore._initialized = true
return {
userConfig,
update,
}

return userConfigStore
}
})
150 changes: 75 additions & 75 deletions apps/files/src/store/viewConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,95 +2,95 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { defineStore } from 'pinia'
import type { ViewConfigs, ViewId, ViewConfig } from '../types'

import { getCurrentUser } from '@nextcloud/auth'
import { emit, subscribe } from '@nextcloud/event-bus'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import { generateUrl } from '@nextcloud/router'
import { defineStore } from 'pinia'
import { ref, set } from 'vue'
import axios from '@nextcloud/axios'
import Vue from 'vue'

import type { ViewConfigs, ViewConfigStore, ViewId, ViewConfig } from '../types'
const initialViewConfig = loadState('files', 'viewConfigs', {}) as ViewConfigs

const viewConfig = loadState('files', 'viewConfigs', {}) as ViewConfigs
export const useViewConfigStore = defineStore('viewconfig', () => {

export const useViewConfigStore = function(...args) {
const store = defineStore('viewconfig', {
state: () => ({
viewConfig,
} as ViewConfigStore),
const viewConfigs = ref({ ...initialViewConfig })

getters: {
getConfig: (state) => (view: ViewId): ViewConfig => state.viewConfig[view] || {},
/**
* Get the config for a specific view
* @param viewid Id of the view to fet the config for
*/
function getConfig(viewid: ViewId): ViewConfig {
return viewConfigs.value[viewid] || {}
}

getConfigs: (state) => (): ViewConfigs => state.viewConfig,
},
/**
* Update the view config local store
* @param viewId The id of the view to update
* @param key The config key to update
* @param value The new value
*/
function onUpdate(viewId: ViewId, key: string, value: string | number | boolean): void {
if (!(viewId in viewConfigs.value)) {
set(viewConfigs.value, viewId, {})
}
set(viewConfigs.value[viewId], key, value)
}

actions: {
/**
* Update the view config local store
* @param view
* @param key
* @param value
*/
onUpdate(view: ViewId, key: string, value: string | number | boolean) {
if (!this.viewConfig[view]) {
Vue.set(this.viewConfig, view, {})
}
Vue.set(this.viewConfig[view], key, value)
},
/**
* Update the view config local store AND on server side
* @param view Id of the view to update
* @param key Config key to update
* @param value New value
*/
async function update(view: ViewId, key: string, value: string | number | boolean): Promise<void> {
if (getCurrentUser() !== null) {
await axios.put(generateUrl('/apps/files/api/v1/views'), {
value,
view,
key,
})
}

/**
* Update the view config local store AND on server side
* @param view
* @param key
* @param value
*/
async update(view: ViewId, key: string, value: string | number | boolean) {
axios.put(generateUrl('/apps/files/api/v1/views'), {
value,
view,
key,
})
emit('files:view-config:updated', { view, key, value })
}

emit('files:viewconfig:updated', { view, key, value })
},
/**
* Set the sorting key AND sort by ASC
* The key param must be a valid key of a File object
* If not found, will be searched within the File attributes
* @param key Key to sort by
* @param view View to set the sorting key for
*/
function setSortingBy(key = 'basename', view = 'files'): void {
// Save new config
update(view, 'sorting_mode', key)
update(view, 'sorting_direction', 'asc')
}

/**
* Set the sorting key AND sort by ASC
* The key param must be a valid key of a File object
* If not found, will be searched within the File attributes
* @param key Key to sort by
* @param view View to set the sorting key for
*/
setSortingBy(key = 'basename', view = 'files') {
// Save new config
this.update(view, 'sorting_mode', key)
this.update(view, 'sorting_direction', 'asc')
},
/**
* Toggle the sorting direction
* @param viewId id of the view to set the sorting order for
*/
function toggleSortingDirection(viewId = 'files'): void {
const config = viewConfigs.value[viewId] || { sorting_direction: 'asc' }
const newDirection = config.sorting_direction === 'asc' ? 'desc' : 'asc'

/**
* Toggle the sorting direction
* @param view view to set the sorting order for
*/
toggleSortingDirection(view = 'files') {
const config = this.getConfig(view) || { sorting_direction: 'asc' }
const newDirection = config.sorting_direction === 'asc' ? 'desc' : 'asc'
// Save new config
update(viewId, 'sorting_direction', newDirection)
}

// Save new config
this.update(view, 'sorting_direction', newDirection)
},
},
})
// Initialize event listener
subscribe('files:view-config:updated', ({ view, key, value }) => onUpdate(view, key, value))

const viewConfigStore = store(...args)
return {
viewConfigs,

// Make sure we only register the listeners once
if (!viewConfigStore._initialized) {
subscribe('files:viewconfig:updated', function({ view, key, value }: { view: ViewId, key: string, value: boolean }) {
viewConfigStore.onUpdate(view, key, value)
})
viewConfigStore._initialized = true
getConfig,
setSortingBy,
toggleSortingDirection,
update,
}

return viewConfigStore
}
})
8 changes: 7 additions & 1 deletion apps/files/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ export interface PathOptions {

// User config store
export interface UserConfig {
[key: string]: boolean
[key: string]: boolean|undefined

show_hidden: boolean
crop_image_previews: boolean
sort_favorites_first: boolean
sort_folders_first: boolean
grid_view: boolean
}
export interface UserConfigStore {
userConfig: UserConfig
Expand Down
14 changes: 6 additions & 8 deletions apps/files/src/views/Navigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,12 @@ export default defineComponent({

methods: {
async loadExpandedViews() {
const viewConfigs = this.viewConfigStore.getConfigs()
const viewsToLoad: View[] = (Object.entries(viewConfigs) as Array<[string, ViewConfig]>)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.filter(([viewId, config]) => config.expanded === true)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.map(([viewId, config]) => this.views.find(view => view.id === viewId))
.filter(Boolean) // Only registered views
.filter(view => view.loadChildViews && !view.loaded)
const viewsToLoad: View[] = (Object.entries(this.viewConfigStore.viewConfigs) as Array<[string, ViewConfig]>)
.filter(([, config]) => config.expanded === true)
.map(([viewId]) => this.views.find(view => view.id === viewId))
// eslint-disable-next-line no-use-before-define
.filter(Boolean as unknown as ((u: unknown) => u is View))
.filter((view) => view.loadChildViews && !view.loaded)
for (const view of viewsToLoad) {
await view.loadChildViews(view)
}
Expand Down

0 comments on commit 8ab40e5

Please sign in to comment.