aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2023-03-31 12:46:46 +0200
committerJohn Molakvoæ <skjnldsv@protonmail.com>2023-04-06 14:49:32 +0200
commitc7c9ee1ebdd07fdd5cf295835d5cf5e061fc40af (patch)
tree2f1b19981435e221717ff42e8215553a319c7bad
parent044e8242602907c8fa73989aa8149cf879de8881 (diff)
downloadnextcloud-server-c7c9ee1ebdd07fdd5cf295835d5cf5e061fc40af.tar.gz
nextcloud-server-c7c9ee1ebdd07fdd5cf295835d5cf5e061fc40af.zip
feat(files): move userconfig to dedicated store and fix crop previews
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
-rw-r--r--apps/files/src/components/FileEntry.vue76
-rw-r--r--apps/files/src/components/FilesListHeader.vue6
-rw-r--r--apps/files/src/components/FilesListHeaderActions.vue (renamed from apps/files/src/components/FilesListActionsHeader.vue)2
-rw-r--r--apps/files/src/main.js10
-rw-r--r--apps/files/src/store/userconfig.ts76
-rw-r--r--apps/files/src/types.ts8
-rw-r--r--apps/files/src/views/FilesList.vue6
-rw-r--r--apps/files/src/views/Settings.vue34
-rw-r--r--apps/files_trashbin/src/services/trashbin.ts2
9 files changed, 171 insertions, 49 deletions
diff --git a/apps/files/src/components/FileEntry.vue b/apps/files/src/components/FileEntry.vue
index fa4c67cb553..71a0c4e2a65 100644
--- a/apps/files/src/components/FileEntry.vue
+++ b/apps/files/src/components/FileEntry.vue
@@ -89,7 +89,10 @@
:key="column.id"
:class="`files-list__row-${currentView?.id}-${column.id}`"
class="files-list__row-column-custom">
- <CustomElementRender v-if="active" :current-view="currentView" :render="column.render" :source="source" />
+ <CustomElementRender v-if="active"
+ :current-view="currentView"
+ :render="column.render"
+ :source="source" />
</td>
</Fragment>
</template>
@@ -99,27 +102,25 @@ import { debounce } from 'debounce'
import { Folder, File, getFileActions, formatFileSize } from '@nextcloud/files'
import { Fragment } from 'vue-fragment'
import { join } from 'path'
-import { loadState } from '@nextcloud/initial-state'
+import { mapState } from 'pinia'
+import { showError } from '@nextcloud/dialogs'
import { translate } from '@nextcloud/l10n'
import FileIcon from 'vue-material-design-icons/File.vue'
import FolderIcon from 'vue-material-design-icons/Folder.vue'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
-import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import Vue from 'vue'
-import { showError } from '@nextcloud/dialogs'
import { useFilesStore } from '../store/files'
import { useSelectionStore } from '../store/selection'
+import { useUserConfigStore } from '../store/userconfig'
import CustomElementRender from './CustomElementRender.vue'
import CustomSvgIconRender from './CustomSvgIconRender.vue'
import logger from '../logger.js'
+import { UserConfig } from '../types'
-// TODO: move to store
-// TODO: watch 'files:config:updated' event
-const userConfig = loadState('files', 'config', {})
// The preview service worker cache name (see webpack config)
const SWCacheName = 'previews'
@@ -160,9 +161,11 @@ export default Vue.extend({
setup() {
const filesStore = useFilesStore()
const selectionStore = useSelectionStore()
+ const userConfigStore = useUserConfigStore()
return {
filesStore,
selectionStore,
+ userConfigStore,
}
},
@@ -171,11 +174,15 @@ export default Vue.extend({
backgroundFailed: false,
backgroundImage: '',
loading: '',
- userConfig,
}
},
computed: {
+ /** @return {UserConfig} */
+ userConfig() {
+ return this.userConfigStore.userConfig
+ },
+
/** @return {Navigation} */
currentView() {
return this.$navigation.active
@@ -244,11 +251,18 @@ export default Vue.extend({
},
},
+ cropPreviews() {
+ return this.userConfig.crop_image_previews
+ },
+
previewUrl() {
try {
const url = new URL(window.location.origin + this.source.attributes.previewUrl)
- const cropping = this.userConfig?.crop_image_previews === true
- url.searchParams.set('a', cropping ? '1' : '0')
+ // Request tiny previews
+ url.searchParams.set('x', '32')
+ url.searchParams.set('y', '32')
+ // Handle cropping
+ url.searchParams.set('a', this.cropPreviews === true ? '1' : '0')
return url.href
} catch (e) {
return null
@@ -287,8 +301,8 @@ export default Vue.extend({
},
watch: {
- active(active) {
- if (active === false) {
+ active(active, before) {
+ if (active === false && before === true) {
this.resetState()
// When the row is not active anymore
@@ -296,6 +310,7 @@ export default Vue.extend({
this.$el.parentNode.style.display = 'none'
return
}
+
// Restore default tabindex
this.$el.parentNode.style.display = ''
},
@@ -303,8 +318,8 @@ export default Vue.extend({
* When the source changes, reset the preview
* and fetch the new one.
*/
- source() {
- this.resetState()
+ previewUrl() {
+ this.clearImg()
this.debounceIfNotCached()
},
},
@@ -313,12 +328,18 @@ export default Vue.extend({
* The row is mounted once and reused as we scroll.
*/
mounted() {
- // Init the debounce function on mount and
- // not when the module is imported ⚠
+ // ⚠ Init the debounce function on mount and
+ // not when the module is imported to
+ // avoid sharing between recycled components
this.debounceGetPreview = debounce(function() {
this.fetchAndApplyPreview()
}, 150, false)
+ // ⚠ Init img on mount and
+ // not when the module is imported to
+ // avoid sharing between recycled components
+ this.img = null
+
this.debounceIfNotCached()
},
@@ -345,7 +366,18 @@ export default Vue.extend({
},
fetchAndApplyPreview() {
+ // Ignore if no preview
+ if (!this.previewUrl) {
+ return
+ }
+
+ // If any image is being processed, reset it
+ if (this.img) {
+ this.clearImg()
+ }
+
this.img = new Image()
+ this.img.fetchpriority = this.active ? 'high' : 'auto'
this.img.onload = () => {
this.backgroundImage = `url(${this.previewUrl})`
}
@@ -360,19 +392,23 @@ export default Vue.extend({
this.loading = ''
// Reset the preview
+ this.clearImg()
+
+ // Close menu
+ this.$refs.actionsMenu.closeMenu()
+ },
+
+ clearImg() {
this.backgroundImage = ''
this.backgroundFailed = false
- // If we're already fetching a preview, cancel it
if (this.img) {
// Do not fail on cancel
this.img.onerror = null
this.img.src = ''
- delete this.img
}
- // Close menu
- this.$refs.actionsMenu.closeMenu()
+ this.img = null
},
isCachedPreview(previewUrl) {
diff --git a/apps/files/src/components/FilesListHeader.vue b/apps/files/src/components/FilesListHeader.vue
index 66b2845ea11..184ca7aa30e 100644
--- a/apps/files/src/components/FilesListHeader.vue
+++ b/apps/files/src/components/FilesListHeader.vue
@@ -26,7 +26,7 @@
</th>
<!-- Actions multiple if some are selected -->
- <FilesListActionsHeader v-if="!isNoneSelected"
+ <FilesListHeaderActions v-if="!isNoneSelected"
:current-view="currentView"
:selected-nodes="selectedNodes" />
@@ -74,7 +74,7 @@ import Vue from 'vue'
import { useFilesStore } from '../store/files'
import { useSelectionStore } from '../store/selection'
import { useSortingStore } from '../store/sorting'
-import FilesListActionsHeader from './FilesListActionsHeader.vue'
+import FilesListHeaderActions from './FilesListHeaderActions.vue'
import FilesListHeaderButton from './FilesListHeaderButton.vue'
import logger from '../logger.js'
import Navigation from '../services/Navigation'
@@ -85,7 +85,7 @@ export default Vue.extend({
components: {
FilesListHeaderButton,
NcCheckboxRadioSwitch,
- FilesListActionsHeader,
+ FilesListHeaderActions,
},
props: {
diff --git a/apps/files/src/components/FilesListActionsHeader.vue b/apps/files/src/components/FilesListHeaderActions.vue
index 9abb30c6c2e..8f519734284 100644
--- a/apps/files/src/components/FilesListActionsHeader.vue
+++ b/apps/files/src/components/FilesListHeaderActions.vue
@@ -57,7 +57,7 @@ import logger from '../logger.js'
const actions = getFileActions()
export default Vue.extend({
- name: 'FilesListActionsHeader',
+ name: 'FilesListHeaderActions',
components: {
CustomSvgIconRender,
diff --git a/apps/files/src/main.js b/apps/files/src/main.js
index 5aca12754b4..e516275ba28 100644
--- a/apps/files/src/main.js
+++ b/apps/files/src/main.js
@@ -18,11 +18,14 @@ import SettingsModel from './models/Setting.js'
import router from './router/router.js'
-
// Init private and public Files namespace
window.OCA.Files = window.OCA.Files ?? {}
window.OCP.Files = window.OCP.Files ?? {}
+// Init Pinia store
+Vue.use(PiniaVuePlugin)
+const pinia = createPinia()
+
// Init Navigation Service
const Navigation = new NavigationService()
Object.assign(window.OCP.Files, { Navigation })
@@ -41,13 +44,10 @@ const FilesNavigationRoot = new View({
Navigation,
},
router,
+ pinia,
})
FilesNavigationRoot.$mount('#app-navigation-files')
-// Init Pinia store
-Vue.use(PiniaVuePlugin)
-const pinia = createPinia()
-
// Init content list view
const ListView = Vue.extend(FilesListView)
const FilesList = new ListView({
diff --git a/apps/files/src/store/userconfig.ts b/apps/files/src/store/userconfig.ts
new file mode 100644
index 00000000000..d432b502acf
--- /dev/null
+++ b/apps/files/src/store/userconfig.ts
@@ -0,0 +1,76 @@
+/**
+ * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+/* eslint-disable */
+import { loadState } from '@nextcloud/initial-state'
+import { generateUrl } from '@nextcloud/router'
+import { defineStore } from 'pinia'
+import Vue from 'vue'
+import axios from '@nextcloud/axios'
+import type { UserConfig, UserConfigStore } from '../types'
+import { emit, subscribe } from '@nextcloud/event-bus'
+import type { update } from 'cypress/types/lodash'
+
+const userConfig = loadState('files', 'config', {
+ show_hidden: false,
+ crop_image_previews: true,
+}) as UserConfig
+
+export const useUserConfigStore = () => {
+ const store = defineStore('userconfig', {
+ state: () => ({
+ userConfig,
+ } as UserConfigStore),
+
+ actions: {
+ /**
+ * Update the user config local store
+ */
+ onUpdate(key: string, value: boolean) {
+ Vue.set(this.userConfig, key, value)
+ },
+
+ /**
+ * Update the user config local store AND on server side
+ */
+ async update(key: string, value: boolean) {
+ await axios.post(generateUrl('/apps/files/api/v1/config/' + key), {
+ value,
+ })
+
+ emit('files:config:updated', { key, value })
+ }
+ }
+ })
+
+ const userConfigStore = store()
+
+ // 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 userConfigStore
+}
+
diff --git a/apps/files/src/types.ts b/apps/files/src/types.ts
index 207a45c080b..28e1bad28a6 100644
--- a/apps/files/src/types.ts
+++ b/apps/files/src/types.ts
@@ -71,3 +71,11 @@ export interface SortingConfig {
export interface SortingStore {
[key: string]: SortingConfig
}
+
+// User config store
+export interface UserConfig {
+ [key: string]: boolean
+}
+export interface UserConfigStore {
+ userConfig: UserConfig
+}
diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue
index 8b615e2cd95..065749adaf5 100644
--- a/apps/files/src/views/FilesList.vue
+++ b/apps/files/src/views/FilesList.vue
@@ -75,7 +75,7 @@ import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import TrashCan from 'vue-material-design-icons/TrashCan.vue'
import Vue from 'vue'
-import { ContentsWithRoot } from '../services/Navigation'
+import Navigation, { ContentsWithRoot } from '../services/Navigation'
import { useFilesStore } from '../store/files'
import { usePathsStore } from '../store/paths'
import { useSelectionStore } from '../store/selection'
@@ -83,7 +83,6 @@ import { useSortingStore } from '../store/sorting'
import BreadCrumbs from '../components/BreadCrumbs.vue'
import FilesListVirtual from '../components/FilesListVirtual.vue'
import logger from '../logger.js'
-import Navigation from '../services/Navigation'
export default Vue.extend({
name: 'FilesList',
@@ -127,6 +126,7 @@ export default Vue.extend({
/**
* The current directory query.
+ *
* @return {string}
*/
dir() {
@@ -136,6 +136,7 @@ export default Vue.extend({
/**
* The current folder.
+ *
* @return {Folder|undefined}
*/
currentFolder() {
@@ -161,6 +162,7 @@ export default Vue.extend({
/**
* The current directory contents.
+ *
* @return {Node[]}
*/
dirContents() {
diff --git a/apps/files/src/views/Settings.vue b/apps/files/src/views/Settings.vue
index 9a117b70e22..b1c544f8ecf 100644
--- a/apps/files/src/views/Settings.vue
+++ b/apps/files/src/views/Settings.vue
@@ -26,11 +26,11 @@
@update:open="onClose">
<!-- Settings API-->
<NcAppSettingsSection id="settings" :title="t('files', 'Files settings')">
- <NcCheckboxRadioSwitch :checked.sync="show_hidden"
+ <NcCheckboxRadioSwitch :checked="userConfig.show_hidden"
@update:checked="setConfig('show_hidden', $event)">
{{ t('files', 'Show hidden files') }}
</NcCheckboxRadioSwitch>
- <NcCheckboxRadioSwitch :checked.sync="crop_image_previews"
+ <NcCheckboxRadioSwitch :checked="userConfig.crop_image_previews"
@update:checked="setConfig('crop_image_previews', $event)">
{{ t('files', 'Crop image previews') }}
</NcCheckboxRadioSwitch>
@@ -86,18 +86,11 @@ import Clipboard from 'vue-material-design-icons/Clipboard.vue'
import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js'
import Setting from '../components/Setting.vue'
-import { emit } from '@nextcloud/event-bus'
import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
-import { loadState } from '@nextcloud/initial-state'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { translate } from '@nextcloud/l10n'
-import axios from '@nextcloud/axios'
-
-const userConfig = loadState('files', 'config', {
- show_hidden: false,
- crop_image_previews: true,
-})
+import { useUserConfigStore } from '../store/userconfig'
export default {
name: 'Settings',
@@ -117,11 +110,15 @@ export default {
},
},
- data() {
+ setup() {
+ const userConfigStore = useUserConfigStore()
return {
+ userConfigStore,
+ }
+ },
- ...userConfig,
-
+ data() {
+ return {
// Settings API
settings: window.OCA?.Files?.Settings?.settings || [],
@@ -133,6 +130,12 @@ export default {
}
},
+ computed: {
+ userConfig() {
+ return this.userConfigStore.userConfig
+ },
+ },
+
beforeMount() {
// Update the settings API entries state
this.settings.forEach(setting => setting.open())
@@ -149,10 +152,7 @@ export default {
},
setConfig(key, value) {
- emit('files:config:updated', { key, value })
- axios.post(generateUrl('/apps/files/api/v1/config/' + key), {
- value,
- })
+ this.userConfigStore.update(key, value)
},
async copyCloudId() {
diff --git a/apps/files_trashbin/src/services/trashbin.ts b/apps/files_trashbin/src/services/trashbin.ts
index 2be4c39dbfc..6592857b2a3 100644
--- a/apps/files_trashbin/src/services/trashbin.ts
+++ b/apps/files_trashbin/src/services/trashbin.ts
@@ -53,7 +53,7 @@ const data = `<?xml version="1.0"?>
const resultToNode = function(node: FileStat): File | Folder {
const permissions = parseWebdavPermissions(node.props?.permissions)
const owner = getCurrentUser()?.uid as string
- const previewUrl = generateUrl('/apps/files_trashbin/preview?fileId={fileid}', node.props)
+ const previewUrl = generateUrl('/apps/files_trashbin/preview?fileId={fileid}x=32&y=32', node.props)
const nodeData = {
id: node.props?.fileid as number || 0,