Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
This commit is contained in:
John Molakvoæ 2023-02-04 21:13:06 +01:00
Родитель 29a7f7f6ef
Коммит 03c32774b0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 60C25B8C072916CF
12 изменённых файлов: 176 добавлений и 180 удалений

Просмотреть файл

@ -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",