mirror of
https://github.com/LibreSign/libresign.git
synced 2025-12-17 21:12:16 +01:00
feat: rewrite file list
Signed-off-by: Vitor Mattos <vitor@php.rio>
This commit is contained in:
parent
4e359acd9c
commit
70d463d715
13 changed files with 752 additions and 1 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -12,6 +12,8 @@
|
|||
"@fontsource/dancing-script": "^5.1.0",
|
||||
"@libresign/vue-pdf-editor": "^1.3.7",
|
||||
"@marionebl/option": "^1.0.8",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@mdi/svg": "^7.4.47",
|
||||
"@nextcloud/auth": "^2.4.0",
|
||||
"@nextcloud/axios": "^2.5.1",
|
||||
"@nextcloud/dialogs": "^6.0.1",
|
||||
|
|
@ -2783,6 +2785,12 @@
|
|||
"integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@mdi/svg": {
|
||||
"version": "7.4.47",
|
||||
"resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-7.4.47.tgz",
|
||||
"integrity": "sha512-WQ2gDll12T9WD34fdRFgQVgO8bag3gavrAgJ0frN4phlwdJARpE6gO1YvLEMJR0KKgoc+/Ea/A0Pp11I00xBvw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@nextcloud/auth": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-2.4.0.tgz",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@
|
|||
"@fontsource/dancing-script": "^5.1.0",
|
||||
"@libresign/vue-pdf-editor": "^1.3.7",
|
||||
"@marionebl/option": "^1.0.8",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@mdi/svg": "^7.4.47",
|
||||
"@nextcloud/auth": "^2.4.0",
|
||||
"@nextcloud/axios": "^2.5.1",
|
||||
"@nextcloud/dialogs": "^6.0.1",
|
||||
|
|
|
|||
11
src/helpers/logger.js
Normal file
11
src/helpers/logger.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { getLoggerBuilder } from '@nextcloud/logger'
|
||||
|
||||
export default getLoggerBuilder()
|
||||
.setApp('libresign')
|
||||
.detectUser()
|
||||
.build()
|
||||
|
|
@ -141,6 +141,11 @@ const router = new Router({
|
|||
name: 'timeline',
|
||||
component: () => import('../views/Timeline/Timeline.vue'),
|
||||
},
|
||||
{
|
||||
path: '/f/filelist/sign',
|
||||
name: 'fileslist',
|
||||
component: () => import('../views/FilesList/FilesList.vue'),
|
||||
},
|
||||
{
|
||||
path: '/f/request',
|
||||
name: 'requestFiles',
|
||||
|
|
|
|||
27
src/store/filters.js
Normal file
27
src/store/filters.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import logger from '../helpers/logger.js'
|
||||
|
||||
export const useFiltersStore = defineStore('filter', {
|
||||
state: () => ({
|
||||
chips: {},
|
||||
}),
|
||||
|
||||
getters: {
|
||||
activeChips(state) {
|
||||
return Object.values(state.chips).flat()
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
onFilterUpdateChips(event) {
|
||||
this.chips = { ...this.chips, [event.id]: [...event.detail] }
|
||||
|
||||
logger.debug('File list filter chips updated', { chips: event.detail })
|
||||
},
|
||||
},
|
||||
})
|
||||
58
src/views/FilesList/FileListFilter/FileListFilter.vue
Normal file
58
src/views/FilesList/FileListFilter/FileListFilter.vue
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<template>
|
||||
<NcActions force-menu
|
||||
:type="isActive ? 'secondary' : 'tertiary'"
|
||||
:menu-name="filterName">
|
||||
<template #icon>
|
||||
<slot name="icon" />
|
||||
</template>
|
||||
<slot />
|
||||
|
||||
<template v-if="isActive">
|
||||
<NcActionSeparator />
|
||||
<NcActionButton class="files-list-filter__clear-button"
|
||||
close-after-click
|
||||
@click="$emit('reset-filter')">
|
||||
{{ t('files', 'Clear filter') }}
|
||||
</NcActionButton>
|
||||
</template>
|
||||
</NcActions>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
||||
import NcActionSeparator from '@nextcloud/vue/dist/Components/NcActionSeparator.js'
|
||||
|
||||
export default {
|
||||
name: 'FileListFilter',
|
||||
components: {
|
||||
NcActions,
|
||||
NcActionButton,
|
||||
NcActionSeparator,
|
||||
},
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
filterName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.files-list-filter__clear-button :deep(.action-button__text) {
|
||||
color: var(--color-error-text);
|
||||
}
|
||||
|
||||
:deep(.button-vue) {
|
||||
font-weight: normal !important;
|
||||
|
||||
* {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
128
src/views/FilesList/FileListFilter/FileListFilterModified.vue
Normal file
128
src/views/FilesList/FileListFilter/FileListFilterModified.vue
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
<template>
|
||||
<FileListFilter :is-active="isActive"
|
||||
:filter-name="t('libresign', 'Modified')"
|
||||
@reset-filter="resetFilter">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiCalendarRange" />
|
||||
</template>
|
||||
<NcActionButton v-for="preset of timePresets"
|
||||
:key="preset.id"
|
||||
type="radio"
|
||||
close-after-click
|
||||
:model-value.sync="selectedOption"
|
||||
:value="preset.id">
|
||||
{{ preset.label }}
|
||||
</NcActionButton>
|
||||
</FileListFilter>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mdiCalendarRange } from '@mdi/js'
|
||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
|
||||
import calendarSvg from '@mdi/svg/svg/calendar.svg?raw'
|
||||
import FileListFilter from './FileListFilter.vue'
|
||||
import { useFiltersStore } from '../../../store/filters.js'
|
||||
|
||||
const startOfToday = () => (new Date()).setHours(0, 0, 0, 0)
|
||||
|
||||
export default {
|
||||
name: 'FileListFilterModified',
|
||||
components: {
|
||||
FileListFilter,
|
||||
NcActionButton,
|
||||
NcIconSvgWrapper,
|
||||
},
|
||||
setup() {
|
||||
const filtersStore = useFiltersStore()
|
||||
return {
|
||||
// icons used in template
|
||||
mdiCalendarRange,
|
||||
filtersStore,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedOption: null,
|
||||
timeRangeEnd: null,
|
||||
timeRangeStart: null,
|
||||
timePresets: [
|
||||
{
|
||||
id: 'today',
|
||||
label: t('libresign', 'Today'),
|
||||
filter: (time) => time > startOfToday(),
|
||||
},
|
||||
{
|
||||
id: 'last-7',
|
||||
label: t('libresign', 'Last 7 days'),
|
||||
filter: (time) => time > (startOfToday() - (7 * 24 * 60 * 60 * 1000)),
|
||||
},
|
||||
{
|
||||
id: 'last-30',
|
||||
label: t('libresign', 'Last 30 days'),
|
||||
filter: (time) => time > (startOfToday() - (30 * 24 * 60 * 60 * 1000)),
|
||||
},
|
||||
{
|
||||
id: 'this-year',
|
||||
label: t('libresign', 'This year ({year})', { year: (new Date()).getFullYear() }),
|
||||
filter: (time) => time > (new Date(startOfToday())).setMonth(0, 1),
|
||||
},
|
||||
{
|
||||
id: 'last-year',
|
||||
label: t('libresign', 'Last year ({year})', { year: (new Date()).getFullYear() - 1 }),
|
||||
filter: (time) => (time > (new Date(startOfToday())).setFullYear((new Date()).getFullYear() - 1, 0, 1)) && (time < (new Date(startOfToday())).setMonth(0, 1)),
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isActive() {
|
||||
return this.selectedOption !== null
|
||||
},
|
||||
currentPreset() {
|
||||
return this.timePresets.find(({ id }) => id === this.selectedOption) ?? null
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedOption() {
|
||||
if (this.selectedOption === null) {
|
||||
this.selectedOption = null
|
||||
this.setPreset()
|
||||
} else {
|
||||
this.setPreset(this.currentPreset)
|
||||
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setPreset(preset) {
|
||||
const chips = []
|
||||
if (preset) {
|
||||
chips.push({
|
||||
icon: calendarSvg,
|
||||
text: preset.label,
|
||||
onclick: () => this.setPreset(),
|
||||
})
|
||||
} else {
|
||||
this.resetFilter()
|
||||
}
|
||||
this.filtersStore.onFilterUpdateChips({ detail: chips, id: 'modified' })
|
||||
},
|
||||
resetFilter() {
|
||||
if (this.selectedOption !== null) {
|
||||
this.selectedOption = null
|
||||
this.timeRangeEnd = null
|
||||
this.timeRangeStart = null
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.files-list-filter-time {
|
||||
&__clear-button :deep(.action-button__text) {
|
||||
color: var(--color-error-text);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
136
src/views/FilesList/FileListFilter/FileListFilterStatus.vue
Normal file
136
src/views/FilesList/FileListFilter/FileListFilterStatus.vue
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<FileListFilter class="file-list-filter-status"
|
||||
:is-active="isActive"
|
||||
:filter-name="t('libresign', 'Status')"
|
||||
@reset-filter="resetFilter">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiListStatus" />
|
||||
</template>
|
||||
<NcActionButton v-for="status of statusPresets"
|
||||
:key="status.id"
|
||||
type="checkbox"
|
||||
:model-value="selectedOptions.includes(status)"
|
||||
@click="toggleOption(status)">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :svg="status.icon" />
|
||||
</template>
|
||||
{{ status.label }}
|
||||
</NcActionButton>
|
||||
</FileListFilter>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mdiListStatus } from '@mdi/js'
|
||||
|
||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
|
||||
import svgFile from '@mdi/svg/svg/file.svg?raw'
|
||||
import svgSignature from '@mdi/svg/svg/signature.svg?raw'
|
||||
import svgFractionOneHalf from '@mdi/svg/svg/fraction-one-half.svg?raw'
|
||||
import svgSignatureFreehand from '@mdi/svg/svg/signature-freehand.svg?raw'
|
||||
import svgDelete from '@mdi/svg/svg/delete.svg?raw'
|
||||
import FileListFilter from './FileListFilter.vue'
|
||||
import { useFiltersStore } from '../../../store/filters.js'
|
||||
|
||||
const colorize = (svg, color) => {
|
||||
return svg.replace('<path ', `<path fill="${color}" `)
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'FileListFilterStatus',
|
||||
components: {
|
||||
FileListFilter,
|
||||
NcActionButton,
|
||||
NcIconSvgWrapper,
|
||||
},
|
||||
setup() {
|
||||
const filtersStore = useFiltersStore()
|
||||
return {
|
||||
mdiListStatus,
|
||||
filtersStore,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedOptions: [],
|
||||
statusPresets: [
|
||||
{
|
||||
id: 0,
|
||||
icon: colorize(svgFile, '#E0E0E0'),
|
||||
label: t('libresign', 'draft'),
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
icon: colorize(svgSignature, '#B2E0B2'),
|
||||
label: t('libresign', 'available for signature'),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
icon: colorize(svgFractionOneHalf, '#F0E68C'),
|
||||
label: t('libresign', 'partially signed'),
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
icon: colorize(svgSignatureFreehand, '#A0C4FF'),
|
||||
label: t('libresign', 'signed'),
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
icon: colorize(svgDelete, '#FFB2B2'),
|
||||
label: t('libresign', 'deleted'),
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isActive() {
|
||||
return this.selectedOptions.length > 0
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedOptions(newValue, oldValue) {
|
||||
if (newValue.length === 0) {
|
||||
this.setPreset()
|
||||
} else {
|
||||
this.setPreset(newValue)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setPreset(presets) {
|
||||
const chips = []
|
||||
if (presets && presets.length > 0) {
|
||||
for (const preset of presets) {
|
||||
chips.push({
|
||||
icon: preset.icon,
|
||||
text: preset.label,
|
||||
onclick: () => this.setPreset(presets.filter(({ id }) => id !== preset.id)),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.resetFilter()
|
||||
}
|
||||
this.filtersStore.onFilterUpdateChips({ detail: chips, id: 'status' })
|
||||
},
|
||||
resetFilter() {
|
||||
if (this.selectedOptions.length > 0) {
|
||||
this.selectedOptions = []
|
||||
}
|
||||
},
|
||||
toggleOption(option) {
|
||||
const idx = this.selectedOptions.indexOf(option)
|
||||
if (idx !== -1) {
|
||||
this.selectedOptions.splice(idx, 1)
|
||||
} else {
|
||||
this.selectedOptions.push(option)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.file-list-filter-status {
|
||||
max-width: 220px;
|
||||
}
|
||||
</style>
|
||||
74
src/views/FilesList/FileListFilters.vue
Normal file
74
src/views/FilesList/FileListFilters.vue
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<div class="file-list-filters">
|
||||
<div class="file-list-filters__filter">
|
||||
<div class="file-list-filters__filter">
|
||||
<FileListFilterModified />
|
||||
<FileListFilterStatus />
|
||||
</div>
|
||||
</div>
|
||||
<ul v-if="filtersStore.activeChips.length > 0" class="file-list-filters__active" :aria-label="t('libresign', 'Active filters')">
|
||||
<li v-for="(chip, index) of filtersStore.activeChips" :key="index">
|
||||
<NcChip :aria-label-close="t('libresign', 'Remove filter')"
|
||||
:icon-svg="chip.icon"
|
||||
:text="chip.text"
|
||||
@close="chip.onclick">
|
||||
<template v-if="chip.user" #icon>
|
||||
<NcAvatar disable-menu
|
||||
:show-user-status="false"
|
||||
:size="24"
|
||||
:user="chip.user" />
|
||||
</template>
|
||||
</NcChip>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NcChip from '@nextcloud/vue/dist/Components/NcChip.js'
|
||||
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
|
||||
import FileListFilterModified from './FileListFilter/FileListFilterModified.vue'
|
||||
import FileListFilterStatus from './FileListFilter/FileListFilterStatus.vue'
|
||||
import { useFiltersStore } from '../../store/filters.js'
|
||||
|
||||
export default {
|
||||
name: 'FileListFilters',
|
||||
components: {
|
||||
NcChip,
|
||||
NcAvatar,
|
||||
FileListFilterModified,
|
||||
FileListFilterStatus,
|
||||
},
|
||||
setup() {
|
||||
const filtersStore = useFiltersStore()
|
||||
return { filtersStore }
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.file-list-filters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--default-grid-baseline);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&__filter {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: start;
|
||||
gap: calc(var(--default-grid-baseline, 4px) * 2);
|
||||
|
||||
> * {
|
||||
flex: 0 1 fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
&__active {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: calc(var(--default-grid-baseline, 4px) * 2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
232
src/views/FilesList/FilesList.vue
Normal file
232
src/views/FilesList/FilesList.vue
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
<template>
|
||||
<NcAppContent :page-heading="t('libresign', 'Files')">
|
||||
<div class="files-list__header">
|
||||
<NcBreadcrumbs class="files-list__breadcrumbs">
|
||||
<NcBreadcrumb :name="t('libresign', 'Files')"
|
||||
:title="t('libresign', 'Files')"
|
||||
:exact="true"
|
||||
:force-icon-text="true"
|
||||
:to="{ name: 'fileslist' }"
|
||||
:aria-description="t('libresign', 'Files')"
|
||||
:disable-drop="true"
|
||||
@click.native="refresh()">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :size="20"
|
||||
:svg="viewIcon" />
|
||||
</template>
|
||||
</NcBreadcrumb>
|
||||
<template #actions>
|
||||
<NcActions :menu-name="t('libresign', 'Request')">
|
||||
<template #icon>
|
||||
<PlusIcon :size="20" />
|
||||
</template>
|
||||
<NcActionButton>
|
||||
<template #icon>
|
||||
<LinkIcon :size="20" />
|
||||
</template>
|
||||
{{ t('libresign', 'Upload from URL') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton>
|
||||
<template #icon>
|
||||
<FolderIcon :size="20" />
|
||||
</template>
|
||||
{{ t('libresign', 'Choose from Files') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton>
|
||||
<template #icon>
|
||||
<NcLoadingIcon v-if="isUploading" :size="20" />
|
||||
<UploadIcon v-else :size="20" />
|
||||
</template>
|
||||
{{ t('libresign', 'Upload') }}
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</template>
|
||||
</NcBreadcrumbs>
|
||||
|
||||
<NcLoadingIcon v-if="isRefreshing" class="files-list__refresh-icon" />
|
||||
|
||||
<NcButton :aria-label="gridViewButtonLabel"
|
||||
:title="gridViewButtonLabel"
|
||||
class="files-list__header-grid-button"
|
||||
type="tertiary"
|
||||
@click="toggleGridView">
|
||||
<template #icon>
|
||||
<ListViewIcon v-if="userConfig.grid_view" />
|
||||
<ViewGridIcon v-else />
|
||||
</template>
|
||||
</NcButton>
|
||||
</div>
|
||||
<NcLoadingIcon v-if="loading && !isRefreshing"
|
||||
class="files-list__loading-icon"
|
||||
:size="38"
|
||||
:name="t('libresign', 'Loading …')" />
|
||||
<NcEmptyContent v-else-if="!loading && isEmptyDir"
|
||||
:name="t('libresign', 'There are no documents')"
|
||||
:description="t('libresign', 'Choose the file to request signatures.')">
|
||||
<template #icon>
|
||||
<FolderIcon />
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
<FilesListVirtual v-else
|
||||
:nodes="dirContentsSorted" />
|
||||
</NcAppContent>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js'
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
||||
|
||||
import HomeSvg from '@mdi/svg/svg/home.svg?raw'
|
||||
import ListViewIcon from 'vue-material-design-icons/FormatListBulletedSquare.vue'
|
||||
import ViewGridIcon from 'vue-material-design-icons/ViewGrid.vue'
|
||||
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
|
||||
import FolderIcon from 'vue-material-design-icons/Folder.vue'
|
||||
import UploadIcon from 'vue-material-design-icons/Upload.vue'
|
||||
import LinkIcon from 'vue-material-design-icons/Link.vue'
|
||||
import NcBreadcrumb from '@nextcloud/vue/dist/Components/NcBreadcrumb.js'
|
||||
import NcBreadcrumbs from '@nextcloud/vue/dist/Components/NcBreadcrumbs.js'
|
||||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
|
||||
import FilesListVirtual from './FilesListVirtual.vue'
|
||||
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
|
||||
import { useFilesStore } from '../../store/files.js'
|
||||
|
||||
export default {
|
||||
name: 'FilesList',
|
||||
components: {
|
||||
NcAppContent,
|
||||
NcButton,
|
||||
PlusIcon,
|
||||
ListViewIcon,
|
||||
ViewGridIcon,
|
||||
NcLoadingIcon,
|
||||
FolderIcon,
|
||||
UploadIcon,
|
||||
LinkIcon,
|
||||
NcBreadcrumb,
|
||||
NcBreadcrumbs,
|
||||
NcActions,
|
||||
NcActionButton,
|
||||
NcIconSvgWrapper,
|
||||
FilesListVirtual,
|
||||
NcEmptyContent,
|
||||
},
|
||||
setup() {
|
||||
const filesStore = useFilesStore()
|
||||
return { filesStore }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isUploading: false,
|
||||
loading: false,
|
||||
userConfig: {
|
||||
grid_view: false,
|
||||
},
|
||||
dirContentsFiltered: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
viewIcon() {
|
||||
return HomeSvg
|
||||
},
|
||||
gridViewButtonLabel() {
|
||||
return this.userConfig.grid_view
|
||||
? t('libresign', 'Switch to list view')
|
||||
: t('libresign', 'Switch to grid view')
|
||||
},
|
||||
dirContentsSorted() {
|
||||
if (!this.isEmptyDir) {
|
||||
return []
|
||||
}
|
||||
return this.dirContentsFiltered
|
||||
},
|
||||
isEmptyDir() {
|
||||
return this.filesStore.files.length === 0
|
||||
},
|
||||
isRefreshing() {
|
||||
return !this.isEmptyDir
|
||||
&& this.loading
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
await this.filesStore.getAllFiles()
|
||||
},
|
||||
methods: {
|
||||
refresh() {
|
||||
console.log('Need to implement refresh')
|
||||
},
|
||||
toggleGridView() {
|
||||
this.userConfig.grid_view = !this.userConfig.grid_view
|
||||
console.log('Need to implement toggle')
|
||||
// this.userConfigStore.update('grid_view', !this.userConfig.grid_view)
|
||||
},
|
||||
filterDirContent() {
|
||||
const nodes = this.filesStore.files
|
||||
console.log('Implement filter here')
|
||||
this.dirContentsFiltered = nodes
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-content {
|
||||
// Virtual list needs to be full height and is scrollable
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
max-height: 100%;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.files-list__breadcrumbs {
|
||||
// Take as much space as possible
|
||||
flex: 1 1 100% !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-block: 0;
|
||||
margin-inline: 10px;
|
||||
min-width: 0;
|
||||
|
||||
:deep() {
|
||||
a {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--with-progress {
|
||||
flex-direction: column !important;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
}
|
||||
|
||||
.files-list {
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// Do not grow or shrink (vertically)
|
||||
flex: 0 0;
|
||||
max-width: 100%;
|
||||
// Align with the navigation toggle icon
|
||||
margin-block: var(--app-navigation-padding, 4px);
|
||||
margin-inline: calc(var(--default-clickable-area, 44px) + 2 * var(--app-navigation-padding, 4px)) var(--app-navigation-padding, 4px);
|
||||
|
||||
>* {
|
||||
// Do not grow or shrink (horizontally)
|
||||
flex: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__refresh-icon {
|
||||
flex: 0 0 var(--default-clickable-area);
|
||||
width: var(--default-clickable-area);
|
||||
height: var(--default-clickable-area);
|
||||
}
|
||||
|
||||
&__loading-icon {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
57
src/views/FilesList/FilesListVirtual.vue
Normal file
57
src/views/FilesList/FilesListVirtual.vue
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<VirtualList>
|
||||
<template #filters>
|
||||
<FileListFilters />
|
||||
</template>
|
||||
</VirtualList>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VirtualList from './VirtualList.vue'
|
||||
import FileListFilters from './FileListFilters.vue'
|
||||
export default {
|
||||
name: 'FilesListVirtual',
|
||||
components: {
|
||||
VirtualList,
|
||||
FileListFilters,
|
||||
},
|
||||
props: {
|
||||
nodes: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.files-list {
|
||||
--row-height: 55px;
|
||||
--cell-margin: 14px;
|
||||
|
||||
--checkbox-padding: calc((var(--row-height) - var(--checkbox-size)) / 2);
|
||||
--checkbox-size: 24px;
|
||||
--clickable-area: var(--default-clickable-area);
|
||||
--icon-preview-size: 32px;
|
||||
|
||||
--fixed-block-start-position: var(--default-clickable-area);
|
||||
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
will-change: scroll-position;
|
||||
& :deep() {
|
||||
.files-list__filters {
|
||||
// Pinned on top when scrolling above table header
|
||||
position: sticky;
|
||||
top: 0;
|
||||
// ensure there is a background to hide the file list on scroll
|
||||
background-color: var(--color-main-background);
|
||||
z-index: 10;
|
||||
// fixed the size
|
||||
padding-inline: var(--row-height) var(--default-grid-baseline, 4px);
|
||||
height: var(--fixed-block-start-position);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
13
src/views/FilesList/VirtualList.vue
Normal file
13
src/views/FilesList/VirtualList.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<div class="files-list">
|
||||
<div class="files-list__filters">
|
||||
<slot name="filters" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'VirtualList',
|
||||
}
|
||||
</script>
|
||||
|
|
@ -12,4 +12,4 @@
|
|||
"vueCompilerOptions": {
|
||||
"target": 2.7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue