mirror of
https://github.com/45Drives/cockpit-navigator.git
synced 2025-07-31 01:24:37 +02:00
add copy/cut/paste to cxt menu and tweak position
This commit is contained in:
parent
685cdeddc3
commit
1847ecc47b
@ -16,23 +16,33 @@ If not, see <https://www.gnu.org/licenses/>.
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="show"
|
||||||
|
class="fixed inset-0 bg-transparent"
|
||||||
|
tabindex="-1"
|
||||||
|
@click="show = false"
|
||||||
|
@contextmenu.prevent="show = false"
|
||||||
|
@keypress="show = false"
|
||||||
|
></div>
|
||||||
<transition
|
<transition
|
||||||
enter-active-class="origin-top-left transition ease-out duration-100"
|
enter-active-class="transition ease-out duration-100"
|
||||||
enter-from-class="transform opacity-0 scale-95"
|
enter-from-class="transform opacity-0 scale-95"
|
||||||
enter-to-class="transform opacity-100 scale-100"
|
enter-to-class="transform opacity-100 scale-100"
|
||||||
leave-active-class="origin-top-left transition ease-in duration-75"
|
leave-active-class="transition ease-in duration-75"
|
||||||
leave-from-class="transform opacity-100 scale-100"
|
leave-from-class="transform opacity-100 scale-100"
|
||||||
leave-to-class="transform opacity-0 scale-95"
|
leave-to-class="transform opacity-0 scale-95"
|
||||||
|
@enter="setPosition"
|
||||||
@after-leave="reset"
|
@after-leave="reset"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="show"
|
v-if="show"
|
||||||
class="fixed inset-0 bg-transparent"
|
class="fixed z-20 max-w-sm flex flex-col items-stretch bg-default shadow-lg divide-y divide-default"
|
||||||
|
tabindex="-1"
|
||||||
@click="show = false"
|
@click="show = false"
|
||||||
@contextmenu.prevent="show = false"
|
@contextmenu.prevent="show = false"
|
||||||
|
@keypress="show = false"
|
||||||
|
ref="contextMenuRef"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="fixed z-20 max-w-sm flex flex-col items-stretch bg-default shadow-lg divide-y divide-default position-contextmenu">
|
|
||||||
<div class="flex items-stretch">
|
<div class="flex items-stretch">
|
||||||
<button
|
<button
|
||||||
:disabled="!pathHistory?.backAllowed()"
|
:disabled="!pathHistory?.backAllowed()"
|
||||||
@ -81,13 +91,29 @@ If not, see <https://www.gnu.org/licenses/>.
|
|||||||
</div>
|
</div>
|
||||||
<template v-if="selection.length === 0">
|
<template v-if="selection.length === 0">
|
||||||
<!-- Current directory actions -->
|
<!-- Current directory actions -->
|
||||||
|
<div
|
||||||
|
v-if="clipboard.content.length"
|
||||||
|
class="flex flex-col items-stretch"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="context-menu-button"
|
||||||
|
@click="$emit('directoryViewAction', 'paste', currentDirEntry)"
|
||||||
|
>
|
||||||
|
<ClipboardIcon class="size-icon icon-default" />
|
||||||
|
<span>
|
||||||
|
Paste {{ clipboard.content.length }}
|
||||||
|
item{{ clipboard.content.length > 1 ? 's' : '' }}
|
||||||
|
here
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="flex flex-col items-stretch">
|
<div class="flex flex-col items-stretch">
|
||||||
<button
|
<button
|
||||||
class="context-menu-button"
|
class="context-menu-button"
|
||||||
@click="$emit('browserAction', 'download', currentDirEntry)"
|
@click="$emit('browserAction', 'download', currentDirEntry)"
|
||||||
>
|
>
|
||||||
<FolderDownloadIcon class="size-icon icon-default" />
|
<FolderDownloadIcon class="size-icon icon-default" />
|
||||||
<span>Zip and download directory</span>
|
<span>Zip and download current directory</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-stretch">
|
<div class="flex flex-col items-stretch">
|
||||||
@ -97,17 +123,31 @@ If not, see <https://www.gnu.org/licenses/>.
|
|||||||
@click="$emit('browserAction', 'editPermissions', currentDirEntry)"
|
@click="$emit('browserAction', 'editPermissions', currentDirEntry)"
|
||||||
>
|
>
|
||||||
<KeyIcon class="size-icon icon-default" />
|
<KeyIcon class="size-icon icon-default" />
|
||||||
<span>Edit permissions</span>
|
<span>Edit permissions of current directory</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="selection.length === 1">
|
<template v-else-if="selection.length === 1">
|
||||||
<!-- Single entry selection actions -->
|
<!-- Single entry selection actions -->
|
||||||
<div
|
<template v-if="selection[0]?.resolvedType === 'f'">
|
||||||
v-if="selection[0]?.resolvedType === 'f'"
|
|
||||||
class="flex flex-col items-stretch"
|
|
||||||
>
|
|
||||||
<!-- regular file actions -->
|
<!-- regular file actions -->
|
||||||
|
<div class="flex flex-col items-stretch">
|
||||||
|
<button
|
||||||
|
class="context-menu-button"
|
||||||
|
@click="$emit('directoryViewAction', 'cut', selection[0])"
|
||||||
|
>
|
||||||
|
<ScissorsIcon class="size-icon icon-default" />
|
||||||
|
<span>Cut file</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="context-menu-button"
|
||||||
|
@click="$emit('directoryViewAction', 'copy', selection[0])"
|
||||||
|
>
|
||||||
|
<ClipboardCopyIcon class="size-icon icon-default" />
|
||||||
|
<span>Copy file</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-stretch">
|
||||||
<button
|
<button
|
||||||
class="context-menu-button"
|
class="context-menu-button"
|
||||||
@click="$emit('browserAction', 'edit', selection[0])"
|
@click="$emit('browserAction', 'edit', selection[0])"
|
||||||
@ -130,11 +170,37 @@ If not, see <https://www.gnu.org/licenses/>.
|
|||||||
<span>Zip and download</span>
|
<span>Zip and download</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
</template>
|
||||||
v-else-if="selection[0]?.resolvedType === 'd'"
|
<template v-else-if="selection[0]?.resolvedType === 'd'">
|
||||||
class="flex flex-col items-stretch"
|
|
||||||
>
|
|
||||||
<!-- directory actions -->
|
<!-- directory actions -->
|
||||||
|
<div class="flex flex-col items-stretch">
|
||||||
|
<button
|
||||||
|
class="context-menu-button"
|
||||||
|
@click="$emit('directoryViewAction', 'cut', selection[0])"
|
||||||
|
>
|
||||||
|
<ScissorsIcon class="size-icon icon-default" />
|
||||||
|
<span>Cut directory</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="context-menu-button"
|
||||||
|
@click="$emit('directoryViewAction', 'copy', selection[0])"
|
||||||
|
>
|
||||||
|
<ClipboardCopyIcon class="size-icon icon-default" />
|
||||||
|
<span>Copy directory</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="clipboard.content.length"
|
||||||
|
class="context-menu-button"
|
||||||
|
@click="$emit('directoryViewAction', 'paste', selection[0])"
|
||||||
|
>
|
||||||
|
<ClipboardIcon class="size-icon icon-default" />
|
||||||
|
<span>
|
||||||
|
Paste {{ clipboard.content.length }}
|
||||||
|
item{{ clipboard.content.length > 1 ? 's' : '' }} into directory
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-stretch">
|
||||||
<button
|
<button
|
||||||
class="context-menu-button"
|
class="context-menu-button"
|
||||||
@click="$emit('browserAction', 'cd', selection[0])"
|
@click="$emit('browserAction', 'cd', selection[0])"
|
||||||
@ -150,6 +216,7 @@ If not, see <https://www.gnu.org/licenses/>.
|
|||||||
<span>Zip and download directory</span>
|
<span>Zip and download directory</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
<div
|
<div
|
||||||
v-if="selection[0]?.type === 'l'"
|
v-if="selection[0]?.type === 'l'"
|
||||||
class="flex flex-col items-stretch"
|
class="flex flex-col items-stretch"
|
||||||
@ -185,13 +252,29 @@ If not, see <https://www.gnu.org/licenses/>.
|
|||||||
class="context-menu-button"
|
class="context-menu-button"
|
||||||
@click="$emit('browserAction', 'delete', selection[0])"
|
@click="$emit('browserAction', 'delete', selection[0])"
|
||||||
>
|
>
|
||||||
<TrashIcon class="size-icon icon-default" />
|
<TrashIcon class="size-icon icon-danger" />
|
||||||
<span>Delete</span>
|
<span>Delete</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<tempalte v-else>
|
<template v-else>
|
||||||
<!-- Multi-entry selection actions -->
|
<!-- Multi-entry selection actions -->
|
||||||
|
<div class="flex flex-col items-stretch">
|
||||||
|
<button
|
||||||
|
class="context-menu-button"
|
||||||
|
@click="$emit('directoryViewAction', 'cut', [...selection])"
|
||||||
|
>
|
||||||
|
<ScissorsIcon class="size-icon icon-default" />
|
||||||
|
<span>Cut {{ selection.length }} items</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="context-menu-button"
|
||||||
|
@click="$emit('directoryViewAction', 'copy', [...selection])"
|
||||||
|
>
|
||||||
|
<ClipboardCopyIcon class="size-icon icon-default" />
|
||||||
|
<span>Copy {{ selection.length }} items</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="flex flex-col items-stretch">
|
<div class="flex flex-col items-stretch">
|
||||||
<button
|
<button
|
||||||
class="context-menu-button"
|
class="context-menu-button"
|
||||||
@ -204,18 +287,17 @@ If not, see <https://www.gnu.org/licenses/>.
|
|||||||
class="context-menu-button"
|
class="context-menu-button"
|
||||||
@click="$emit('browserAction', 'delete', [...selection])"
|
@click="$emit('browserAction', 'delete', [...selection])"
|
||||||
>
|
>
|
||||||
<TrashIcon class="size-icon-sm icon-default" />
|
<TrashIcon class="size-icon-sm icon-danger" />
|
||||||
<span>Delete {{ selection.length }} items</span>
|
<span>Delete {{ selection.length }} items</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</tempalte>
|
</template>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { inject, ref, computed } from 'vue'
|
import { inject, ref, computed, nextTick } from 'vue'
|
||||||
import {
|
import {
|
||||||
ArrowLeftIcon,
|
ArrowLeftIcon,
|
||||||
ArrowRightIcon,
|
ArrowRightIcon,
|
||||||
@ -231,8 +313,11 @@ import {
|
|||||||
PencilIcon,
|
PencilIcon,
|
||||||
KeyIcon,
|
KeyIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
|
ClipboardIcon,
|
||||||
|
ClipboardCopyIcon,
|
||||||
|
ScissorsIcon,
|
||||||
} from '@heroicons/vue/solid';
|
} from '@heroicons/vue/solid';
|
||||||
import { pathHistoryInjectionKey } from '../keys';
|
import { pathHistoryInjectionKey, clipboardInjectionKey } from '../keys';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
@ -240,13 +325,18 @@ export default {
|
|||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const pathHistory = inject(pathHistoryInjectionKey);
|
const pathHistory = inject(pathHistoryInjectionKey);
|
||||||
|
const clipboard = inject(clipboardInjectionKey);
|
||||||
const show = ref();
|
const show = ref();
|
||||||
const event = ref();
|
const event = ref();
|
||||||
const selection = ref();
|
const selection = ref();
|
||||||
const currentDirEntry = computed(() => ({
|
const currentDirEntry = computed(() => ({
|
||||||
...props.currentPath,
|
...props.currentPath,
|
||||||
name: `Current directory (${props.currentPath.path.split('/').pop()})`
|
name: `Current directory (${props.currentPath.path.split('/').pop()})`,
|
||||||
|
type: 'd',
|
||||||
|
resolvedType: 'd',
|
||||||
|
resolvedPath: props.currentPath.path,
|
||||||
}));
|
}));
|
||||||
|
const contextMenuRef = ref();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the context menu
|
* Open the context menu
|
||||||
@ -260,6 +350,30 @@ export default {
|
|||||||
show.value = true;
|
show.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setPosition = (el, done) => {
|
||||||
|
// const { width: menuWidth, height: menuHeight } = el.getBoundingClientRect();
|
||||||
|
const menuWidth = el.scrollWidth;
|
||||||
|
const menuHeight = el.scrollHeight;
|
||||||
|
let x = event.value.clientX;
|
||||||
|
let y = event.value.clientY;
|
||||||
|
let origin;
|
||||||
|
if (y + menuHeight > window.innerHeight) {
|
||||||
|
y -= menuHeight;
|
||||||
|
origin = 'bottom';
|
||||||
|
} else {
|
||||||
|
origin = 'top';
|
||||||
|
}
|
||||||
|
if (x + menuWidth > window.innerWidth) {
|
||||||
|
x -= menuWidth;
|
||||||
|
origin += ' right';
|
||||||
|
} else {
|
||||||
|
origin += ' left';
|
||||||
|
}
|
||||||
|
el.style.left = `${x}px`;
|
||||||
|
el.style.top = `${y}px`;
|
||||||
|
el.style.transformOrigin = origin;
|
||||||
|
}
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
event.value = null;
|
event.value = null;
|
||||||
selection.value = [];
|
selection.value = [];
|
||||||
@ -268,12 +382,15 @@ export default {
|
|||||||
return {
|
return {
|
||||||
// data
|
// data
|
||||||
pathHistory,
|
pathHistory,
|
||||||
|
clipboard,
|
||||||
show,
|
show,
|
||||||
event,
|
event,
|
||||||
selection,
|
selection,
|
||||||
currentDirEntry,
|
currentDirEntry,
|
||||||
|
contextMenuRef,
|
||||||
// methods
|
// methods
|
||||||
open,
|
open,
|
||||||
|
setPosition,
|
||||||
reset,
|
reset,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -292,22 +409,21 @@ export default {
|
|||||||
PencilIcon,
|
PencilIcon,
|
||||||
KeyIcon,
|
KeyIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
|
ClipboardIcon,
|
||||||
|
ClipboardCopyIcon,
|
||||||
|
ScissorsIcon,
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
'hide',
|
'hide',
|
||||||
'browserAction',
|
'browserAction',
|
||||||
|
'directoryViewAction',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
div.position-contextmenu {
|
|
||||||
top: v-bind("`${event?.clientY ?? 0}px`");
|
|
||||||
left: v-bind("`${event?.clientX ?? 0}px`");
|
|
||||||
}
|
|
||||||
|
|
||||||
button.context-menu-button {
|
button.context-menu-button {
|
||||||
@apply text-default font-normal pl-1 pr-2 text-sm text-left flex items-center gap-1;
|
@apply text-default font-normal pl-1 pr-2 py-1 text-sm text-left flex items-center gap-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.context-menu-button:hover {
|
button.context-menu-button:hover {
|
||||||
|
@ -26,7 +26,7 @@ If not, see <https://www.gnu.org/licenses/>.
|
|||||||
class="h-full"
|
class="h-full"
|
||||||
@selectRectangle="selectRectangle"
|
@selectRectangle="selectRectangle"
|
||||||
@mouseup.exact="deselectAll"
|
@mouseup.exact="deselectAll"
|
||||||
@contextmenu.prevent="$emit('browserAction', 'contextMenu', $event)"
|
@contextmenu.prevent="contextMenuCallback"
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
:key="host + path"
|
:key="host + path"
|
||||||
@ -36,6 +36,7 @@ If not, see <https://www.gnu.org/licenses/>.
|
|||||||
stickyHeaders
|
stickyHeaders
|
||||||
noShrink
|
noShrink
|
||||||
noShrinkHeight="h-full"
|
noShrinkHeight="h-full"
|
||||||
|
class="border-x-0"
|
||||||
>
|
>
|
||||||
<template #thead>
|
<template #thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -249,6 +250,7 @@ import LoadingSpinner from './LoadingSpinner.vue';
|
|||||||
import SortCallbackButton from './SortCallbackButton.vue';
|
import SortCallbackButton from './SortCallbackButton.vue';
|
||||||
import DirectoryEntryList from './DirectoryEntryList.vue';
|
import DirectoryEntryList from './DirectoryEntryList.vue';
|
||||||
import DragSelectArea from './DragSelectArea.vue';
|
import DragSelectArea from './DragSelectArea.vue';
|
||||||
|
import { commonPath } from '../functions/commonPath';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
@ -331,7 +333,7 @@ export default {
|
|||||||
selectedCount.value = gatherEntries().map(entry => entry.selected = true).length ?? 0;
|
selectedCount.value = gatherEntries().map(entry => entry.selected = true).length ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deselectAll = (event) => {
|
const deselectAll = () => {
|
||||||
gatherEntries([], false).map(entry => entry.selected = false);
|
gatherEntries([], false).map(entry => entry.selected = false);
|
||||||
selectedCount.value = 0;
|
selectedCount.value = 0;
|
||||||
}
|
}
|
||||||
@ -353,11 +355,64 @@ export default {
|
|||||||
tallySelected();
|
tallySelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unCutEntries = () => gatherEntries([], false).map(entry => entry.cut = false);
|
||||||
|
|
||||||
|
const clipboardStore = (items, cut = false, append = false) => {
|
||||||
|
if (!append)
|
||||||
|
unCutEntries();
|
||||||
|
const newContent = items.map(entry => {
|
||||||
|
entry.cut = cut;
|
||||||
|
return {
|
||||||
|
uniqueId: entry.uniqueId,
|
||||||
|
host: entry.host,
|
||||||
|
path: entry.path,
|
||||||
|
name: entry.name,
|
||||||
|
cut,
|
||||||
|
clipboardRelativePath: entry.path.slice(props.path.length).replace(/^\//, '')
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (append)
|
||||||
|
clipboard.content = [...newContent, ...clipboard.content].filter((a, index, arr) => arr.findIndex(b => b.uniqueId === a.uniqueId) === index);
|
||||||
|
else
|
||||||
|
clipboard.content = newContent;
|
||||||
|
const message = append
|
||||||
|
? `Added ${newContent.length} items to clipboard.\n(${clipboard.content.length} items total)`
|
||||||
|
: `${cut ? 'Cut' : 'Copied'} ${newContent.length} items to clipboard.`;
|
||||||
|
notifications.value.constructNotification('Clipboard', message, 'info', 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const paste = (destinations) => {
|
||||||
|
let destination;
|
||||||
|
if (destinations.length === 1) {
|
||||||
|
destination = destinations[0];
|
||||||
|
if (destination.resolvedType !== 'd') {
|
||||||
|
notifications.value.constructNotification("Paste Failed", 'Cannot paste to non-directory.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (destinations.length === 0) {
|
||||||
|
destination = { host: props.host, path: props.path };
|
||||||
|
} else {
|
||||||
|
notifications.value.constructNotification("Paste Failed", 'Cannot paste to multiple directories.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("paste", clipboard.content, destination);
|
||||||
|
const fullSources = [...clipboard.content];
|
||||||
|
const { common } = commonPath(fullSources.map(item => item.path));
|
||||||
|
const groupedByHost = fullSources.reduce((res, item) => {
|
||||||
|
if (!res[item.host])
|
||||||
|
res[item.host] = [];
|
||||||
|
res[item.host].push(item);
|
||||||
|
return res;
|
||||||
|
}, {});
|
||||||
|
for (const host of Object.keys(groupedByHost)) {
|
||||||
|
console.log(`rsync -avh ${groupedByHost[host].map(a => a.path).join(' ')} ${destination.host}:${destination.path}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {KeyboardEvent} event
|
* @param {KeyboardEvent} event
|
||||||
*/
|
*/
|
||||||
const keyHandler = (event) => {
|
const keyHandler = (event) => {
|
||||||
const unCutEntries = () => gatherEntries([], false).map(entry => entry.cut = false);
|
|
||||||
const keypress = event.key.toLowerCase();
|
const keypress = event.key.toLowerCase();
|
||||||
const handleExact = (keypress) => {
|
const handleExact = (keypress) => {
|
||||||
switch (keypress) {
|
switch (keypress) {
|
||||||
@ -379,7 +434,6 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
|
||||||
}
|
}
|
||||||
const handleCtrl = (keypress) => {
|
const handleCtrl = (keypress) => {
|
||||||
switch (keypress) {
|
switch (keypress) {
|
||||||
@ -392,51 +446,15 @@ export default {
|
|||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
case 'x':
|
case 'x':
|
||||||
const isCut = keypress === 'x';
|
clipboardStore(getSelected(), keypress === 'x', event.shiftKey);
|
||||||
if (!event.shiftKey)
|
|
||||||
unCutEntries();
|
|
||||||
const newContent = getSelected().map(entry => {
|
|
||||||
entry.cut = isCut;
|
|
||||||
return {
|
|
||||||
uniqueId: entry.uniqueId,
|
|
||||||
host: entry.host,
|
|
||||||
path: entry.path,
|
|
||||||
name: entry.name,
|
|
||||||
cut: isCut,
|
|
||||||
clipboardRelativePath: entry.path.slice(props.path.length).replace(/^\//, '')
|
|
||||||
};
|
|
||||||
});
|
|
||||||
if (event.shiftKey)
|
|
||||||
clipboard.content = [...newContent, ...clipboard.content].filter((a, index, arr) => arr.findIndex(b => b.uniqueId === a.uniqueId) === index);
|
|
||||||
else
|
|
||||||
clipboard.content = newContent;
|
|
||||||
const message = event.shiftKey
|
|
||||||
? `Added ${newContent.length} items to clipboard.\n(${clipboard.content.length} items total)`
|
|
||||||
: `Copied ${newContent.length} items to clipboard.`;
|
|
||||||
notifications.value.constructNotification('Clipboard', message, 'info', 2000);
|
|
||||||
break;
|
break;
|
||||||
case 'v':
|
case 'v':
|
||||||
const selected = getSelected();
|
paste(getSelected());
|
||||||
let destination;
|
|
||||||
if (selected.length === 1) {
|
|
||||||
destination = selected[0];
|
|
||||||
if (destination.resolvedType !== 'd') {
|
|
||||||
notifications.value.constructNotification("Paste Failed", 'Cannot paste to non-directory.', 'error');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (selected.length === 0) {
|
|
||||||
destination = { host: props.host, path: props.path };
|
|
||||||
} else {
|
|
||||||
notifications.value.constructNotification("Paste Failed", 'Cannot paste to multiple directories.', 'error');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
console.log("paste", clipboard.content, destination);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
|
||||||
}
|
}
|
||||||
const handleShift = (keypress) => {
|
const handleShift = (keypress) => {
|
||||||
switch(keypress) {
|
switch(keypress) {
|
||||||
@ -444,7 +462,6 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
|
||||||
}
|
}
|
||||||
const handleCtrlShift = (keypress) => {
|
const handleCtrlShift = (keypress) => {
|
||||||
switch(keypress) {
|
switch(keypress) {
|
||||||
@ -452,7 +469,6 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
|
||||||
}
|
}
|
||||||
const handleAny = (keypress) => {
|
const handleAny = (keypress) => {
|
||||||
switch(keypress) {
|
switch(keypress) {
|
||||||
@ -460,7 +476,6 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
|
||||||
}
|
}
|
||||||
if (event.ctrlKey && event.shiftKey) {
|
if (event.ctrlKey && event.shiftKey) {
|
||||||
handleCtrlShift(keypress);
|
handleCtrlShift(keypress);
|
||||||
@ -488,11 +503,27 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const contextMenuCallback = (event) => {
|
||||||
|
if (!(event.ctrlKey || event.shiftKey)) {
|
||||||
|
deselectAll();
|
||||||
|
}
|
||||||
|
emit('browserAction', 'contextMenu', event);
|
||||||
|
}
|
||||||
|
|
||||||
const handleAction = (action, ...args) => {
|
const handleAction = (action, ...args) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'toggleSelected':
|
case 'toggleSelected':
|
||||||
toggleSelected(...args);
|
toggleSelected(...args);
|
||||||
break;
|
break;
|
||||||
|
case 'copy':
|
||||||
|
clipboardStore(args?.flat(1) ?? getSelected(), false, false);
|
||||||
|
break;
|
||||||
|
case 'cut':
|
||||||
|
clipboardStore(args?.flat(1) ?? getSelected(), true, false);
|
||||||
|
break;
|
||||||
|
case 'paste':
|
||||||
|
paste(args?.flat(1) ?? getSelected());
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
console.error('Unknown directoryViewAction:', action, args);
|
console.error('Unknown directoryViewAction:', action, args);
|
||||||
break;
|
break;
|
||||||
@ -539,6 +570,7 @@ export default {
|
|||||||
selectAll,
|
selectAll,
|
||||||
deselectAll,
|
deselectAll,
|
||||||
selectRectangle,
|
selectRectangle,
|
||||||
|
contextMenuCallback,
|
||||||
handleAction,
|
handleAction,
|
||||||
tallySelected,
|
tallySelected,
|
||||||
}
|
}
|
||||||
|
@ -192,6 +192,7 @@
|
|||||||
<ContextMenu
|
<ContextMenu
|
||||||
:currentPath="pathHistory.current() ?? { path: '/', host: 'localhost' }"
|
:currentPath="pathHistory.current() ?? { path: '/', host: 'localhost' }"
|
||||||
@browserAction="handleAction"
|
@browserAction="handleAction"
|
||||||
|
@directoryViewAction="(...args) => directoryViewRef?.handleAction(...args)"
|
||||||
ref="contextMenuRef"
|
ref="contextMenuRef"
|
||||||
/>
|
/>
|
||||||
<ModalPrompt ref="modalPromptRef" />
|
<ModalPrompt ref="modalPromptRef" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user