зеркало из https://github.com/nextcloud/server.git
feat(files): switch to pinia
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
This commit is contained in:
Родитель
29a7f7f6ef
Коммит
03c32774b0
|
@ -12,8 +12,9 @@
|
|||
import NcBreadcrumbs from '@nextcloud/vue/dist/Components/NcBreadcrumbs.js'
|
||||
import NcBreadcrumb from '@nextcloud/vue/dist/Components/NcBreadcrumb.js'
|
||||
import { basename } from 'path'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default {
|
||||
export default Vue.extend({
|
||||
name: 'BreadCrumbs',
|
||||
|
||||
components: {
|
||||
|
@ -45,7 +46,7 @@ export default {
|
|||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -47,12 +47,15 @@ import { Folder, File } from '@nextcloud/files'
|
|||
import { Fragment } from 'vue-fragment'
|
||||
import { join } from 'path'
|
||||
import { translate } from '@nextcloud/l10n'
|
||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
|
||||
import FolderIcon from 'vue-material-design-icons/Folder.vue'
|
||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
|
||||
import Vue from 'vue'
|
||||
|
||||
import logger from '../logger'
|
||||
import { useSelectionStore } from '../store/selection'
|
||||
import { useFilesStore } from '../store/files'
|
||||
|
||||
export default {
|
||||
export default Vue.extend({
|
||||
name: 'FileEntry',
|
||||
|
||||
components: {
|
||||
|
@ -72,6 +75,15 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
const filesStore = useFilesStore()
|
||||
const selectionStore = useSelectionStore()
|
||||
return {
|
||||
filesStore,
|
||||
selectionStore,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
dir() {
|
||||
// Remove any trailing slash but leave root slash
|
||||
|
@ -104,11 +116,11 @@ export default {
|
|||
|
||||
selectedFiles: {
|
||||
get() {
|
||||
return this.$store.state.selection.selected
|
||||
return this.selectionStore.selected
|
||||
},
|
||||
set(selection) {
|
||||
logger.debug('Added node to selection', { selection })
|
||||
this.$store.dispatch('selection/set', selection)
|
||||
this.selectionStore.set(selection)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -121,12 +133,12 @@ export default {
|
|||
* @return {Folder|File}
|
||||
*/
|
||||
getNode(fileId) {
|
||||
return this.$store.getters['files/getNode'](fileId)
|
||||
return this.filesStore.getNode(fileId)
|
||||
},
|
||||
|
||||
t: translate,
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -36,13 +36,16 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { File, Folder } from '@nextcloud/files'
|
||||
import { translate } from '@nextcloud/l10n'
|
||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
|
||||
import Vue from 'vue'
|
||||
|
||||
import logger from '../logger'
|
||||
import { File, Folder } from '@nextcloud/files'
|
||||
import { useSelectionStore } from '../store/selection'
|
||||
import { useFilesStore } from '../store/files'
|
||||
|
||||
export default {
|
||||
export default Vue.extend({
|
||||
name: 'FilesListHeader',
|
||||
|
||||
components: {
|
||||
|
@ -56,6 +59,15 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
const filesStore = useFilesStore()
|
||||
const selectionStore = useSelectionStore()
|
||||
return {
|
||||
filesStore,
|
||||
selectionStore,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
dir() {
|
||||
// Remove any trailing slash but leave root slash
|
||||
|
@ -63,12 +75,14 @@ export default {
|
|||
},
|
||||
|
||||
selectAllBind() {
|
||||
const label = this.isNoneSelected || this.isSomeSelected
|
||||
? this.t('files', 'Select all')
|
||||
: this.t('files', 'Unselect all')
|
||||
return {
|
||||
ariaLabel: this.isNoneSelected || this.isSomeSelected
|
||||
? this.t('files', 'Select all')
|
||||
: this.t('files', 'Unselect all'),
|
||||
'aria-label': label,
|
||||
checked: this.isAllSelected,
|
||||
indeterminate: this.isSomeSelected,
|
||||
title: label,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -85,7 +99,7 @@ export default {
|
|||
},
|
||||
|
||||
selectedFiles() {
|
||||
return this.$store.state.selection.selected
|
||||
return this.selectionStore.selected
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -97,23 +111,23 @@ export default {
|
|||
* @return {Folder|File}
|
||||
*/
|
||||
getNode(fileId) {
|
||||
return this.$store.getters['files/getNode'](fileId)
|
||||
return this.filesStore.getNode(fileId)
|
||||
},
|
||||
|
||||
onToggleAll(selected) {
|
||||
if (selected) {
|
||||
const selection = this.nodes.map(node => node.attributes.fileid.toString())
|
||||
logger.debug('Added all nodes to selection', { selection })
|
||||
this.$store.dispatch('selection/set', selection)
|
||||
this.selectionStore.set(selection)
|
||||
} else {
|
||||
logger.debug('Cleared selection')
|
||||
this.$store.dispatch('selection/reset')
|
||||
this.selectionStore.reset()
|
||||
}
|
||||
},
|
||||
|
||||
t: translate,
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -44,11 +44,12 @@
|
|||
import { Folder, File } from '@nextcloud/files'
|
||||
import { translate, translatePlural } from '@nextcloud/l10n'
|
||||
import VirtualList from 'vue-virtual-scroll-list'
|
||||
import Vue from 'vue'
|
||||
|
||||
import FileEntry from './FileEntry.vue'
|
||||
import FilesListHeader from './FilesListHeader.vue'
|
||||
|
||||
export default {
|
||||
export default Vue.extend({
|
||||
name: 'FilesListVirtual',
|
||||
|
||||
components: {
|
||||
|
@ -94,7 +95,7 @@ export default {
|
|||
|
||||
t: translate,
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -116,6 +117,14 @@ export default {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
thead {
|
||||
// Pinned on top when scrolling
|
||||
position: sticky;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
background-color: var(--color-main-background);
|
||||
}
|
||||
|
||||
thead, .files-list__row {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ import './legacy/filelistSearch.js'
|
|||
import processLegacyFilesViews from './legacy/navigationMapper.js'
|
||||
|
||||
import Vue from 'vue'
|
||||
import { createPinia, PiniaVuePlugin } from 'pinia'
|
||||
|
||||
import NavigationService from './services/Navigation.ts'
|
||||
|
||||
import NavigationView from './views/Navigation.vue'
|
||||
|
@ -12,7 +14,6 @@ import SettingsService from './services/Settings.js'
|
|||
import SettingsModel from './models/Setting.js'
|
||||
|
||||
import router from './router/router.js'
|
||||
import store from './store/index.ts'
|
||||
|
||||
// Init private and public Files namespace
|
||||
window.OCA.Files = window.OCA.Files ?? {}
|
||||
|
@ -38,6 +39,10 @@ const FilesNavigationRoot = new View({
|
|||
})
|
||||
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({
|
||||
|
@ -46,7 +51,7 @@ const FilesList = new ListView({
|
|||
Navigation,
|
||||
},
|
||||
router,
|
||||
store,
|
||||
pinia,
|
||||
})
|
||||
FilesList.$mount('#app-content-vue')
|
||||
|
||||
|
|
|
@ -21,77 +21,50 @@
|
|||
*/
|
||||
/* eslint-disable */
|
||||
import type { Folder, Node } from '@nextcloud/files'
|
||||
import type { FilesStore, RootsStore, RootOptions, Service, FilesState } from '../types'
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import Vue from 'vue'
|
||||
import type { FileStore, RootStore, RootOptions, Service } from '../types'
|
||||
import logger from '../logger'
|
||||
|
||||
const state = {
|
||||
files: {} as FileStore,
|
||||
roots: {} as RootStore,
|
||||
}
|
||||
export const useFilesStore = defineStore('files', {
|
||||
state: (): FilesState => ({
|
||||
files: {} as FilesStore,
|
||||
roots: {} as RootsStore,
|
||||
}),
|
||||
|
||||
const getters = {
|
||||
/**
|
||||
* Get a file or folder by id
|
||||
*/
|
||||
getNode: (state) => (id: number): Node|undefined => state.files[id],
|
||||
getters: {
|
||||
/**
|
||||
* Get a file or folder by id
|
||||
*/
|
||||
getNode: (state) => (id: number): Node|undefined => state.files[id],
|
||||
|
||||
/**
|
||||
* Get a list of files or folders by their IDs
|
||||
* Does not return undefined values
|
||||
*/
|
||||
getNodes: (state) => (ids: number[]): Node[] => ids
|
||||
.map(id => state.files[id])
|
||||
.filter(Boolean),
|
||||
/**
|
||||
* Get a file or folder by id
|
||||
*/
|
||||
getRoot: (state) => (service: Service): Folder|undefined => state.roots[service],
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
updateNodes: (state, nodes: Node[]) => {
|
||||
nodes.forEach(node => {
|
||||
if (!node.attributes.fileid) {
|
||||
return
|
||||
}
|
||||
Vue.set(state.files, node.attributes.fileid, node)
|
||||
// state.files = {
|
||||
// ...state.files,
|
||||
// [node.attributes.fileid]: node,
|
||||
// }
|
||||
})
|
||||
/**
|
||||
* Get a list of files or folders by their IDs
|
||||
* Does not return undefined values
|
||||
*/
|
||||
getNodes: (state) => (ids: number[]): Node[] => ids
|
||||
.map(id => state.files[id])
|
||||
.filter(Boolean),
|
||||
/**
|
||||
* Get a file or folder by id
|
||||
*/
|
||||
getRoot: (state) => (service: Service): Folder|undefined => state.roots[service],
|
||||
},
|
||||
|
||||
setRoot: (state, { service, root }: RootOptions) => {
|
||||
state.roots = {
|
||||
...state.roots,
|
||||
[service]: root,
|
||||
actions: {
|
||||
updateNodes(nodes: Node[]) {
|
||||
nodes.forEach(node => {
|
||||
if (!node.attributes.fileid) {
|
||||
logger.warn('Trying to update/set a node without fileid', node)
|
||||
return
|
||||
}
|
||||
Vue.set(this.files, node.attributes.fileid, node)
|
||||
})
|
||||
},
|
||||
|
||||
setRoot({ service, root }: RootOptions) {
|
||||
Vue.set(this.roots, service, root)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
/**
|
||||
* Insert valid nodes into the store.
|
||||
* Roots (that does _not_ have a fileid) should
|
||||
* be defined in the roots store
|
||||
*/
|
||||
addNodes: (context, nodes: Node[]) => {
|
||||
context.commit('updateNodes', nodes)
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the root of a service
|
||||
*/
|
||||
setRoot(context, { service, root }: RootOptions) {
|
||||
context.commit('setRoot', { service, root })
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
actions,
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex, { Store } from 'vuex'
|
||||
|
||||
import files from './files'
|
||||
import paths from './paths'
|
||||
import selection from './selection'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Store({
|
||||
modules: {
|
||||
files,
|
||||
paths,
|
||||
selection,
|
||||
},
|
||||
})
|
|
@ -20,52 +20,34 @@
|
|||
*
|
||||
*/
|
||||
/* eslint-disable */
|
||||
import type { Folder } from '@nextcloud/files'
|
||||
import Vue from 'vue'
|
||||
import type { PathOptions, ServicePaths, ServiceStore } from '../types'
|
||||
import type { PathOptions, ServicesState } from '../types'
|
||||
|
||||
const module = {
|
||||
state: {
|
||||
services: {
|
||||
files: {} as ServicePaths,
|
||||
} as ServiceStore,
|
||||
},
|
||||
import { defineStore } from 'pinia'
|
||||
import Vue from 'vue'
|
||||
|
||||
export const usePathsStore = defineStore('paths', {
|
||||
state: (): ServicesState => ({}),
|
||||
|
||||
getters: {
|
||||
getPath(state: { services: ServiceStore }) {
|
||||
getPath: (state) => {
|
||||
return (service: string, path: string): number|undefined => {
|
||||
if (!state.services[service]) {
|
||||
if (!state[service]) {
|
||||
return undefined
|
||||
}
|
||||
return state.services[service][path]
|
||||
return state[service][path]
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mutations: {
|
||||
addPath: (state, opts: PathOptions) => {
|
||||
// If it doesn't exists, init the service state
|
||||
if (!state.services[opts.service]) {
|
||||
// TODO: investigate why Vue.set is not working
|
||||
state.services = {
|
||||
[opts.service]: {} as ServicePaths,
|
||||
...state.services
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can set the path
|
||||
Vue.set(state.services[opts.service], opts.path, opts.fileid)
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
addPath: (context, opts: PathOptions) => {
|
||||
context.commit('addPath', opts)
|
||||
addPath(payload: PathOptions) {
|
||||
// If it doesn't exists, init the service state
|
||||
if (!this[payload.service]) {
|
||||
Vue.set(this, payload.service, {})
|
||||
}
|
||||
|
||||
// Now we can set the provided path
|
||||
Vue.set(this[payload.service], payload.path, payload.fileid)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
...module,
|
||||
}
|
||||
})
|
||||
|
|
|
@ -20,32 +20,27 @@
|
|||
*
|
||||
*/
|
||||
/* eslint-disable */
|
||||
import type { Folder } from '@nextcloud/files'
|
||||
import { defineStore } from 'pinia'
|
||||
import Vue from 'vue'
|
||||
import type { PathOptions, ServicePaths, ServiceStore } from '../types'
|
||||
|
||||
const module = {
|
||||
state: {
|
||||
export const useSelectionStore = defineStore('selection', {
|
||||
state: () => ({
|
||||
selected: [] as number[]
|
||||
},
|
||||
|
||||
mutations: {
|
||||
set: (state, selection: number[]) => {
|
||||
Vue.set(state, 'selected', selection)
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
actions: {
|
||||
set: (context, selection = [] as number[]) => {
|
||||
context.commit('set', selection)
|
||||
/**
|
||||
* Set the selection of fileIds
|
||||
*/
|
||||
set(selection = [] as number[]) {
|
||||
Vue.set(this, 'selected', selection)
|
||||
},
|
||||
reset(context) {
|
||||
context.commit('set', [])
|
||||
|
||||
/**
|
||||
* Reset the selection
|
||||
*/
|
||||
reset() {
|
||||
Vue.set(this, 'selected', [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
...module,
|
||||
}
|
||||
})
|
||||
|
|
|
@ -27,11 +27,16 @@ import type { Node } from '@nextcloud/files'
|
|||
export type Service = string
|
||||
|
||||
// Files store
|
||||
export type FileStore = {
|
||||
export type FilesState = {
|
||||
files: FilesStore,
|
||||
roots: RootsStore,
|
||||
}
|
||||
|
||||
export type FilesStore = {
|
||||
[id: number]: Node
|
||||
}
|
||||
|
||||
export type RootStore = {
|
||||
export type RootsStore = {
|
||||
[service: Service]: Folder
|
||||
}
|
||||
|
||||
|
@ -41,12 +46,12 @@ export interface RootOptions {
|
|||
}
|
||||
|
||||
// Paths store
|
||||
export type ServicePaths = {
|
||||
[path: string]: number
|
||||
export type ServicesState = {
|
||||
[service: Service]: PathsStore
|
||||
}
|
||||
|
||||
export type ServiceStore = {
|
||||
[service: Service]: ServicePaths
|
||||
export type PathsStore = {
|
||||
[path: string]: number
|
||||
}
|
||||
|
||||
export interface PathOptions {
|
||||
|
|
|
@ -75,8 +75,12 @@ import Navigation from '../services/Navigation'
|
|||
import FilesListVirtual from '../components/FilesListVirtual.vue'
|
||||
import { ContentsWithRoot } from '../services/Navigation'
|
||||
import { join } from 'path'
|
||||
import Vue from 'vue'
|
||||
import { usePathsStore } from '../store/paths'
|
||||
import { useFilesStore } from '../store/files'
|
||||
import { useSelectionStore } from '../store/selection'
|
||||
|
||||
export default {
|
||||
export default Vue.extend({
|
||||
name: 'FilesList',
|
||||
|
||||
components: {
|
||||
|
@ -97,6 +101,17 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
const pathsStore = usePathsStore()
|
||||
const filesStore = useFilesStore()
|
||||
const selectionStore = useSelectionStore()
|
||||
return {
|
||||
filesStore,
|
||||
pathsStore,
|
||||
selectionStore,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
|
@ -134,10 +149,10 @@ export default {
|
|||
*/
|
||||
currentFolder() {
|
||||
if (this.dir === '/') {
|
||||
return this.$store.getters['files/getRoot'](this.currentViewId)
|
||||
return this.filesStore.getRoot(this.currentViewId)
|
||||
}
|
||||
const fileId = this.$store.getters['paths/getPath'](this.currentViewId, this.dir)
|
||||
return this.$store.getters['files/getNode'](fileId)
|
||||
const fileId = this.pathsStore.getPath(this.currentViewId, this.dir)
|
||||
return this.filesStore.getNode(fileId)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -182,14 +197,14 @@ export default {
|
|||
}
|
||||
|
||||
logger.debug('View changed', { newView, oldView })
|
||||
this.$store.dispatch('selection/reset')
|
||||
this.selectionStore.reset()
|
||||
this.fetchContent()
|
||||
},
|
||||
|
||||
dir(newDir, oldDir) {
|
||||
logger.debug('Directory changed', { newDir, oldDir })
|
||||
// TODO: preserve selection on browsing?
|
||||
this.$store.dispatch('selection/reset')
|
||||
this.selectionStore.reset()
|
||||
this.fetchContent()
|
||||
},
|
||||
|
||||
|
@ -226,7 +241,7 @@ export default {
|
|||
logger.debug('Fetched contents', { dir, folder, contents })
|
||||
|
||||
// Update store
|
||||
this.$store.dispatch('files/addNodes', contents)
|
||||
this.filesStore.updateNodes(contents)
|
||||
|
||||
// Define current directory children
|
||||
folder.children = contents.map(node => node.attributes.fileid)
|
||||
|
@ -234,12 +249,12 @@ export default {
|
|||
// If we're in the root dir, define the root
|
||||
if (dir === '/') {
|
||||
console.debug('files', 'Setting root', { service: currentView.id, folder })
|
||||
this.$store.dispatch('files/setRoot', { service: currentView.id, root: folder })
|
||||
this.filesStore.setRoot({ service: currentView.id, root: folder })
|
||||
} else
|
||||
// Otherwise, add the folder to the store
|
||||
if (folder.attributes.fileid) {
|
||||
this.$store.dispatch('files/addNodes', [folder])
|
||||
this.$store.dispatch('paths/addPath', { service: currentView.id, fileid: folder.attributes.fileid, path: dir })
|
||||
this.filesStore.updateNodes([folder])
|
||||
this.pathsStore.addPath({ service: currentView.id, fileid: folder.attributes.fileid, path: dir })
|
||||
} else {
|
||||
// If we're here, the view API messed up
|
||||
logger.error('Invalid root folder returned', { dir, folder, currentView })
|
||||
|
@ -248,7 +263,7 @@ export default {
|
|||
// Update paths store
|
||||
const folders = contents.filter(node => node.type === 'folder')
|
||||
folders.forEach(node => {
|
||||
this.$store.dispatch('paths/addPath', { service: currentView.id, fileid: node.attributes.fileid, path: join(dir, node.basename) })
|
||||
this.pathsStore.addPath({ service: currentView.id, fileid: node.attributes.fileid, path: join(dir, node.basename) })
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error while fetching content', { error })
|
||||
|
@ -265,12 +280,12 @@ export default {
|
|||
* @return {Folder|File}
|
||||
*/
|
||||
getNode(fileId) {
|
||||
return this.$store.getters['files/getNode'](fileId)
|
||||
return this.filesStore.getNode(fileId)
|
||||
},
|
||||
|
||||
t: translate,
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
"p-limit": "^4.0.0",
|
||||
"p-queue": "^7.3.0",
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^2.0.30",
|
||||
"query-string": "^7.1.1",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"select2": "3.5.1",
|
||||
|
|
Загрузка…
Ссылка в новой задаче