1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252 |
- <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='commonStore.locale'
- :aria-label='commonStore.locale'
- style='height: 40px;'
- )
- locale-selector-menu
- q-input(
- dark
- v-model='state.search'
- standout='bg-white text-dark'
- dense
- ref='searchField'
- style='width: 100%;'
- :label='t(`fileman.searchFolder`)'
- :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}}
- template(v-if='insertMode')
- q-separator.q-my-md
- q-btn.full-width(
- @click='insertItem()'
- :label='t(`common.actions.insert`)'
- color='primary'
- icon='las la-plus-circle'
- push
- padding='sm'
- )
- 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)'
- @new-page='() => close()'
- :base-path='folderPath'
- )
- 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='doubleClickItem(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.translucent-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='insertMode && item.type !== `folder`', @click='insertItem(item)')
- q-item-section(side)
- q-icon(name='las la-plus-circle', color='primary')
- q-item-section {{ t(`common.actions.insert`) }}
- q-item(clickable, v-if='item.type === `page`', @click='editItem(item)')
- q-item-section(side)
- q-icon(name='las la-edit', color='orange')
- q-item-section {{ t(`common.actions.edit`) }}
- q-item(clickable, v-if='item.type === `page`', @click='rerenderPage(item)')
- q-item-section(side)
- q-icon(name='las la-magic', color='orange')
- q-item-section {{ t(`common.actions.rerender`) }}
- 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 {{ t(`common.actions.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 {{ t(`common.actions.copyURL`) }}
- q-item(clickable, v-if='item.type !== `folder`', @click='downloadItem(item)')
- q-item-section(side)
- q-icon(name='las la-download', color='primary')
- q-item-section {{ t(`common.actions.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 {{ t(`common.actions.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, toRaw, watch } from 'vue'
- import { filesize } from 'filesize'
- import { useQuasar } from 'quasar'
- import { DateTime } from 'luxon'
- import { cloneDeep, dropRight, find, findKey, initial, last, nth } 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 { useCommonStore } from 'src/stores/common'
- 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'
- import LocaleSelectorMenu from 'src/components/LocaleSelectorMenu.vue'
- // QUASAR
- const $q = useQuasar()
- // STORES
- const commonStore = useCommonStore()
- 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 insertMode = computed(() => siteStore.overlayOpts?.insertMode ?? false)
- 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}.webp`
- 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
- }
- function insertItem (item) {
- if (!item) {
- item = find(state.fileList, ['id', state.currentFileId])
- }
- EVENT_BUS.emit('insertAsset', toRaw(item))
- close()
- }
- 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) {
- let folderParentId = parentId
- if (!folderParentId) {
- const parentFolderParts = item.folderPath.split('/')
- const parentFolder = find(items, { folderPath: parentFolderParts.length > 1 ? initial(parentFolderParts).join('/') : '', fileName: last(parentFolderParts) })
- folderParentId = parentFolder.id
- }
- if (item.id !== folderParentId && !state.treeNodes[folderParentId]?.children?.includes(item.id)) {
- state.treeNodes[folderParentId]?.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(async () => {
- treeComp.value.resetLoaded()
- // // -> Delete current folder and children from cache
- // const fPath = [state.treeNodes[folderId].folderPath, state.treeNodes[folderId].fileName].filter(p => !!p).join('/')
- // delete state.treeNodes[folderId]
- // for (const [nodeId, node] of Object.entries(state.treeNodes)) {
- // if (node.folderPath.startsWith(fPath)) {
- // delete state.treeNodes[nodeId]
- // }
- // }
- // -> Reload tree
- await loadTree({ parentId: folderId, types: ['folder'], initLoad: true }) // Update tree
- // -> Reload current view (in case current folder is included)
- await loadTree({ parentId: state.currentFolderId })
- })
- }
- 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 rerenderPage (item) {
- $q.dialog({
- component: defineAsyncComponent(() => import('src/components/RerenderPageDialog.vue')),
- componentProps: {
- id: item.id
- }
- })
- }
- 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({
- context: {
- uploadMode: true
- },
- 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 doubleClickItem (item) {
- if (insertMode.value) {
- insertItem(item)
- } else {
- openItem(item)
- }
- }
- 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
- })
- }
- }
- async function editItem (item) {
- router.push(item.folderPath ? `/_edit/${item.folderPath}/${item.fileName}` : `/_edit/${item.fileName}`)
- close()
- }
- function downloadItem (item) {
- }
- 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(async () => {
- const pathParts = pageStore.path.split('/')
- const parentPath = initial(pathParts).join('/')
- await loadTree({
- parentPath,
- initLoad: true
- })
- // -> Open tree up to current folder
- const folderFolderPath = dropRight(pathParts, 2).join('/')
- const folderFileName = nth(pathParts, -2)
- for (const [id, node] of Object.entries(state.treeNodes)) {
- if (parentPath.startsWith(node.folderPath ? `${node.folderPath}/${node.fileName}` : node.fileName)) {
- treeComp.value.setOpened(id)
- }
- }
- // -> Switch to current folder (from page path)
- const currentNodeId = findKey(state.treeNodes, n => n.folderPath === folderFolderPath && n.fileName === folderFileName)
- if (currentNodeId) {
- state.currentFolderId = currentNodeId
- }
- })
- </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>
|