From 06c6a04d591207bb8c1a27d75a84456e7d6281c2 Mon Sep 17 00:00:00 2001 From: joshuaboud Date: Fri, 17 Jun 2022 13:39:37 -0300 Subject: [PATCH] flatten entry object, make actions specific to component --- navigator/src/components/DirectoryEntry.vue | 69 +++++++++---------- .../src/components/DirectoryEntryList.vue | 16 ++--- navigator/src/components/DirectoryView.vue | 21 +++--- navigator/src/functions/getDirEntryObjects.js | 67 +++++++++++------- navigator/src/views/Browser.vue | 63 +++++++++++------ 5 files changed, 135 insertions(+), 101 deletions(-) diff --git a/navigator/src/components/DirectoryEntry.vue b/navigator/src/components/DirectoryEntry.vue index 26b5114..01edff3 100644 --- a/navigator/src/components/DirectoryEntry.vue +++ b/navigator/src/components/DirectoryEntry.vue @@ -5,28 +5,28 @@
-
- +
+ +
-
-
-
+
@@ -39,8 +39,8 @@ {{ entry.sizeHuman }} - {{ - entry.ctimeStr + {{ + entry.btimeStr }} {{ entry.mtimeStr @@ -49,23 +49,25 @@ entry.atimeStr }} - + @browserAction="(...args) => $emit('browserAction', ...args)" + @directoryViewAction="(...args) => $emit('directoryViewAction', ...args)" />
-
-
- -
+
+ + +
+ :class="[entry.linkBroken ? 'text-red-300 dark:text-red-800' : 'text-gray-100 dark:text-gray-900']" />
{{ entry.path.split('/').slice(-1 * (level + 1)).join('/') }}: {{ entry.name }}: - {{ entry.mode.toString(8) }}, {{ entry.owner }}:{{ entry.group }}, {{ entry.sizeHuman }} + {{ entry.mode.toString(8) }}, {{ entry.owner }}:{{ entry.group }}, {{ entry.sizeHuman }}, + modified: {{ entry.mtime }}
@@ -85,7 +88,7 @@ diff --git a/navigator/src/components/DirectoryEntryList.vue b/navigator/src/components/DirectoryEntryList.vue index 9ae35ef..cc1f992 100644 --- a/navigator/src/components/DirectoryEntryList.vue +++ b/navigator/src/components/DirectoryEntryList.vue @@ -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', ] } diff --git a/navigator/src/components/DirectoryView.vue b/navigator/src/components/DirectoryView.vue index 0a87d90..4142b30 100644 --- a/navigator/src/components/DirectoryView.vue +++ b/navigator/src/components/DirectoryView.vue @@ -64,10 +64,10 @@ @@ -109,7 +109,8 @@
@@ -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', ] } diff --git a/navigator/src/functions/getDirEntryObjects.js b/navigator/src/functions/getDirEntryObjects.js index fe60e43..89a98f1 100644 --- a/navigator/src/functions/getDirEntryObjects.js +++ b/navigator/src/functions/getDirEntryObjects.js @@ -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(/(?
- + What would you like to do with this file? @@ -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: {