* 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>
1028 lines
38 KiB
Vue
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>
|