mirror of https://github.com/requarks/wiki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1150 lines
30 KiB
1150 lines
30 KiB
<template lang="pug">
|
|
q-layout.fileman(view='hHh lpR lFr', container)
|
|
q-header.card-header
|
|
q-toolbar(dark)
|
|
q-icon(name='img:/_assets/icons/fluent-folder.svg', left, size='md')
|
|
span {{t(`fileman.title`)}}
|
|
q-toolbar(dark)
|
|
q-btn.q-mr-sm.acrylic-btn(
|
|
flat
|
|
color='white'
|
|
label='EN'
|
|
style='height: 40px;'
|
|
)
|
|
q-input(
|
|
dark
|
|
v-model='state.search'
|
|
standout='bg-white text-dark'
|
|
dense
|
|
ref='searchField'
|
|
style='width: 100%;'
|
|
label='Search folder...'
|
|
:debounce='500'
|
|
)
|
|
template(v-slot:prepend)
|
|
q-icon(name='las la-search')
|
|
template(v-slot:append)
|
|
q-icon.cursor-pointer(
|
|
name='las la-times'
|
|
@click='state.search=``'
|
|
v-if='state.search.length > 0'
|
|
:color='$q.dark.isActive ? `blue` : `grey-4`'
|
|
)
|
|
q-toolbar(dark)
|
|
q-space
|
|
q-btn(
|
|
flat
|
|
dense
|
|
no-caps
|
|
color='red-3'
|
|
:aria-label='t(`common.actions.close`)'
|
|
icon='las la-times'
|
|
@click='close'
|
|
)
|
|
q-tooltip(anchor='bottom middle', self='top middle') {{t(`common.actions.close`)}}
|
|
q-drawer.fileman-left(:model-value='true', :width='350')
|
|
q-scroll-area(
|
|
:thumb-style='thumbStyle'
|
|
:bar-style='barStyle'
|
|
style='height: 100%;'
|
|
)
|
|
.q-px-md.q-pb-sm
|
|
tree(
|
|
ref='treeComp'
|
|
:nodes='state.treeNodes'
|
|
:roots='state.treeRoots'
|
|
v-model:selected='state.currentFolderId'
|
|
@lazy-load='treeLazyLoad'
|
|
:use-lazy-load='true'
|
|
@context-action='treeContextAction'
|
|
:display-mode='state.displayMode'
|
|
)
|
|
q-drawer.fileman-right(:model-value='$q.screen.gt.md', :width='350', side='right')
|
|
q-scroll-area(
|
|
:thumb-style='thumbStyle'
|
|
:bar-style='barStyle'
|
|
style='height: 100%;'
|
|
)
|
|
.q-pa-md
|
|
template(v-if='currentFileDetails')
|
|
q-img.rounded-borders.q-mb-md(
|
|
:src='currentFileDetails.thumbnail'
|
|
width='100%'
|
|
fit='cover'
|
|
:ratio='16/10'
|
|
no-spinner
|
|
)
|
|
.fileman-details-row(
|
|
v-for='item of currentFileDetails.items'
|
|
)
|
|
label {{item.label}}
|
|
span {{item.value}}
|
|
q-page-container
|
|
q-page.fileman-center.column
|
|
//- TOOLBAR -----------------------------------------------------
|
|
q-toolbar.fileman-toolbar
|
|
template(v-if='state.isUploading')
|
|
.fileman-progressbar
|
|
div(:style='`width: ` + state.uploadPercentage + `%`') {{state.uploadPercentage}}%
|
|
q-btn.acrylic-btn.q-ml-sm(
|
|
flat
|
|
dense
|
|
no-caps
|
|
color='negative'
|
|
:aria-label='t(`common.actions.cancel`)'
|
|
icon='las la-square'
|
|
@click='uploadCancel'
|
|
v-if='state.uploadPercentage < 100'
|
|
)
|
|
template(v-else)
|
|
q-space
|
|
q-btn.q-mr-sm(
|
|
flat
|
|
dense
|
|
no-caps
|
|
color='grey'
|
|
:aria-label='t(`fileman.viewOptions`)'
|
|
icon='las la-th-list'
|
|
@click=''
|
|
)
|
|
q-tooltip(anchor='bottom middle', self='top middle') {{t(`fileman.viewOptions`)}}
|
|
q-menu(
|
|
transition-show='jump-down'
|
|
transition-hide='jump-up'
|
|
anchor='bottom right'
|
|
self='top right'
|
|
)
|
|
q-card.q-pa-sm
|
|
.text-center
|
|
small.text-grey {{t(`fileman.viewOptions`)}}
|
|
q-list(dense)
|
|
q-separator.q-my-sm
|
|
q-item(clickable)
|
|
q-item-section(side)
|
|
q-icon(name='las la-list', color='grey', size='xs')
|
|
q-item-section.q-pr-sm Browse using...
|
|
q-item-section(side)
|
|
q-icon(name='las la-angle-right', color='grey', size='xs')
|
|
q-menu(
|
|
anchor='top end'
|
|
self='top start'
|
|
)
|
|
q-list.q-pa-sm(dense)
|
|
q-item(clickable, @click='state.displayMode = `path`')
|
|
q-item-section(side)
|
|
q-icon(
|
|
:name='state.displayMode === `path` ? `las la-check-circle` : `las la-circle`'
|
|
:color='state.displayMode === `path` ? `positive` : `grey`'
|
|
size='xs'
|
|
)
|
|
q-item-section.q-pr-sm Browse Using Paths
|
|
q-item(clickable, @click='state.displayMode = `title`')
|
|
q-item-section(side)
|
|
q-icon(
|
|
:name='state.displayMode === `title` ? `las la-check-circle` : `las la-circle`'
|
|
:color='state.displayMode === `title` ? `positive` : `grey`'
|
|
size='xs'
|
|
)
|
|
q-item-section.q-pr-sm Browse Using Titles
|
|
q-item(clickable, @click='state.isCompact = !state.isCompact')
|
|
q-item-section(side)
|
|
q-icon(
|
|
:name='state.isCompact ? `las la-check-square` : `las la-stop`'
|
|
:color='state.isCompact ? `positive` : `grey`'
|
|
size='xs'
|
|
)
|
|
q-item-section.q-pr-sm Compact List
|
|
q-item(clickable, @click='state.shouldShowFolders = !state.shouldShowFolders')
|
|
q-item-section(side)
|
|
q-icon(
|
|
:name='state.shouldShowFolders ? `las la-check-square` : `las la-stop`'
|
|
:color='state.shouldShowFolders ? `positive` : `grey`'
|
|
size='xs'
|
|
)
|
|
q-item-section.q-pr-sm Show Folders
|
|
q-btn.q-mr-sm(
|
|
flat
|
|
dense
|
|
no-caps
|
|
color='grey'
|
|
:aria-label='t(`common.actions.refresh`)'
|
|
icon='las la-redo-alt'
|
|
@click='reloadFolder(state.currentFolderId)'
|
|
)
|
|
q-tooltip(anchor='bottom middle', self='top middle') {{t(`common.actions.refresh`)}}
|
|
q-separator.q-mr-sm(inset, vertical)
|
|
q-btn.q-mr-sm(
|
|
flat
|
|
dense
|
|
no-caps
|
|
color='blue'
|
|
:label='t(`common.actions.new`)'
|
|
:aria-label='t(`common.actions.new`)'
|
|
icon='las la-plus-circle'
|
|
@click=''
|
|
)
|
|
new-menu(
|
|
:hide-asset-btn='true'
|
|
:show-new-folder='true'
|
|
@new-folder='() => newFolder(state.currentFolderId)'
|
|
)
|
|
q-btn(
|
|
flat
|
|
dense
|
|
no-caps
|
|
color='positive'
|
|
:label='t(`common.actions.upload`)'
|
|
:aria-label='t(`common.actions.upload`)'
|
|
icon='las la-cloud-upload-alt'
|
|
@click='uploadFile'
|
|
)
|
|
|
|
.row(style='flex: 1 1 100%;')
|
|
.col
|
|
q-scroll-area(
|
|
:thumb-style='thumbStyle'
|
|
:bar-style='barStyle'
|
|
style='height: 100%;'
|
|
)
|
|
.fileman-loadinglist(v-if='state.fileListLoading')
|
|
q-spinner.q-mr-sm(color='primary', size='64px', :thickness='1')
|
|
span.text-primary Fetching folder contents...
|
|
.fileman-emptylist(v-else-if='files.length < 1')
|
|
img(src='/_assets/icons/carbon-copy-empty-box.svg')
|
|
span This folder is empty.
|
|
q-list.fileman-filelist(
|
|
v-else
|
|
:class='state.isCompact && `is-compact`'
|
|
)
|
|
q-item(
|
|
v-for='item of files'
|
|
:key='item.id'
|
|
clickable
|
|
active-class='active'
|
|
:active='item.id === state.currentFileId'
|
|
@click.native='selectItem(item)'
|
|
@dblclick.native='openItem(item)'
|
|
)
|
|
q-item-section.fileman-filelist-icon(avatar)
|
|
q-icon(:name='item.icon', :size='state.isCompact ? `md` : `xl`')
|
|
q-item-section.fileman-filelist-label
|
|
q-item-label {{usePathTitle ? item.fileName : item.title}}
|
|
q-item-label(caption, v-if='!state.isCompact') {{item.caption}}
|
|
q-item-section.fileman-filelist-side(side, v-if='item.side')
|
|
.text-caption {{item.side}}
|
|
//- RIGHT-CLICK MENU
|
|
q-menu(
|
|
touch-position
|
|
context-menu
|
|
auto-close
|
|
transition-show='jump-down'
|
|
transition-hide='jump-up'
|
|
)
|
|
q-card.q-pa-sm
|
|
q-list(dense, style='min-width: 150px;')
|
|
q-item(clickable, v-if='item.type === `page`')
|
|
q-item-section(side)
|
|
q-icon(name='las la-edit', color='orange')
|
|
q-item-section Edit
|
|
q-item(clickable, v-if='item.type !== `folder`', @click='openItem(item)')
|
|
q-item-section(side)
|
|
q-icon(name='las la-eye', color='primary')
|
|
q-item-section View
|
|
template(v-if='item.type === `asset` && item.imageEdit')
|
|
q-item(clickable)
|
|
q-item-section(side)
|
|
q-icon(name='las la-edit', color='orange')
|
|
q-item-section Edit Image...
|
|
q-item(clickable)
|
|
q-item-section(side)
|
|
q-icon(name='las la-crop', color='orange')
|
|
q-item-section Resize Image...
|
|
q-item(clickable, v-if='item.type !== `folder`', @click='copyItemURL(item)')
|
|
q-item-section(side)
|
|
q-icon(name='las la-clipboard', color='primary')
|
|
q-item-section Copy URL
|
|
q-item(clickable, v-if='item.type !== `folder`', @click='')
|
|
q-item-section(side)
|
|
q-icon(name='las la-download', color='primary')
|
|
q-item-section Download
|
|
q-item(clickable)
|
|
q-item-section(side)
|
|
q-icon(name='las la-copy', color='teal')
|
|
q-item-section Duplicate...
|
|
q-item(clickable, @click='renameItem(item)')
|
|
q-item-section(side)
|
|
q-icon(name='las la-redo', color='teal')
|
|
q-item-section Rename...
|
|
q-item(clickable)
|
|
q-item-section(side)
|
|
q-icon(name='las la-arrow-right', color='teal')
|
|
q-item-section Move to...
|
|
q-item(clickable, @click='delItem(item)')
|
|
q-item-section(side)
|
|
q-icon(name='las la-trash-alt', color='negative')
|
|
q-item-section.text-negative Delete
|
|
q-footer
|
|
q-bar.fileman-path
|
|
small.text-caption.text-grey-7 {{folderPath}}
|
|
|
|
input(
|
|
type='file'
|
|
ref='fileIpt'
|
|
multiple
|
|
@change='uploadNewFiles'
|
|
style='display: none'
|
|
)
|
|
</template>
|
|
|
|
<script setup>
|
|
import { useI18n } from 'vue-i18n'
|
|
import { computed, defineAsyncComponent, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
|
import { filesize } from 'filesize'
|
|
import { useQuasar } from 'quasar'
|
|
import { DateTime } from 'luxon'
|
|
import { cloneDeep, find } from 'lodash-es'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import gql from 'graphql-tag'
|
|
import Fuse from 'fuse.js/dist/fuse.basic.esm'
|
|
|
|
import NewMenu from './PageNewMenu.vue'
|
|
import Tree from './TreeNav.vue'
|
|
|
|
import fileTypes from '../helpers/fileTypes'
|
|
|
|
import { usePageStore } from 'src/stores/page'
|
|
import { useSiteStore } from 'src/stores/site'
|
|
|
|
import FolderCreateDialog from 'src/components/FolderCreateDialog.vue'
|
|
import FolderDeleteDialog from 'src/components/FolderDeleteDialog.vue'
|
|
import FolderRenameDialog from 'src/components/FolderRenameDialog.vue'
|
|
|
|
// QUASAR
|
|
|
|
const $q = useQuasar()
|
|
|
|
// STORES
|
|
|
|
const pageStore = usePageStore()
|
|
const siteStore = useSiteStore()
|
|
|
|
// ROUTER
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
|
|
// I18N
|
|
|
|
const { t } = useI18n()
|
|
|
|
// DATA
|
|
|
|
const state = reactive({
|
|
loading: 0,
|
|
isFetching: false,
|
|
search: '',
|
|
currentFolderId: null,
|
|
currentFileId: null,
|
|
treeNodes: {},
|
|
treeRoots: [],
|
|
displayMode: 'title',
|
|
isCompact: false,
|
|
shouldShowFolders: true,
|
|
isUploading: false,
|
|
shouldCancelUpload: false,
|
|
uploadPercentage: 0,
|
|
fileList: [],
|
|
fileListLoading: false
|
|
})
|
|
|
|
const thumbStyle = {
|
|
right: '2px',
|
|
borderRadius: '5px',
|
|
backgroundColor: '#000',
|
|
width: '5px',
|
|
opacity: 0.15
|
|
}
|
|
const barStyle = {
|
|
backgroundColor: '#FAFAFA',
|
|
width: '9px',
|
|
opacity: 1
|
|
}
|
|
|
|
// REFS
|
|
|
|
const fileIpt = ref(null)
|
|
const treeComp = ref(null)
|
|
|
|
// COMPUTED
|
|
|
|
const folderPath = computed(() => {
|
|
if (!state.currentFolderId) {
|
|
return '/'
|
|
} else {
|
|
const folderNode = state.treeNodes[state.currentFolderId] ?? {}
|
|
return folderNode.folderPath ? `/${folderNode.folderPath}/${folderNode.fileName}/` : `/${folderNode.fileName}/`
|
|
}
|
|
})
|
|
|
|
const usePathTitle = computed(() => state.displayMode === 'path')
|
|
|
|
const filteredFiles = computed(() => {
|
|
if (state.search) {
|
|
const fuse = new Fuse(state.fileList, {
|
|
keys: [
|
|
'title',
|
|
'fileName'
|
|
]
|
|
})
|
|
return fuse.search(state.search).map(n => n.item)
|
|
} else {
|
|
return state.fileList
|
|
}
|
|
})
|
|
|
|
const files = computed(() => {
|
|
return filteredFiles.value.filter(f => {
|
|
// -> Show Folders Filter
|
|
if (f.type === 'folder' && !state.shouldShowFolders) {
|
|
return false
|
|
}
|
|
return true
|
|
}).map(f => {
|
|
switch (f.type) {
|
|
case 'folder': {
|
|
f.icon = fileTypes.folder.icon
|
|
f.caption = t('fileman.folderChildrenCount', { count: f.children }, f.children)
|
|
break
|
|
}
|
|
case 'page': {
|
|
f.icon = fileTypes.page.icon
|
|
f.caption = t(`fileman.${f.pageType}PageType`)
|
|
break
|
|
}
|
|
case 'asset': {
|
|
f.icon = fileTypes[f.fileExt]?.icon ?? ''
|
|
f.side = filesize(f.fileSize, { round: 0 })
|
|
f.imageEdit = fileTypes[f.fileExt]?.imageEdit
|
|
if (fileTypes[f.fileExt]) {
|
|
f.caption = t(`fileman.${f.fileExt}FileType`)
|
|
} else {
|
|
f.caption = t('fileman.unknownFileType', { type: f.fileExt.toUpperCase() })
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return f
|
|
})
|
|
})
|
|
|
|
const currentFileDetails = computed(() => {
|
|
if (state.currentFileId) {
|
|
const item = find(state.fileList, ['id', state.currentFileId])
|
|
if (item.type === 'folder') {
|
|
return null
|
|
}
|
|
|
|
const items = [
|
|
{
|
|
label: t('fileman.detailsTitle'),
|
|
value: item.title
|
|
}
|
|
]
|
|
let thumbnail = ''
|
|
switch (item.type) {
|
|
case 'page': {
|
|
thumbnail = '/_assets/illustrations/fileman-page.svg'
|
|
items.push({
|
|
label: t('fileman.detailsPageType'),
|
|
value: t(`fileman.${item.pageType}PageType`)
|
|
})
|
|
items.push({
|
|
label: t('fileman.detailsPageEditor'),
|
|
value: item.pageType
|
|
})
|
|
items.push({
|
|
label: t('fileman.detailsPageUpdated'),
|
|
value: DateTime.fromISO(item.updatedAt).toFormat('yyyy-MM-dd \'at\' h:mm ZZZZ')
|
|
})
|
|
items.push({
|
|
label: t('fileman.detailsPageCreated'),
|
|
value: DateTime.fromISO(item.updatedAt).toFormat('yyyy-MM-dd \'at\' h:mm ZZZZ')
|
|
})
|
|
break
|
|
}
|
|
case 'asset': {
|
|
thumbnail = `/_thumb/${item.id}.png`
|
|
items.push({
|
|
label: t('fileman.detailsAssetType'),
|
|
value: fileTypes[item.fileExt] ? t(`fileman.${item.fileExt}FileType`) : t('fileman.unknownFileType', { type: item.fileExt.toUpperCase() })
|
|
})
|
|
items.push({
|
|
label: t('fileman.detailsAssetSize'),
|
|
value: filesize(item.fileSize)
|
|
})
|
|
break
|
|
}
|
|
}
|
|
return {
|
|
thumbnail,
|
|
items
|
|
}
|
|
} else {
|
|
return null
|
|
}
|
|
})
|
|
|
|
// WATCHERS
|
|
|
|
watch(() => state.currentFolderId, async (newValue) => {
|
|
await loadTree({ parentId: newValue })
|
|
})
|
|
|
|
// METHODS
|
|
|
|
function close () {
|
|
siteStore.overlay = null
|
|
}
|
|
|
|
async function treeLazyLoad (nodeId, isCurrent, { done, fail }) {
|
|
await loadTree({ parentId: nodeId, types: isCurrent ? null : ['folder'] })
|
|
done()
|
|
}
|
|
|
|
async function loadTree ({ parentId = null, parentPath = null, types, initLoad = false }) {
|
|
if (state.isFetching) { return }
|
|
state.isFetching = true
|
|
if (!parentId) {
|
|
parentId = null
|
|
}
|
|
if (parentId === state.currentFolderId) {
|
|
state.fileListLoading = true
|
|
state.currentFileId = null
|
|
state.fileList = []
|
|
}
|
|
try {
|
|
const resp = await APOLLO_CLIENT.query({
|
|
query: gql`
|
|
query loadTree (
|
|
$siteId: UUID!
|
|
$parentId: UUID
|
|
$parentPath: String
|
|
$types: [TreeItemType]
|
|
$includeAncestors: Boolean
|
|
$includeRootFolders: Boolean
|
|
) {
|
|
tree (
|
|
siteId: $siteId
|
|
parentId: $parentId
|
|
parentPath: $parentPath
|
|
types: $types
|
|
includeAncestors: $includeAncestors
|
|
includeRootFolders: $includeRootFolders
|
|
) {
|
|
__typename
|
|
id
|
|
folderPath
|
|
fileName
|
|
title
|
|
... on TreeItemFolder {
|
|
childrenCount
|
|
isAncestor
|
|
}
|
|
... on TreeItemPage {
|
|
createdAt
|
|
updatedAt
|
|
editor
|
|
}
|
|
... on TreeItemAsset {
|
|
createdAt
|
|
updatedAt
|
|
fileSize
|
|
fileExt
|
|
mimeType
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
siteId: siteStore.id,
|
|
parentId,
|
|
parentPath,
|
|
types,
|
|
includeAncestors: initLoad,
|
|
includeRootFolders: initLoad
|
|
},
|
|
fetchPolicy: 'network-only'
|
|
})
|
|
const items = cloneDeep(resp?.data?.tree)
|
|
if (items?.length > 0) {
|
|
const newTreeRoots = []
|
|
for (const item of items) {
|
|
switch (item.__typename) {
|
|
case 'TreeItemFolder': {
|
|
// -> Tree Nodes
|
|
state.treeNodes[item.id] = {
|
|
folderPath: item.folderPath,
|
|
fileName: item.fileName,
|
|
title: item.title,
|
|
children: state.treeNodes[item.id]?.children ?? []
|
|
}
|
|
|
|
// -> Set Ancestors / Tree Roots
|
|
if (item.folderPath) {
|
|
if (!state.treeNodes[parentId].children.includes(item.id)) {
|
|
state.treeNodes[parentId].children.push(item.id)
|
|
}
|
|
} else {
|
|
newTreeRoots.push(item.id)
|
|
}
|
|
|
|
// -> File List
|
|
if (parentId === state.currentFolderId && !item.isAncestor) {
|
|
state.fileList.push({
|
|
id: item.id,
|
|
type: 'folder',
|
|
title: item.title,
|
|
fileName: item.fileName,
|
|
children: item.childrenCount || 0
|
|
})
|
|
}
|
|
break
|
|
}
|
|
case 'TreeItemAsset': {
|
|
if (parentId === state.currentFolderId) {
|
|
state.fileList.push({
|
|
id: item.id,
|
|
type: 'asset',
|
|
title: item.title,
|
|
fileExt: item.fileExt,
|
|
fileSize: item.fileSize,
|
|
mimeType: item.mimeType,
|
|
folderPath: item.folderPath,
|
|
fileName: item.fileName
|
|
})
|
|
}
|
|
break
|
|
}
|
|
case 'TreeItemPage': {
|
|
if (parentId === state.currentFolderId) {
|
|
state.fileList.push({
|
|
id: item.id,
|
|
type: 'page',
|
|
title: item.title,
|
|
pageType: 'markdown',
|
|
updatedAt: '2022-11-24T18:27:00Z',
|
|
folderPath: item.folderPath,
|
|
fileName: item.fileName
|
|
})
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if (newTreeRoots.length > 0) {
|
|
state.treeRoots = newTreeRoots
|
|
}
|
|
}
|
|
} catch (err) {
|
|
$q.notify({
|
|
type: 'negative',
|
|
message: 'Failed to load folder tree.',
|
|
caption: err.message
|
|
})
|
|
}
|
|
if (parentId === state.currentFolderId) {
|
|
nextTick(() => {
|
|
state.fileListLoading = false
|
|
})
|
|
}
|
|
if (parentId) {
|
|
treeComp.value.setLoaded(parentId)
|
|
}
|
|
state.isFetching = false
|
|
}
|
|
|
|
function treeContextAction (nodeId, action) {
|
|
switch (action) {
|
|
case 'newFolder': {
|
|
newFolder(nodeId)
|
|
break
|
|
}
|
|
case 'rename': {
|
|
renameFolder(nodeId)
|
|
break
|
|
}
|
|
case 'del': {
|
|
delFolder(nodeId)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------------------------
|
|
// FOLDER METHODS
|
|
// --------------------------------------
|
|
|
|
function newFolder (parentId) {
|
|
$q.dialog({
|
|
component: FolderCreateDialog,
|
|
componentProps: {
|
|
parentId
|
|
}
|
|
}).onOk(() => {
|
|
loadTree({ parentId })
|
|
})
|
|
}
|
|
|
|
function renameFolder (folderId) {
|
|
$q.dialog({
|
|
component: FolderRenameDialog,
|
|
componentProps: {
|
|
folderId
|
|
}
|
|
}).onOk(() => {
|
|
treeComp.value.resetLoaded()
|
|
loadTree({ parentId: folderId, initLoad: true })
|
|
})
|
|
}
|
|
|
|
function delFolder (folderId, mustReload = false) {
|
|
$q.dialog({
|
|
component: FolderDeleteDialog,
|
|
componentProps: {
|
|
folderId,
|
|
folderName: state.treeNodes[folderId].title
|
|
}
|
|
}).onOk(() => {
|
|
for (const nodeId in state.treeNodes) {
|
|
if (state.treeNodes[nodeId].children.includes(folderId)) {
|
|
state.treeNodes[nodeId].children = state.treeNodes[nodeId].children.filter(c => c !== folderId)
|
|
}
|
|
}
|
|
delete state.treeNodes[folderId]
|
|
if (state.treeRoots.includes(folderId)) {
|
|
state.treeRoots = state.treeRoots.filter(n => n !== folderId)
|
|
}
|
|
if (mustReload) {
|
|
loadTree({ parentId: state.currentFolderId })
|
|
}
|
|
})
|
|
}
|
|
|
|
function reloadFolder (folderId) {
|
|
loadTree({ parentId: folderId })
|
|
treeComp.value.resetLoaded()
|
|
}
|
|
|
|
// PAGE METHODS
|
|
// --------------------------------------
|
|
|
|
function delPage (pageId, pageName) {
|
|
$q.dialog({
|
|
component: defineAsyncComponent(() => import('src/components/PageDeleteDialog.vue')),
|
|
componentProps: {
|
|
pageId,
|
|
pageName
|
|
}
|
|
}).onOk(() => {
|
|
loadTree(state.currentFolderId, null)
|
|
})
|
|
}
|
|
|
|
// --------------------------------------
|
|
// UPLOAD METHODS
|
|
// --------------------------------------
|
|
|
|
function uploadFile () {
|
|
fileIpt.value.click()
|
|
}
|
|
|
|
async function uploadNewFiles () {
|
|
if (!fileIpt.value.files?.length) {
|
|
return
|
|
}
|
|
|
|
state.isUploading = true
|
|
state.uploadPercentage = 0
|
|
|
|
state.loading++
|
|
|
|
nextTick(() => {
|
|
setTimeout(async () => {
|
|
try {
|
|
const totalFiles = fileIpt.value.files.length
|
|
let idx = 0
|
|
for (const fileToUpload of fileIpt.value.files) {
|
|
idx++
|
|
state.uploadPercentage = totalFiles > 1 ? Math.round(idx / totalFiles * 100) : 90
|
|
const resp = await APOLLO_CLIENT.mutate({
|
|
mutation: gql`
|
|
mutation uploadAssets (
|
|
$folderId: UUID
|
|
$locale: String
|
|
$siteId: UUID
|
|
$files: [Upload!]!
|
|
) {
|
|
uploadAssets (
|
|
folderId: $folderId
|
|
locale: $locale
|
|
siteId: $siteId
|
|
files: $files
|
|
) {
|
|
operation {
|
|
succeeded
|
|
message
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
folderId: state.currentFolderId,
|
|
siteId: siteStore.id,
|
|
locale: 'en', // TODO: use current locale
|
|
files: [fileToUpload]
|
|
}
|
|
})
|
|
if (!resp?.data?.uploadAssets?.operation?.succeeded) {
|
|
throw new Error(resp?.data?.uploadAssets?.operation?.message || 'An unexpected error occured.')
|
|
}
|
|
}
|
|
state.uploadPercentage = 100
|
|
loadTree({ parentId: state.currentFolderId })
|
|
$q.notify({
|
|
type: 'positive',
|
|
message: t('fileman.uploadSuccess')
|
|
})
|
|
} catch (err) {
|
|
$q.notify({
|
|
type: 'negative',
|
|
message: 'Failed to upload file.',
|
|
caption: err.message
|
|
})
|
|
}
|
|
state.loading--
|
|
fileIpt.value.value = null
|
|
setTimeout(() => {
|
|
state.isUploading = false
|
|
state.uploadPercentage = 0
|
|
}, 1500)
|
|
}, 400)
|
|
})
|
|
}
|
|
|
|
function uploadCancel () {
|
|
state.isUploading = false
|
|
state.uploadPercentage = 0
|
|
}
|
|
|
|
// --------------------------------------
|
|
// ITEM LIST ACTIONS
|
|
// --------------------------------------
|
|
|
|
function selectItem (item) {
|
|
if (item.type === 'folder') {
|
|
state.currentFolderId = item.id
|
|
treeComp.value.setOpened(item.id)
|
|
} else {
|
|
state.currentFileId = item.id
|
|
}
|
|
}
|
|
|
|
function openItem (item) {
|
|
switch (item.type) {
|
|
case 'folder': {
|
|
return
|
|
}
|
|
case 'page': {
|
|
const pagePath = item.folderPath ? `${item.folderPath}/${item.fileName}` : item.fileName
|
|
router.push(`/${pagePath}`)
|
|
close()
|
|
break
|
|
}
|
|
case 'asset': {
|
|
// TODO: Open asset
|
|
close()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
async function copyItemURL (item) {
|
|
try {
|
|
switch (item.type) {
|
|
case 'page': {
|
|
const pagePath = item.folderPath ? `${item.folderPath}/${item.fileName}` : item.fileName
|
|
await navigator.clipboard.writeText(`${window.location.origin}/${pagePath}`)
|
|
break
|
|
}
|
|
case 'asset': {
|
|
const assetPath = item.folderPath ? `${item.folderPath}/${item.fileName}` : item.fileName
|
|
await navigator.clipboard.writeText(`${window.location.origin}/${assetPath}`)
|
|
break
|
|
}
|
|
default: {
|
|
throw new Error('Invalid Item Type')
|
|
}
|
|
}
|
|
$q.notify({
|
|
type: 'positive',
|
|
message: t('fileman.copyURLSuccess')
|
|
})
|
|
} catch (err) {
|
|
$q.notify({
|
|
type: 'negative',
|
|
message: 'Failed to copy URL to clipboard.',
|
|
caption: err.message
|
|
})
|
|
}
|
|
}
|
|
|
|
function renameItem (item) {
|
|
console.info(item)
|
|
switch (item.type) {
|
|
case 'folder': {
|
|
renameFolder(item.id)
|
|
break
|
|
}
|
|
case 'page': {
|
|
// TODO: Rename page
|
|
break
|
|
}
|
|
case 'asset': {
|
|
// TODO: Rename asset
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
function delItem (item) {
|
|
switch (item.type) {
|
|
case 'folder': {
|
|
delFolder(item.id, true)
|
|
break
|
|
}
|
|
case 'page': {
|
|
delPage(item.id, item.title)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// MOUNTED
|
|
|
|
onMounted(() => {
|
|
loadTree({})
|
|
})
|
|
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.fileman {
|
|
&-left {
|
|
@at-root .body--light & {
|
|
background-color: $blue-grey-1;
|
|
}
|
|
@at-root .body--dark & {
|
|
background-color: $dark-4;
|
|
}
|
|
}
|
|
|
|
&-center {
|
|
|
|
@at-root .body--light & {
|
|
background-color: #FFF;
|
|
}
|
|
@at-root .body--dark & {
|
|
background-color: $dark-6;
|
|
}
|
|
}
|
|
|
|
&-right {
|
|
@at-root .body--light & {
|
|
background-color: $grey-1;
|
|
}
|
|
@at-root .body--dark & {
|
|
background-color: $dark-5;
|
|
}
|
|
}
|
|
|
|
&-toolbar {
|
|
@at-root .body--light & {
|
|
background-color: $grey-1;
|
|
}
|
|
@at-root .body--dark & {
|
|
background-color: $dark-5;
|
|
}
|
|
}
|
|
|
|
&-path {
|
|
@at-root .body--light & {
|
|
background-color: $blue-grey-1 !important;
|
|
}
|
|
@at-root .body--dark & {
|
|
background-color: $dark-4 !important;
|
|
}
|
|
}
|
|
|
|
&-main {
|
|
height: 100%;
|
|
}
|
|
|
|
&-loadinglist {
|
|
padding: 16px;
|
|
font-style: italic;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
> span {
|
|
margin-top: 16px;
|
|
}
|
|
}
|
|
|
|
&-emptylist {
|
|
padding: 16px;
|
|
font-style: italic;
|
|
font-size: 1.5em;
|
|
font-weight: 300;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
> img {
|
|
opacity: .25;
|
|
width: 200px;
|
|
}
|
|
|
|
@at-root .body--light & {
|
|
color: $grey-6;
|
|
}
|
|
@at-root .body--dark & {
|
|
color: $grey-7;
|
|
|
|
> img {
|
|
filter: invert(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
&-filelist {
|
|
padding: 8px 12px;
|
|
|
|
> .q-item {
|
|
padding: 4px 6px;
|
|
border-radius: 8px;
|
|
|
|
&.active {
|
|
background-color: var(--q-primary);
|
|
color: #FFF;
|
|
|
|
.fileman-filelist-label .q-item__label--caption {
|
|
color: rgba(255,255,255,.7);
|
|
}
|
|
|
|
.fileman-filelist-side .text-caption {
|
|
color: rgba(255,255,255,.7);
|
|
}
|
|
}
|
|
}
|
|
|
|
&.is-compact {
|
|
> .q-item {
|
|
padding: 0 6px;
|
|
min-height: 36px;
|
|
}
|
|
|
|
.fileman-filelist-icon {
|
|
padding-right: 6px;
|
|
min-width: 0;
|
|
}
|
|
}
|
|
}
|
|
&-details-row {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 5px 0;
|
|
|
|
label {
|
|
font-size: .7rem;
|
|
font-weight: 500;
|
|
|
|
@at-root .body--light & {
|
|
color: $grey-6;
|
|
}
|
|
@at-root .body--dark & {
|
|
color: $blue-grey-4;
|
|
}
|
|
}
|
|
span {
|
|
font-size: .85rem;
|
|
|
|
@at-root .body--light & {
|
|
color: $grey-8;
|
|
}
|
|
@at-root .body--dark & {
|
|
color: $blue-grey-2;
|
|
}
|
|
}
|
|
|
|
& + .fileman-details-row {
|
|
margin-top: 5px;
|
|
}
|
|
}
|
|
|
|
&-progressbar {
|
|
width: 100%;
|
|
flex: 1;
|
|
height: 12px;
|
|
border-radius: 3px;
|
|
|
|
@at-root .body--light & {
|
|
background-color: $blue-grey-2;
|
|
}
|
|
@at-root .body--dark & {
|
|
background-color: $dark-4 !important;
|
|
}
|
|
|
|
> div {
|
|
height: 12px;
|
|
background-color: $positive;
|
|
border-radius: 3px 0 0 3px;
|
|
background-image: linear-gradient(
|
|
-45deg,
|
|
rgba(255, 255, 255, 0.3) 25%,
|
|
transparent 25%,
|
|
transparent 50%,
|
|
rgba(255, 255, 255, 0.3) 50%,
|
|
rgba(255, 255, 255, 0.3) 75%,
|
|
transparent 75%,
|
|
transparent
|
|
);
|
|
background-size: 50px 50px;
|
|
background-position: 0 0;
|
|
animation: fileman-progress 2s linear infinite;
|
|
box-shadow: 0 0 5px 0 $positive;
|
|
font-size: 9px;
|
|
letter-spacing: 2px;
|
|
font-weight: 700;
|
|
color: #FFF;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
overflow: hidden;
|
|
transition: all 1s ease;
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes fileman-progress {
|
|
0% {
|
|
background-position: 0 0;
|
|
}
|
|
100% {
|
|
background-position: -50px -50px;
|
|
}
|
|
}
|
|
</style>
|