Stefan Dej 654785fedf
feat: global fullscreen fileupload (#777)
* feat: init upload from global gcode file upload

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* feat: global fileupload & fileupload status snackbar

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* feat: implement cancel upload

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* chore: update config file upload to global file upload

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* chore: fix eslint issue

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* lcoale: add fullscreenUpload to i18n

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* chore: fix toast position with upload snackbar

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* chore: fix IDE code analyzer issues

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* fix: app favicon draw function

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* style: fix favicon source code

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* chore: fix type in TheFullscreenUpload.vue

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* chore: fix type in TheFullscreenUpload.vue

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* fix: fullscreen upload dont work in firefox

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* feat: add icon to TheFullscreenUpload.vue

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* locale(en): remove blank line

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* locale(en): add missing translation

Signed-off-by: Stefan Dej <meteyou@gmail.com>

* locale(de): add DE translations

Signed-off-by: Stefan Dej <meteyou@gmail.com>
2022-05-08 17:29:59 +02:00

1028 lines
38 KiB
Vue

<style scoped></style>
<template>
<div>
<panel
:title="$t('Machine.ConfigFilesPanel.ConfigFiles')"
card-class="machine-configfiles-panel"
:icon="mdiInformation">
<v-card-text>
<v-row>
<v-col class="col-12 col-lg pr-lg-0">
<v-select
v-model="root"
class="machine-configfiles-panel__root-select"
:items="registeredDirectories"
:label="$t('Machine.ConfigFilesPanel.Root')"
outlined
hide-details
dense
attach=".machine-configfiles-panel__root-select"
@change="changeRoot"></v-select>
</v-col>
<v-col class="col col-lg-auto pl-lg-0 text-right">
<input ref="fileUpload" type="file" style="display: none" multiple @change="uploadFile" />
<v-btn
v-for="button in filteredToolbarButtons"
:key="button.loadingName"
class="px-2 minwidth-0 ml-3"
:loading="button.loadingName !== null && loadings.includes(button.loadingName)"
@click="button.click">
<v-tooltip top>
<template #activator="{ on, attrs }">
<v-icon v-bind="attrs" v-on="on">{{ button.icon }}</v-icon>
</template>
<span>{{ button.text }}</span>
</v-tooltip>
</v-btn>
<v-menu offset-y left :title="$t('Machine.ConfigFilesPanel.SetupCurrentList')">
<template #activator="{ on, attrs }">
<v-btn class="px-2 minwidth-0 ml-3" v-bind="attrs" v-on="on">
<v-icon class="machine-configfiles-panel__settings-icon">{{ mdiCog }}</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item class="minHeight36">
<v-checkbox
v-model="showHiddenFiles"
class="mt-0"
hide-details
:label="$t('Machine.ConfigFilesPanel.HiddenFiles')"></v-checkbox>
</v-list-item>
<v-list-item class="minHeight36">
<v-checkbox
v-model="hideBackupFiles"
class="mt-0"
hide-details
:label="$t('Machine.ConfigFilesPanel.HideBackupFiles')"></v-checkbox>
</v-list-item>
</v-list>
</v-menu>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row>
<v-col class="col-12 py-2 d-flex align-center">
<span>
<b>{{ $t('Machine.ConfigFilesPanel.CurrentPath') }}:</b>
{{ absolutePath }}
</span>
<v-spacer></v-spacer>
<template v-if="disk_usage !== null">
<v-tooltip top>
<template #activator="{ on, attrs }">
<span v-bind="attrs" v-on="on">
<b>{{ $t('Machine.ConfigFilesPanel.FreeDisk') }}:</b>
{{ formatFilesize(disk_usage.free) }}
</span>
</template>
<span>
{{ $t('Machine.ConfigFilesPanel.Used') }}: {{ formatFilesize(disk_usage.used) }}
<br />
{{ $t('Machine.ConfigFilesPanel.Free') }}: {{ formatFilesize(disk_usage.free) }}
<br />
{{ $t('Machine.ConfigFilesPanel.Total') }}: {{ formatFilesize(disk_usage.total) }}
</span>
</v-tooltip>
</template>
</v-col>
</v-row>
</v-card-text>
<v-divider></v-divider>
<v-data-table
:items="files"
class="files-table"
:headers="headers"
:page.sync="currentPage"
:custom-sort="sortFiles"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
:items-per-page.sync="countPerPage"
:footer-props="{
itemsPerPageText: $t('Machine.ConfigFilesPanel.Files'),
itemsPerPageAllText: $t('Machine.ConfigFilesPanel.AllFiles'),
itemsPerPageOptions: [10, 25, 50, 100, -1],
}"
mobile-breakpoint="0"
item-key="name">
<template #no-data>
<div class="text-center">{{ $t('Machine.ConfigFilesPanel.Empty') }}</div>
</template>
<template v-if="currentPath !== ''" slot="body.prepend">
<tr
class="file-list-cursor"
@click="clickRowGoBack"
@dragover="dragOverFilelist($event, { isDirectory: true, filename: '..' })"
@dragleave="dragLeaveFilelist"
@drop.prevent.stop="dragDropFilelist($event, { isDirectory: true, filename: '..' })">
<td class="pr-0 text-center" style="width: 32px">
<v-icon>{{ mdiFolderUpload }}</v-icon>
</td>
<td class=" " colspan="4">..</td>
</tr>
</template>
<template #item="{ index, item }">
<tr
:key="`${index} ${item.filename}`"
v-longpress:600="(e) => showContextMenu(e, item)"
class="file-list-cursor user-select-none"
:data-name="item.filename"
draggable="true"
@contextmenu="showContextMenu($event, item)"
@click="clickRow(item)"
@drag="dragFile($event, item)"
@dragend="dragendFile($event)"
@dragover="dragOverFilelist($event, item)"
@dragleave="dragLeaveFilelist"
@drop.prevent.stop="dragDropFilelist($event, item)">
<td class="pr-0 text-center" style="width: 32px">
<v-icon v-if="item.isDirectory">{{ mdiFolder }}</v-icon>
<v-icon v-if="!item.isDirectory">{{ mdiFile }}</v-icon>
</td>
<td class=" ">{{ item.filename }}</td>
<td class="text-no-wrap text-right">
{{ item.isDirectory ? '--' : formatFilesize(item.size) }}
</td>
<td class="text-right">{{ formatDate(item.modified) }}</td>
</tr>
</template>
</v-data-table>
</panel>
<v-menu v-model="contextMenu.shown" :position-x="contextMenu.x" :position-y="contextMenu.y" absolute offset-y>
<v-list>
<v-list-item v-if="!contextMenu.item.isDirectory" @click="clickRow(contextMenu.item, true)">
<v-icon class="mr-1">{{ mdiFileDocumentEditOutline }}</v-icon>
{{
contextMenu.item.permissions.includes('w')
? $t('Machine.ConfigFilesPanel.EditFile')
: $t('Machine.ConfigFilesPanel.ShowFile')
}}
</v-list-item>
<v-list-item v-if="!contextMenu.item.isDirectory" @click="downloadFile">
<v-icon class="mr-1">{{ mdiCloudDownload }}</v-icon>
{{ $t('Machine.ConfigFilesPanel.Download') }}
</v-list-item>
<v-list-item
v-if="!contextMenu.item.isDirectory && contextMenu.item.permissions.includes('w')"
@click="renameFile(contextMenu.item)">
<v-icon class="mr-1">{{ mdiRenameBox }}</v-icon>
{{ $t('Machine.ConfigFilesPanel.Rename') }}
</v-list-item>
<v-list-item
v-if="contextMenu.item.isDirectory && contextMenu.item.permissions.includes('w')"
@click="renameDirectory(contextMenu.item)">
<v-icon class="mr-1">{{ mdiRenameBox }}</v-icon>
{{ $t('Machine.ConfigFilesPanel.Rename') }}
</v-list-item>
<v-list-item
v-if="!contextMenu.item.isDirectory && contextMenu.item.permissions.includes('w')"
class="red--text"
@click="removeFile">
<v-icon class="mr-1" color="error">{{ mdiDelete }}</v-icon>
{{ $t('Machine.ConfigFilesPanel.Delete') }}
</v-list-item>
<v-list-item
v-if="contextMenu.item.isDirectory && contextMenu.item.permissions.includes('w')"
class="red--text"
@click="deleteDirectory(contextMenu.item)">
<v-icon class="mr-1" color="error">{{ mdiDelete }}</v-icon>
{{ $t('Machine.ConfigFilesPanel.Delete') }}
</v-list-item>
</v-list>
</v-menu>
<v-dialog
v-model="dialogImage.show"
hide-overlay
fullscreen
class="fill-height"
@keydown.esc="
dialogImage.show = false
dialogImage.item.url = null
dialogImage.item.svg = null
">
<panel
:title="dialogImage.item.name"
card-class="maschine-configfiles-imageviewer-dialog"
style="position: relative">
<template #buttons>
<v-btn
icon
tile
@click="
dialogImage.show = false
dialogImage.item.url = null
dialogImage.item.svg = null
">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<div class="d-flex justify-center" style="max-height: calc(var(--app-height) - 64px); overflow: auto">
<img
v-if="dialogImage.item.url"
:src="dialogImage.item.url"
style="max-height: 100%; width: auto"
alt="image" />
<div v-else-if="dialogImage.item.svg" class="fill-width" v-html="dialogImage.item.svg"></div>
</div>
</panel>
</v-dialog>
<v-dialog v-model="dialogCreateFile.show" max-width="400">
<panel
:title="$t('Machine.ConfigFilesPanel.CreateFile')"
card-class="maschine-configfiles-create-file-dialog"
:margin-bottom="false">
<template #buttons>
<v-btn icon tile @click="dialogCreateFile.show = false">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<v-card-text>
<v-text-field
ref="inputDialogCreateFileName"
v-model="dialogCreateFile.name"
:label="$t('Machine.ConfigFilesPanel.Name')"
required
@keyup.enter="createFileAction"></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="" text @click="dialogCreateFile.show = false">
{{ $t('Machine.ConfigFilesPanel.Cancel') }}
</v-btn>
<v-btn color="primary" text @click="createFileAction">
{{ $t('Machine.ConfigFilesPanel.Create') }}
</v-btn>
</v-card-actions>
</panel>
</v-dialog>
<v-dialog v-model="dialogRenameFile.show" max-width="400">
<panel
:title="$t('Machine.ConfigFilesPanel.RenameFile')"
card-class="maschine-configfiles-rename-file-dialog"
:margin-bottom="false">
<template #buttons>
<v-btn icon tile @click="dialogRenameFile.show = false">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<v-card-text>
<v-text-field
ref="inputDialogRenameFileName"
v-model="dialogRenameFile.newName"
:label="$t('Machine.ConfigFilesPanel.Name')"
required
@keyup.enter="renameFileAction"></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="" text @click="dialogRenameFile.show = false">
{{ $t('Machine.ConfigFilesPanel.Cancel') }}
</v-btn>
<v-btn color="primary" text @click="renameFileAction">
{{ $t('Machine.ConfigFilesPanel.Rename') }}
</v-btn>
</v-card-actions>
</panel>
</v-dialog>
<v-dialog v-model="dialogCreateDirectory.show" max-width="400">
<panel
:title="$t('Machine.ConfigFilesPanel.CreateDirectory')"
card-class="maschine-configfiles-create-directory-dialog"
:margin-bottom="false">
<template #buttons>
<v-btn icon tile @click="dialogCreateDirectory.show = false">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<v-card-text>
<v-text-field
ref="inputDialogCreateDirectoryName"
v-model="dialogCreateDirectory.name"
:label="$t('Machine.ConfigFilesPanel.Name')"
required
@keyup.enter="createDirectoryAction"></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="" text @click="dialogCreateDirectory.show = false">
{{ $t('Machine.ConfigFilesPanel.Cancel') }}
</v-btn>
<v-btn color="primary" text @click="createDirectoryAction">
{{ $t('Machine.ConfigFilesPanel.Create') }}
</v-btn>
</v-card-actions>
</panel>
</v-dialog>
<v-dialog v-model="dialogRenameDirectory.show" max-width="400">
<panel
:title="$t('Machine.ConfigFilesPanel.RenameDirectory')"
card-class="maschine-configfiles-rename-directory-dialog"
:margin-bottom="false">
<template #buttons>
<v-btn icon tile @click="dialogRenameDirectory.show = false">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<v-card-text>
<v-text-field
ref="inputDialogRenameDirectoryName"
v-model="dialogRenameDirectory.newName"
:label="$t('Machine.ConfigFilesPanel.Name')"
required
@keyup.enter="renameDirectoryAction"></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="" text @click="dialogRenameDirectory.show = false">
{{ $t('Machine.ConfigFilesPanel.Cancel') }}
</v-btn>
<v-btn color="primary" text @click="renameDirectoryAction">
{{ $t('Machine.ConfigFilesPanel.Rename') }}
</v-btn>
</v-card-actions>
</panel>
</v-dialog>
<v-dialog v-model="dialogDeleteDirectory.show" max-width="400">
<panel
:title="$t('Machine.ConfigFilesPanel.DeleteDirectory')"
card-class="maschine-configfiles-delete-directory-dialog"
:margin-bottom="false">
<template #buttons>
<v-btn icon tile @click="dialogDeleteDirectory.show = false">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<v-card-text>
<p class="mb-0">
{{
$t('Machine.ConfigFilesPanel.DeleteDirectoryQuestion', {
name: dialogDeleteDirectory.item.filename,
})
}}
</p>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="" text @click="dialogDeleteDirectory.show = false">
{{ $t('Machine.ConfigFilesPanel.Cancel') }}
</v-btn>
<v-btn color="error" text @click="deleteDirectoryAction">
{{ $t('Machine.ConfigFilesPanel.Delete') }}
</v-btn>
</v-card-actions>
</panel>
</v-dialog>
<v-snackbar v-model="uploadSnackbar.status" :timeout="-1" :value="true" fixed right bottom dark>
<span v-if="uploadSnackbar.max > 1" class="mr-1">
({{ uploadSnackbar.number }}/{{ uploadSnackbar.max }})
</span>
<strong>{{ $t('Machine.ConfigFilesPanel.Uploading') }} {{ uploadSnackbar.filename }}</strong>
<br />
{{ Math.round(uploadSnackbar.percent) }} % @ {{ formatFilesize(Math.round(uploadSnackbar.speed)) }}/s
<br />
<v-progress-linear class="mt-2" :value="uploadSnackbar.percent"></v-progress-linear>
<template #action="{ attrs }">
<v-btn color="red" text v-bind="attrs" style="min-width: auto" @click="cancelUpload">
<v-icon class="0">{{ mdiClose }}</v-icon>
</v-btn>
</template>
</v-snackbar>
</div>
</template>
<script lang="ts">
import { Component, Mixins } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import { formatDate, formatFilesize, sortFiles } from '@/plugins/helpers'
import { FileStateFile } from '@/store/files/types'
import axios from 'axios'
import Panel from '@/components/ui/Panel.vue'
import { hiddenRootDirectories } from '@/store/variables'
import {
mdiFilePlus,
mdiFileUpload,
mdiFolderPlus,
mdiInformation,
mdiRefresh,
mdiClose,
mdiCog,
mdiFolder,
mdiFolderUpload,
mdiFile,
mdiFileDocumentEditOutline,
mdiCloudDownload,
mdiRenameBox,
mdiDelete,
mdiCloseThick,
} from '@mdi/js'
interface contextMenu {
shown: boolean
isDirectory: boolean
touchTimer: number | null
x: number
y: number
item: FileStateFile
}
interface dialogImageObject {
show: boolean
item: {
name: string | null
url: string | null
svg: string | null
}
}
interface dialogRenameObject {
show: boolean
newName: string
item: FileStateFile
}
interface dialogDeleteObject {
show: boolean
item: FileStateFile
}
interface uploadSnackbar {
status: boolean
filename: string
percent: number
speed: number
total: number
number: number
max: number
cancelTokenSource: any
lastProgress: {
time: number
loaded: number
}
}
interface draggingFile {
item: FileStateFile
}
@Component({
components: { Panel },
})
export default class ConfigFilesPanel extends Mixins(BaseMixin) {
mdiInformation = mdiInformation
mdiClose = mdiClose
mdiCog = mdiCog
mdiFolder = mdiFolder
mdiFolderUpload = mdiFolderUpload
mdiFileDocumentEditOutline = mdiFileDocumentEditOutline
mdiFile = mdiFile
mdiCloudDownload = mdiCloudDownload
mdiRenameBox = mdiRenameBox
mdiDelete = mdiDelete
mdiCloseThick = mdiCloseThick
sortFiles = sortFiles
formatFilesize = formatFilesize
formatDate = formatDate
declare $refs: {
fileUpload: HTMLInputElement
inputDialogCreateFileName: HTMLInputElement
inputDialogRenameFileName: HTMLInputElement
inputDialogCreateDirectoryName: HTMLInputElement
inputDialogRenameDirectoryName: HTMLInputElement
}
private currentPage = 1
private contextMenu: contextMenu = {
shown: false,
isDirectory: false,
touchTimer: null,
x: 0,
y: 0,
item: {
isDirectory: false,
filename: '',
permissions: '',
modified: new Date(),
},
}
private dialogImage: dialogImageObject = {
show: false,
item: {
name: null,
url: null,
svg: null,
},
}
private dialogCreateFile = {
show: false,
name: '',
}
private dialogRenameFile: dialogRenameObject = {
show: false,
newName: '',
item: {
isDirectory: false,
filename: '',
permissions: '',
modified: new Date(),
},
}
private dialogCreateDirectory = {
show: false,
name: '',
}
private dialogRenameDirectory: dialogRenameObject = {
show: false,
newName: '',
item: {
isDirectory: false,
filename: '',
permissions: '',
modified: new Date(),
},
}
private dialogDeleteDirectory: dialogDeleteObject = {
show: false,
item: {
isDirectory: false,
filename: '',
permissions: '',
modified: new Date(),
},
}
private uploadSnackbar: uploadSnackbar = {
status: false,
filename: '',
percent: 0,
speed: 0,
total: 0,
number: 0,
max: 0,
cancelTokenSource: {},
lastProgress: {
time: 0,
loaded: 0,
},
}
private draggingFile: draggingFile = {
item: {
isDirectory: false,
filename: '',
permissions: '',
modified: new Date(),
},
}
get blockFileUpload() {
return this.$store.state.gui.view.blockFileUpload ?? false
}
set blockFileUpload(newVal) {
this.$store.dispatch('gui/saveSettingWithoutUpload', { name: 'view.blockFileUpload', value: newVal })
}
get toolbarButtons() {
return [
{
text: this.$t('Machine.ConfigFilesPanel.UploadFile'),
color: 'grey darken-3',
icon: mdiFileUpload,
loadingName: null,
onlyWriteable: true,
click: this.uploadFileButton,
},
{
text: this.$t('Machine.ConfigFilesPanel.CreateFile'),
color: 'grey darken-3',
icon: mdiFilePlus,
loadingName: null,
onlyWriteable: true,
click: this.createFile,
},
{
text: this.$t('Machine.ConfigFilesPanel.CreateDirectory'),
color: 'grey darken-3',
icon: mdiFolderPlus,
loadingName: null,
onlyWriteable: true,
click: this.createDirecotry,
},
{
text: this.$t('Machine.ConfigFilesPanel.RefreshDirectory'),
color: 'grey darken-3',
icon: mdiRefresh,
loadingName: null,
onlyWriteable: false,
click: this.refreshFileList,
},
]
}
get filteredToolbarButtons() {
return this.toolbarButtons.filter((button) => {
return (this.directoryPermissions.includes('w') && button.onlyWriteable) || !button.onlyWriteable
})
}
get absolutePath() {
let path = '/' + this.root
if (this.currentPath) path += this.currentPath
return path
}
get directory() {
return this.$store.getters['files/getDirectory'](this.absolutePath)
}
get disk_usage() {
return this.directory?.disk_usage ?? { used: 0, free: 0, total: 0 }
}
get directoryPermissions() {
return this.directory?.permissions ?? 'r'
}
get files() {
let files = [...(this.directory?.childrens ?? [])]
if (!this.showHiddenFiles) {
files = files.filter((file) => file.filename.substr(0, 1) !== '.')
}
if (this.hideBackupFiles) {
const backupFileMatcher = /.*\/?printer-\d{8}_\d{6}\.cfg$/
files = files.filter((file) => !file.filename.match(backupFileMatcher))
}
return files
}
get headers() {
return [
{ text: '', value: '' },
{ text: this.$t('Machine.ConfigFilesPanel.Name'), value: 'filename' },
{ text: this.$t('Machine.ConfigFilesPanel.Filesize'), value: 'size', align: 'right' },
{ text: this.$t('Machine.ConfigFilesPanel.LastModified'), value: 'modified', align: 'right' },
]
}
get countPerPage() {
return this.$store.state.gui.view.configfiles.countPerPage
}
set countPerPage(newVal) {
this.$store.dispatch('gui/saveSetting', { name: 'view.configfiles.countPerPage', value: newVal })
}
get showHiddenFiles() {
return this.$store.state.gui.view.configfiles.showHiddenFiles
}
set showHiddenFiles(newVal) {
this.$store.dispatch('gui/saveSetting', { name: 'view.configfiles.showHiddenFiles', value: newVal })
}
get hideBackupFiles() {
return this.$store.state.gui.view.configfiles.hideBackupFiles
}
set hideBackupFiles(newVal) {
this.$store.dispatch('gui/saveSetting', { name: 'view.configfiles.hideBackupFiles', value: newVal })
}
get sortBy() {
return this.$store.state.gui.view.configfiles.sortBy
}
set sortBy(newVal) {
if (newVal === undefined) newVal = 'filename'
this.$store.dispatch('gui/saveSetting', { name: 'view.configfiles.sortBy', value: newVal })
}
get sortDesc() {
return this.$store.state.gui.view.configfiles.sortDesc
}
set sortDesc(newVal) {
if (newVal === undefined) newVal = false
this.$store.dispatch('gui/saveSetting', { name: 'view.configfiles.sortDesc', value: newVal })
}
get registeredDirectories() {
return this.$store.state.server.registered_directories
.filter((dir: string) => !hiddenRootDirectories.includes(dir))
.sort()
}
get root() {
return this.$store.state.gui.view.configfiles.rootPath
}
set root(newVal) {
this.$store.dispatch('gui/saveSettingWithoutUpload', { name: 'view.configfiles.rootPath', value: newVal })
}
get currentPath() {
return this.$store.state.gui.view.configfiles.currentPath
}
set currentPath(newVal) {
this.$store.dispatch('gui/saveSettingWithoutUpload', { name: 'view.configfiles.currentPath', value: newVal })
}
refreshFileList() {
this.$socket.emit(
'server.files.get_directory',
{ path: this.absolutePath.substring(1) },
{ action: 'files/getDirectory' }
)
}
changeRoot() {
this.currentPath = ''
}
clickRow(item: FileStateFile, force = false) {
if (!this.contextMenu.shown || force) {
if (force) this.contextMenu.shown = false
if (!item.isDirectory) {
if (
['png', 'jpeg', 'jpg', 'gif', 'bmp', 'tif', 'svg'].includes(
item.filename.split('.').pop()?.toLowerCase() ?? ''
)
) {
const url = `${this.apiUrl}/server/files${this.absolutePath}/${item.filename}?t=${Date.now()}`
this.dialogImage.item.name = item.filename
if (['svg'].includes(item.filename.split('.').pop()?.toLowerCase() ?? '')) {
fetch(url)
.then((res) => res.text())
.then((svg) => {
this.dialogImage.show = true
this.dialogImage.item.svg = svg
})
} else {
this.dialogImage.show = true
this.dialogImage.item.url = url
}
} else {
this.$store.dispatch('editor/openFile', {
root: this.root,
path: this.currentPath,
filename: item.filename,
size: item.size,
permissions: item.permissions,
})
}
} else {
this.currentPath += '/' + item.filename
this.currentPage = 1
}
}
}
clickRowGoBack() {
this.currentPath = this.currentPath.substr(0, this.currentPath.lastIndexOf('/'))
}
showContextMenu(e: any, item: FileStateFile) {
if (!this.contextMenu.shown) {
e?.preventDefault()
this.contextMenu.shown = true
this.contextMenu.x = e?.clientX || e?.pageX || window.screenX / 2
this.contextMenu.y = e?.clientY || e?.pageY || window.screenY / 2
this.contextMenu.item = item
this.$nextTick(() => {
this.contextMenu.shown = true
})
}
}
downloadFile() {
const filename = this.absolutePath + '/' + this.contextMenu.item.filename
const href = `${this.apiUrl}/server/files${encodeURI(filename)}`
window.open(href)
}
createDirecotry() {
this.dialogCreateDirectory.name = ''
this.dialogCreateDirectory.show = true
setTimeout(() => {
this.$refs.inputDialogCreateDirectoryName?.focus()
}, 200)
}
createDirectoryAction() {
this.dialogCreateDirectory.show = false
this.$socket.emit(
'server.files.post_directory',
{
path: this.absolutePath.substring(1) + '/' + this.dialogCreateDirectory.name,
},
{ action: 'files/getCreateDir' }
)
}
renameDirectory(item: FileStateFile) {
this.dialogRenameDirectory.item = item
this.dialogRenameDirectory.newName = item.filename
this.dialogRenameDirectory.show = true
setTimeout(() => {
this.$refs.inputDialogRenameDirectoryName?.focus()
}, 200)
}
renameDirectoryAction() {
this.dialogRenameDirectory.show = false
this.$socket.emit(
'server.files.move',
{
source: (this.absolutePath + '/' + this.dialogRenameDirectory.item.filename).slice(1),
dest: (this.absolutePath + '/' + this.dialogRenameDirectory.newName).slice(1),
},
{ action: 'files/getMove' }
)
}
deleteDirectory(item: FileStateFile) {
this.dialogDeleteDirectory.item = item
this.dialogDeleteDirectory.show = true
}
deleteDirectoryAction() {
this.dialogDeleteDirectory.show = false
this.$socket.emit(
'server.files.delete_directory',
{ path: this.absolutePath + '/' + this.dialogDeleteDirectory.item.filename, force: true },
{ action: 'files/getDeleteDir' }
)
}
createFile() {
this.dialogCreateFile.name = ''
this.dialogCreateFile.show = true
setTimeout(() => {
this.$refs.inputDialogCreateFileName?.focus()
}, 200)
}
createFileAction() {
const file = new File([''], this.dialogCreateFile.name)
let formData = new FormData()
formData.append('file', file)
formData.append('root', this.root)
if (this.currentPath.length) formData.append('path', this.currentPath.slice(1))
axios
.post(this.apiUrl + '/server/files/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
.then(() => {
this.$toast.success(
this.$t('Files.SuccessfullyCreated', { filename: this.dialogCreateFile.name }).toString()
)
this.dialogCreateFile.show = false
this.dialogCreateFile.name = ''
})
.catch(() => {
window.console.error('Error create file: ' + this.dialogCreateFile.name)
})
}
renameFile(item: FileStateFile) {
this.dialogRenameFile.item = item
this.dialogRenameFile.newName = item.filename
this.dialogRenameFile.show = true
setTimeout(() => {
this.$refs.inputDialogRenameFileName?.focus()
}, 200)
}
renameFileAction() {
this.dialogRenameFile.show = false
this.$socket.emit(
'server.files.move',
{
source: (this.absolutePath + '/' + this.dialogRenameFile.item.filename).slice(1),
dest: (this.absolutePath + '/' + this.dialogRenameFile.newName).slice(1),
},
{ action: 'files/getMove' }
)
}
removeFile() {
this.$socket.emit(
'server.files.delete_file',
{ path: this.absolutePath + '/' + this.contextMenu.item.filename },
{ action: 'files/getDeleteFile' }
)
}
uploadFileButton() {
this.$refs.fileUpload.click()
}
async uploadFile() {
if (this.$refs.fileUpload.files?.length) {
const files = [...this.$refs.fileUpload.files]
this.$refs.fileUpload.value = ''
await this.$store.dispatch('socket/addLoading', { name: 'configFileUpload' })
await this.$store.dispatch('files/uploadSetCurrentNumber', 0)
await this.$store.dispatch('files/uploadSetMaxNumber', this.$refs.fileUpload.files.length)
for (const file of files) {
await this.$store.dispatch('files/uploadIncrementCurrentNumber')
const path = this.currentPath.slice(0, 1) === '/' ? this.currentPath.slice(1) : this.currentPath
const result = await this.$store.dispatch('files/uploadFile', {
file,
path,
root: 'config',
})
if (result !== false)
this.$toast.success(this.$t('Files.SuccessfullyUploaded', { filename: result }).toString())
}
await this.$store.dispatch('socket/removeLoading', { name: 'configFileUpload' })
}
}
cancelUpload() {
this.uploadSnackbar.cancelTokenSource.cancel()
this.uploadSnackbar.status = false
}
dragFile(e: Event, item: FileStateFile) {
e.preventDefault()
this.blockFileUpload = true
this.draggingFile.item = item
}
dragendFile(e: Event) {
e.preventDefault()
this.blockFileUpload = false
this.draggingFile.item = {
isDirectory: false,
filename: '',
permissions: '',
modified: new Date(),
}
}
dragOverFilelist(e: any, row: any) {
if (this.blockFileUpload) {
e.preventDefault()
//e.stopPropagation()
if (row.isDirectory) e.target.parentElement.style.backgroundColor = '#43A04720'
}
}
dragLeaveFilelist(e: any) {
if (this.blockFileUpload) {
e.preventDefault()
e.stopPropagation()
e.target.parentElement.style.backgroundColor = 'transparent'
}
}
async dragDropFilelist(e: any, row: any) {
if (this.blockFileUpload) {
e.preventDefault()
e.target.parentElement.style.backgroundColor = 'transparent'
let dest: string
if (row.filename === '..') {
dest =
this.absolutePath.slice(1, this.absolutePath.lastIndexOf('/') + 1) + this.draggingFile.item.filename
} else dest = this.absolutePath + '/' + row.filename + '/' + this.draggingFile.item.filename
this.$socket.emit(
'server.files.move',
{
source: this.absolutePath.slice(1) + '/' + this.draggingFile.item.filename,
dest: dest,
},
{ action: 'files/getMove' }
)
}
}
}
</script>