mirror of
https://github.com/45Drives/cockpit-navigator.git
synced 2025-07-29 16:45:13 +02:00
flatten entry object, make actions specific to component
This commit is contained in:
parent
b528a7f57c
commit
06c6a04d59
@ -5,28 +5,28 @@
|
||||
<td class="!pl-1 relative">
|
||||
<div :class="[entry.cut ? 'line-through' : '', 'flex items-center gap-1']">
|
||||
<div class="w-6" v-for="i in Array(level).fill(0)" v-memo="[level]"></div>
|
||||
<div class="relative w-6">
|
||||
<component :is="icon" class="size-icon icon-default"
|
||||
:class="{ 'text-gray-500/50': entry.cut }" />
|
||||
<div class="relative w-6" :class="[entry.cut ? 'text-gray-500/50' : 'icon-default']">
|
||||
<FolderIcon v-if="entry.resolvedType === 'd'" class="size-icon" />
|
||||
<DocumentIcon v-else class="size-icon" />
|
||||
<LinkIcon v-if="entry.type === 'l'" class="w-2 h-2 absolute right-0 bottom-0 text-default" />
|
||||
</div>
|
||||
<button class="z-10" v-if="directoryLike" @click.stop="toggleShowEntries" @mouseenter="hover = true"
|
||||
<button class="z-10 icon-default" v-if="entry.resolvedType === 'd'" @click.stop="toggleShowEntries" @mouseenter="hover = true"
|
||||
@mouseleave="hover = false">
|
||||
<ChevronDownIcon v-if="!showEntries" class="size-icon icon-default" />
|
||||
<ChevronUpIcon v-else class="size-icon icon-default" />
|
||||
<ChevronDownIcon v-if="!showEntries" class="size-icon" />
|
||||
<ChevronUpIcon v-else class="size-icon" />
|
||||
</button>
|
||||
<div v-html="entry.nameHTML" :title="entry.name"></div>
|
||||
<div v-if="entry.type === 'l'" class="inline-flex gap-1 items-center">
|
||||
<div class="inline relative">
|
||||
<ArrowNarrowRightIcon class="text-default size-icon-sm inline" />
|
||||
<XIcon v-if="entry.target?.broken"
|
||||
<XIcon v-if="entry.linkBroken"
|
||||
class="icon-danger size-icon-sm absolute inset-x-0 bottom-0" />
|
||||
</div>
|
||||
<div v-html="entry.target?.rawPathHTML ?? ''" :title="entry.target.rawPath"></div>
|
||||
<div v-html="entry.linkRawPathHTML ?? ''" :title="entry.linkRawPath"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute left-0 top-0 bottom-0 w-full max-w-[50vw]" @mouseup.stop
|
||||
@click.prevent="$emit('entryAction', 'toggleSelected', entry, $event)"
|
||||
@click.prevent="$emit('directoryViewAction', 'toggleSelected', entry, $event)"
|
||||
@dblclick="doubleClickCallback" @mouseenter="hover = true" @mouseleave="hover = false"
|
||||
ref="selectIntersectElement" />
|
||||
</td>
|
||||
@ -39,8 +39,8 @@
|
||||
<td v-if="settings?.directoryView?.cols?.size" class="font-mono text-right">{{
|
||||
entry.sizeHuman
|
||||
}}</td>
|
||||
<td v-if="settings?.directoryView?.cols?.ctime">{{
|
||||
entry.ctimeStr
|
||||
<td v-if="settings?.directoryView?.cols?.btime">{{
|
||||
entry.btimeStr
|
||||
}}</td>
|
||||
<td v-if="settings?.directoryView?.cols?.mtime">{{
|
||||
entry.mtimeStr
|
||||
@ -49,23 +49,25 @@
|
||||
entry.atimeStr
|
||||
}}</td>
|
||||
</tr>
|
||||
<component :is="DirectoryEntryList" v-if="directoryLike && showEntries" :host="host" :path="entry.path"
|
||||
<component :is="DirectoryEntryList" v-if="entry.resolvedType === 'd' && showEntries" :host="host" :path="entry.path"
|
||||
:isChild="true" :sortCallback="inheritedSortCallback" :searchFilterRegExp="searchFilterRegExp"
|
||||
@startProcessing="(...args) => $emit('startProcessing', ...args)"
|
||||
@stopProcessing="(...args) => $emit('stopProcessing', ...args)" @cancelShowEntries="showEntries = false"
|
||||
ref="directoryEntryListRef" :level="level + 1" :selectedCount="selectedCount"
|
||||
@entryAction="(...args) => $emit('entryAction', ...args)" />
|
||||
@browserAction="(...args) => $emit('browserAction', ...args)"
|
||||
@directoryViewAction="(...args) => $emit('directoryViewAction', ...args)" />
|
||||
</template>
|
||||
<div v-else class="select-none dir-entry flex flex-col items-center overflow-hidden dir-entry-width p-2"
|
||||
:class="{ '!bg-red-600/10': hover && !entry.selected, '!bg-red-600/20': hover && entry.selected, 'dir-entry-selected': entry.selected, '!border-t-transparent': suppressBorderT, '!border-b-transparent': suppressBorderB, '!border-l-transparent': suppressBorderL, '!border-r-transparent': suppressBorderR }">
|
||||
<div class="w-full" @dblclick="doubleClickCallback" @click.prevent="$emit('entryAction', 'toggleSelected', entry, $event)"
|
||||
<div class="w-full" @dblclick="doubleClickCallback" @click.prevent="$emit('directoryViewAction', 'toggleSelected', entry, $event)"
|
||||
@mouseup.stop @mouseenter="hover = true" @mouseleave="hover = false" ref="selectIntersectElement">
|
||||
<div class="relative w-full">
|
||||
<component :is="icon" class="icon-default w-full h-auto" :class="{ 'text-gray-500/50': entry.cut }" />
|
||||
<div :class="[directoryLike ? 'right-[15%] bottom-[25%]' : 'right-[25%] bottom-[15%]', 'inline absolute w-[20%]']"
|
||||
:title="`-> ${entry.target?.rawPath ?? '?'}`">
|
||||
<div class="relative w-full" :class="[entry.cut ? 'text-gray-500/50' : 'icon-default']">
|
||||
<FolderIcon v-if="entry.resolvedType === 'd'" class="w-full h-auto" />
|
||||
<DocumentIcon v-else class="w-full h-auto" />
|
||||
<div :class="[entry.resolvedType === 'd' ? 'right-[15%] bottom-[25%]' : 'right-[25%] bottom-[15%]', 'inline absolute w-[20%]']"
|
||||
:title="`-> ${entry.linkRawPath ?? '?'}`">
|
||||
<LinkIcon v-if="entry.type === 'l'"
|
||||
:class="[entry.target?.broken ? 'text-red-300 dark:text-red-800' : 'text-gray-100 dark:text-gray-900']" />
|
||||
:class="[entry.linkBroken ? 'text-red-300 dark:text-red-800' : 'text-gray-100 dark:text-gray-900']" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center w-full text-sm break-words"
|
||||
@ -77,7 +79,8 @@
|
||||
<div>
|
||||
<span v-if="level > 0">{{ entry.path.split('/').slice(-1 * (level + 1)).join('/') }}:</span>
|
||||
<span v-else>{{ entry.name }}:</span>
|
||||
{{ entry.mode.toString(8) }}, {{ entry.owner }}:{{ entry.group }}, {{ entry.sizeHuman }}
|
||||
{{ entry.mode.toString(8) }}, {{ entry.owner }}:{{ entry.group }}, {{ entry.sizeHuman }},
|
||||
modified: {{ entry.mtime }}
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
@ -85,7 +88,7 @@
|
||||
<script>
|
||||
import { ref, inject, watch, nextTick, onBeforeUnmount, onMounted, onActivated, onDeactivated, onUpdated } from 'vue';
|
||||
import { DocumentIcon, FolderIcon, LinkIcon, DocumentRemoveIcon, ArrowNarrowRightIcon, XIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/solid';
|
||||
import { settingsInjectionKey } from '../keys';
|
||||
import { notificationsInjectionKey, settingsInjectionKey } from '../keys';
|
||||
import DirectoryEntryList from './DirectoryEntryList.vue';
|
||||
import { escapeStringHTML } from '../functions/escapeStringHTML';
|
||||
|
||||
@ -109,26 +112,19 @@ export default {
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const settings = inject(settingsInjectionKey);
|
||||
const icon = ref(FolderIcon);
|
||||
const directoryLike = ref(false);
|
||||
const notifications = inject(notificationsInjectionKey);
|
||||
const showEntries = ref(false);
|
||||
const directoryEntryListRef = ref();
|
||||
const selectIntersectElement = ref();
|
||||
const hover = ref(false);
|
||||
|
||||
if (props.entry.type === 'd' || (props.entry.type === 'l' && props.entry.target?.type === 'd')) {
|
||||
icon.value = FolderIcon;
|
||||
directoryLike.value = true;
|
||||
} else {
|
||||
icon.value = DocumentIcon;
|
||||
directoryLike.value = false;
|
||||
}
|
||||
|
||||
const doubleClickCallback = () => {
|
||||
if (directoryLike.value) {
|
||||
emit('entryAction', 'cd', props.entry);
|
||||
if (props.entry.resolvedType === 'd') {
|
||||
emit('browserAction', 'cd', props.entry);
|
||||
} else if (props.entry.resolvedType === 'f') {
|
||||
emit('browserAction', 'openFilePrompt', props.entry);
|
||||
} else {
|
||||
emit('entryAction', 'openPrompt', props.entry);
|
||||
notifications.value.constructNotification('Cannot open or download file', `${props.entry.nameHTML} is a ${props.entry.resolvedTypeHuman}`, 'denied');
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,8 +158,6 @@ export default {
|
||||
|
||||
return {
|
||||
settings,
|
||||
icon,
|
||||
directoryLike,
|
||||
showEntries,
|
||||
directoryEntryListRef,
|
||||
doubleClickCallback,
|
||||
@ -192,7 +186,8 @@ export default {
|
||||
'startProcessing',
|
||||
'stopProcessing',
|
||||
'setEntryProp',
|
||||
'entryAction',
|
||||
'browserAction',
|
||||
'directoryViewAction',
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
@ -5,7 +5,8 @@
|
||||
@startProcessing="(...args) => $emit('startProcessing', ...args)"
|
||||
@stopProcessing="(...args) => $emit('stopProcessing', ...args)" ref="entryRefs" :level="level" :selectedCount="selectedCount"
|
||||
@setEntryProp="(prop, value) => entry[prop] = value"
|
||||
@entryAction="(...args) => $emit('entryAction', ...args)"
|
||||
@browserAction="(...args) => $emit('browserAction', ...args)"
|
||||
@directoryViewAction="(...args) => $emit('directoryViewAction', ...args)"
|
||||
:suppressBorderT="visibleEntries[index - cols]?.selected && !(visibleEntries[index - cols]?.dirOpen)"
|
||||
:suppressBorderB="visibleEntries[index + cols]?.selected && !(entry.dirOpen)"
|
||||
:suppressBorderL="settings.directoryView.view !== 'list' && (visibleEntries[index - 1]?.selected && (index) % cols !== 0)"
|
||||
@ -69,13 +70,9 @@ export default {
|
||||
const sortCallbackComputed = computed(() => {
|
||||
return (a, b) => {
|
||||
if (settings.directoryView?.separateDirs) {
|
||||
const checkA = a.type === 'l' ? (a.target?.type ?? null) : a.type;
|
||||
const checkB = b.type === 'l' ? (b.target?.type ?? null) : b.type;
|
||||
if (checkA === null || checkB === null)
|
||||
return 0;
|
||||
if (checkA === 'd' && checkB !== 'd')
|
||||
if (a.resolvedType === 'd' && b.resolvedType !== 'd')
|
||||
return -1;
|
||||
else if (checkA !== 'd' && checkB === 'd')
|
||||
else if (a.resolvedType !== 'd' && b.resolvedType === 'd')
|
||||
return 1;
|
||||
}
|
||||
return props.sortCallback(a, b);
|
||||
@ -231,7 +228,7 @@ export default {
|
||||
visibleEntries.value = entries.value.filter(entryFilterCallback);
|
||||
// nextTick(() => console.timeEnd('updateVisibleEntries-' + props.path));
|
||||
const _stats = visibleEntries.value.reduce((_stats, entry) => {
|
||||
if (entry.type === 'd' || (entry.type === 'l' && entry.target?.type === 'd'))
|
||||
if (entry.resolvedType === 'd')
|
||||
_stats.dirs++;
|
||||
else
|
||||
_stats.files++;
|
||||
@ -276,7 +273,8 @@ export default {
|
||||
'stopProcessing',
|
||||
'cancelShowEntries',
|
||||
'deselectAll',
|
||||
'entryAction',
|
||||
'browserAction',
|
||||
'directoryViewAction',
|
||||
'tallySelected',
|
||||
]
|
||||
}
|
||||
|
@ -64,10 +64,10 @@
|
||||
</template>
|
||||
<template #tbody>
|
||||
<DirectoryEntryList :host="host" :path="path" :sortCallback="sortCallback"
|
||||
:searchFilterRegExp="searchFilterRegExp" @cd="(...args) => $emit('cd', ...args)"
|
||||
@edit="(...args) => $emit('edit', ...args)"
|
||||
:searchFilterRegExp="searchFilterRegExp"
|
||||
@startProcessing="processing++" @stopProcessing="processing--"
|
||||
@entryAction="handleEntryAction" ref="directoryEntryListRef"
|
||||
@browserAction="(...args) => $emit('browserAction', ...args)"
|
||||
@directoryViewAction="handleAction" ref="directoryEntryListRef"
|
||||
@tallySelected="tallySelected"
|
||||
:level="0" :cols="1" :selectedCount="selectedCount" />
|
||||
</template>
|
||||
@ -109,7 +109,8 @@
|
||||
<DirectoryEntryList :host="host" :path="path" :sortCallback="sortCallback"
|
||||
:searchFilterRegExp="searchFilterRegExp"
|
||||
@startProcessing="processing++" @stopProcessing="processing--"
|
||||
@entryAction="handleEntryAction"
|
||||
@browserAction="(...args) => $emit('browserAction', ...args)"
|
||||
@directoryViewAction="handleAction"
|
||||
@tallySelected="tallySelected" ref="directoryEntryListRef"
|
||||
:level="0" :cols="cols" :selectedCount="selectedCount" />
|
||||
</div>
|
||||
@ -291,7 +292,7 @@ export default {
|
||||
let destination;
|
||||
if (selected.length === 1) {
|
||||
destination = selected[0];
|
||||
if (destination.type !== 'd' && !(destination.type === 'l' && destination.target.type === 'd')) {
|
||||
if (destination.resolvedType !== 'd') {
|
||||
notifications.value.constructNotification("Paste Failed", 'Cannot paste to non-directory.', 'error');
|
||||
break;
|
||||
}
|
||||
@ -326,13 +327,13 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
const handleEntryAction = (action, entry, event, ...args) => {
|
||||
const handleAction = (action, ...args) => {
|
||||
switch (action) {
|
||||
case 'toggleSelected':
|
||||
toggleSelected(entry, event);
|
||||
toggleSelected(...args);
|
||||
break;
|
||||
default:
|
||||
emit('entryAction', action, entry, event, ...args);
|
||||
console.error('Unknown directoryViewAction:', action, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -377,7 +378,7 @@ export default {
|
||||
selectAll,
|
||||
deselectAll,
|
||||
selectRectangle,
|
||||
handleEntryAction,
|
||||
handleAction,
|
||||
tallySelected,
|
||||
}
|
||||
},
|
||||
@ -389,7 +390,7 @@ export default {
|
||||
DragSelectArea,
|
||||
},
|
||||
emits: [
|
||||
'entryAction',
|
||||
'browserAction',
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { useSpawn, errorString } from "@45drives/cockpit-helpers";
|
||||
import { UNIT_SEPARATOR, RECORD_SEPARATOR } from "../constants";
|
||||
import { szudzikPair } from "./szudzikPair";
|
||||
import { szudzikPair, hashString } from "./szudzikPair";
|
||||
import { escapeStringHTML } from "./escapeStringHTML";
|
||||
|
||||
/**
|
||||
* Get list of directory entry objects from list of directory entry names
|
||||
*
|
||||
* find -H path -maxdepth 1 -mindepth 1 -printf '%D:%i%f:%m:%M:%s:%u:%g:%B@:%T@:%A@:%y:%Y:%l\n'
|
||||
* find -H path -maxdepth 1 -mindepth 1 -printf '%D:%i%f:%p:%m:%M:%s:%u:%g:%B@:%T@:%A@:%y:%Y:%l\n'
|
||||
*
|
||||
* @param {String} cwd - Working directory to run find in
|
||||
* @param {String} host - Host to run find on
|
||||
@ -25,7 +25,7 @@ async function getDirEntryObjects(cwd, host, extraFindArgs = [], failCallback =
|
||||
'%s', // size
|
||||
'%u', // owner
|
||||
'%g', // group
|
||||
'%B@', // ctime
|
||||
'%B@', // btime
|
||||
'%T@', // mtime
|
||||
'%A@', // atime
|
||||
'%y', // type
|
||||
@ -96,17 +96,31 @@ async function getDirEntryStats(cwd, host, outputFormat, extraFindArguments = []
|
||||
* @returns {DirectoryEntryObj[]}
|
||||
*/
|
||||
function parseRawEntryStats(records, cwd, host, failCallback, byteFormatter = cockpit.format_bytes) {
|
||||
const typeHumanLUT = {
|
||||
f: 'regular file',
|
||||
d: 'directory',
|
||||
l: 'symbolic link',
|
||||
c: 'character device',
|
||||
b: 'block device',
|
||||
p: 'FIFO (named pipe)',
|
||||
s: 'socket',
|
||||
U: 'unknown file type',
|
||||
L: 'broken link (loop)',
|
||||
N: 'broken link (non-existent)',
|
||||
D: 'door (Solaris)', // probably don't need this, but why not right?
|
||||
}
|
||||
const hostHash = hashString(host);
|
||||
return records.map(fields => {
|
||||
try {
|
||||
let [devId, inode, name, path, mode, modeStr, size, owner, group, ctime, mtime, atime, type, symlinkTargetType, symlinkTargetName] = fields;
|
||||
[size, ctime, mtime, atime] = [size, ctime, mtime, atime].map(num => parseInt(num));
|
||||
let [devId, inode, name, path, mode, modeStr, size, owner, group, btime, mtime, atime, type, symlinkTargetType, symlinkTargetName] = fields;
|
||||
[size, btime, mtime, atime] = [size, btime, mtime, atime].map(num => parseInt(num));
|
||||
[devId, inode] = [devId, inode].map(num => BigInt(num));
|
||||
[ctime, mtime, atime] = [ctime, mtime, atime].map(ts => (ts && ts > 0) ? new Date(ts * 1000) : null);
|
||||
let [ctimeStr, mtimeStr, atimeStr] = [ctime, mtime, atime].map(date => date?.toLocaleString() ?? '-');
|
||||
[btime, mtime, atime] = [btime, mtime, atime].map(ts => (ts && ts > 0) ? new Date(ts * 1000) : null);
|
||||
let [btimeStr, mtimeStr, atimeStr] = [btime, mtime, atime].map(date => date?.toLocaleString() ?? '-');
|
||||
let [nameHTML, symlinkTargetNameHTML] = [name, symlinkTargetName].map(escapeStringHTML);
|
||||
mode = parseInt(mode, 8);
|
||||
return {
|
||||
uniqueId: szudzikPair(host, devId, inode),
|
||||
uniqueId: szudzikPair(hostHash, devId, inode),
|
||||
devId,
|
||||
inode,
|
||||
name,
|
||||
@ -118,20 +132,21 @@ function parseRawEntryStats(records, cwd, host, failCallback, byteFormatter = co
|
||||
sizeHuman: byteFormatter(size, 1000).replace(/(?<!B)$/, ' B'),
|
||||
owner,
|
||||
group,
|
||||
ctime,
|
||||
btime,
|
||||
mtime,
|
||||
atime,
|
||||
ctimeStr,
|
||||
btimeStr,
|
||||
mtimeStr,
|
||||
atimeStr,
|
||||
type,
|
||||
target: {
|
||||
type: symlinkTargetType,
|
||||
rawPath: symlinkTargetName,
|
||||
rawPathHTML: symlinkTargetNameHTML,
|
||||
path: type === 'l' ? symlinkTargetName.replace(/^(?!\/)/, `${cwd}/`) : '',
|
||||
broken: ['L', 'N', '?'].includes(symlinkTargetType), // L: loop N: nonexistent ?: error
|
||||
},
|
||||
typeHuman: typeHumanLUT[type],
|
||||
linkType: symlinkTargetType || null,
|
||||
linkRawPath: symlinkTargetName || null,
|
||||
linkRawPathHTML: symlinkTargetNameHTML || null,
|
||||
linkBroken: ['L', 'N', '?'].includes(symlinkTargetType),
|
||||
resolvedPath: type === 'l' ? symlinkTargetName.replace(/^(?!\/)/, `${cwd}/`) : path,
|
||||
resolvedType: symlinkTargetType || type,
|
||||
resolvedTypeHuman: typeHumanLUT[symlinkTargetType || type],
|
||||
selected: false,
|
||||
host,
|
||||
cut: false,
|
||||
@ -177,17 +192,21 @@ export default getDirEntryObjects;
|
||||
* @property {String} sizeHuman - Human readable size
|
||||
* @property {String} owner - File owner
|
||||
* @property {String} group - File group
|
||||
* @property {Date} ctime - Creation time
|
||||
* @property {Date} btime - Creation time
|
||||
* @property {Date} mtime - Last Modified time
|
||||
* @property {Date} atime - Last Accessed time
|
||||
* @property {String} ctimeStr - Creation time string
|
||||
* @property {String} btimeStr - Creation time string
|
||||
* @property {String} mtimeStr - Last Modified time string
|
||||
* @property {String} atimeStr - Last Accessed time string
|
||||
* @property {String} type - Type of inode returned by find
|
||||
* @property {Object} target - Object for symlink target
|
||||
* @property {String} target.rawPath - Symlink target path directly grabbed from find
|
||||
* @property {String} target.path - Resolved symlink target path
|
||||
* @property {Boolean} target.broken - Whether or not the link is broken
|
||||
* @property {String} type - Type of inode returned by find, single character from mode string
|
||||
* @property {String} typeHuman - Type of inode returned by find, human readable string
|
||||
* @property {String|null} linkType - Type of inode link is pointing to or null if not link
|
||||
* @property {String|null} linkRawPath - Target of link or null if not link
|
||||
* @property {String|null} linkRawPathHTML - HTML formatted target of link or null if not link
|
||||
* @property {Boolean} linkBroken - True if is symlink and broken, otherwise false
|
||||
* @property {String} resolvedPath - Path to file or path to target if symlink
|
||||
* @property {String} resolvedType - Type of file or type of target if symlink, single character from mode string
|
||||
* @property {String} resolvedTypeHuman - Type of file or type of target if symlink, human readable string
|
||||
* @property {Boolean} selected - Whether or not the user has selected this entry in the browser
|
||||
* @property {String} host - host that owns entry
|
||||
* @property {Boolean} cut - whether or not the file is going to be cut
|
||||
|
@ -68,21 +68,21 @@
|
||||
<div class="grow overflow-hidden">
|
||||
<DirectoryView :host="pathHistory.current()?.host" :path="pathHistory.current()?.path"
|
||||
:searchFilterRegExp="searchFilterRegExp" @cd="path => cd({ path })" @edit="openEditor"
|
||||
@entryAction="handleEntryAction"
|
||||
@browserAction="handleAction"
|
||||
ref="directoryViewRef" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ModalPopup :showModal="openPrompt.show" :headerText="openPrompt.entry?.name ?? 'NULL'" @close="() => openPrompt.close()">
|
||||
<ModalPopup :showModal="openFilePromptModal.show" :headerText="openFilePromptModal.entry?.name ?? 'NULL'" @close="() => openFilePromptModal.close()">
|
||||
What would you like to do with this file?
|
||||
<template #footer>
|
||||
<button type="button" class="btn btn-secondary" @click="() => openPrompt.close()">
|
||||
<button type="button" class="btn btn-secondary" @click="() => openFilePromptModal.close()">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" @click="() => openEditor(openPrompt.entry.path)">
|
||||
<button type="button" class="btn btn-primary" @click="() => openFilePromptModal.action('edit') ">
|
||||
Open for editing
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary">
|
||||
<button type="button" class="btn btn-primary" @click="() => openFilePromptModal.action('download') ">
|
||||
Download
|
||||
</button>
|
||||
</template>
|
||||
@ -112,6 +112,7 @@ import { notificationsInjectionKey, pathHistoryInjectionKey, lastPathStorageKey,
|
||||
import { ArrowLeftIcon, ArrowRightIcon, ArrowUpIcon, RefreshIcon, ChevronDownIcon, SearchIcon, SunIcon, MoonIcon, EyeIcon, EyeOffIcon, ViewListIcon, ViewGridIcon } from '@heroicons/vue/solid';
|
||||
import IconToggle from '../components/IconToggle.vue';
|
||||
import ModalPopup from '../components/ModalPopup.vue';
|
||||
import { fileDownload } from '@45drives/cockpit-helpers';
|
||||
|
||||
const encodePartial = (string) =>
|
||||
encodeURIComponent(string)
|
||||
@ -160,19 +161,23 @@ export default {
|
||||
forwardHistoryDropdown.showDropdown = false;
|
||||
}
|
||||
});
|
||||
const openPrompt = reactive({
|
||||
const openFilePromptModal = reactive({
|
||||
show: false,
|
||||
entry: null,
|
||||
resetTimeoutHandle: null,
|
||||
open: (entry) => {
|
||||
clearTimeout(openPrompt.resetTimeoutHandle);
|
||||
openPrompt.entry = entry;
|
||||
openPrompt.show = true;
|
||||
clearTimeout(openFilePromptModal.resetTimeoutHandle);
|
||||
openFilePromptModal.entry = entry;
|
||||
openFilePromptModal.show = true;
|
||||
},
|
||||
close: () => {
|
||||
openPrompt.show = false;
|
||||
openPrompt.resetTimeoutHandle = setTimeout(() => openPrompt.resetTimeoutHandle = openPrompt.entry = null, 500);
|
||||
openFilePromptModal.show = false;
|
||||
openFilePromptModal.resetTimeoutHandle = setTimeout(() => openFilePromptModal.resetTimeoutHandle = openFilePromptModal.entry = null, 500);
|
||||
},
|
||||
action: (action) => {
|
||||
handleAction(action, openFilePromptModal.entry);
|
||||
openFilePromptModal.close();
|
||||
}
|
||||
});
|
||||
|
||||
const cd = ({ path, host }) => {
|
||||
@ -193,25 +198,39 @@ export default {
|
||||
cd({path: pathHistory.current().path + '/..'});
|
||||
}
|
||||
|
||||
const openEditor = (path) => {
|
||||
router.push(`/edit/${pathHistory.current().host}${encodePartial(path)}`);
|
||||
const openEditor = ({ path, host }) => {
|
||||
const newHost = host ?? (pathHistory.current().host);
|
||||
const newPath = encodePartial(path ?? (pathHistory.current().path));
|
||||
router.push(`/edit/${newHost}${newPath}`);
|
||||
}
|
||||
|
||||
const download = ({ path, name, host }) => {
|
||||
fileDownload(path, name, host);
|
||||
console.log('download', `${host}:${path}`);
|
||||
}
|
||||
|
||||
const openFilePrompt = (entry) => {
|
||||
openFilePromptModal.open(entry);
|
||||
}
|
||||
|
||||
const getSelected = () => directoryViewRef.value?.getSelected?.() ?? [];
|
||||
|
||||
const handleEntryAction = (action, entry, event) => {
|
||||
const handleAction = (action, ...args) => {
|
||||
switch (action) {
|
||||
case 'cd':
|
||||
cd({path: entry.path});
|
||||
cd(...args);
|
||||
break;
|
||||
case 'edit':
|
||||
openEditor({path: entry.path})
|
||||
openEditor(...args);
|
||||
break;
|
||||
case 'openPrompt':
|
||||
openPrompt.open(entry);
|
||||
case 'openFilePrompt':
|
||||
openFilePrompt(...args);
|
||||
break;
|
||||
case 'download':
|
||||
download(...args);
|
||||
break;
|
||||
default:
|
||||
console.error('Unknown entryAction:', action, entry);
|
||||
console.error('Unknown browserAction:', action, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -247,14 +266,16 @@ export default {
|
||||
searchFilterRegExp,
|
||||
backHistoryDropdown,
|
||||
forwardHistoryDropdown,
|
||||
openPrompt,
|
||||
openFilePromptModal,
|
||||
cd,
|
||||
back,
|
||||
forward,
|
||||
up,
|
||||
openEditor,
|
||||
download,
|
||||
openFilePrompt,
|
||||
getSelected,
|
||||
handleEntryAction,
|
||||
handleAction,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user