toggleSelected -> entryAction, maintain selection on refresh, tallySelection on entry list change

This commit is contained in:
joshuaboud 2022-06-16 13:05:43 -03:00
parent 3d0322c6e3
commit 9e931f5bfa
No known key found for this signature in database
GPG Key ID: 17EFB59E2A8BF50E
3 changed files with 51 additions and 31 deletions

View File

@ -1,6 +1,7 @@
<template>
<template v-if="settings.directoryView?.view === 'list'">
<tr @dblclick="doubleClickCallback" @click.prevent="$emit('toggleSelected', entry, $event)"
<tr @dblclick="doubleClickCallback" @click.prevent="$emit('entryAction', 'toggleSelected', entry, $event)"
@mouseup.stop
:class="{'hover:!bg-red-600/10 select-none dir-entry': true, 'dir-entry-selected': entry.selected, 'suppress-border-t': suppressBorderT, 'suppress-border-b': suppressBorderB }"
ref="selectIntersectElement">
<td class="!pl-1" >
@ -57,10 +58,9 @@
@startProcessing="(...args) => $emit('startProcessing', ...args)"
@stopProcessing="(...args) => $emit('stopProcessing', ...args)" @cancelShowEntries="showEntries = false"
ref="directoryEntryListRef" :level="level + 1" :selectedCount="selectedCount"
@toggleSelected="(...args) => $emit('toggleSelected', ...args)"
@entryAction="(...args) => $emit('entryAction', ...args)" />
</template>
<div v-else @dblclick="doubleClickCallback" @click.prevent="$emit('toggleSelected', entry, $event)"
<div v-else @dblclick="doubleClickCallback" @click.prevent="$emit('entryAction', 'toggleSelected', entry, $event)" @mouseup.stop
ref="selectIntersectElement"
class="hover:!bg-red-600/10 select-none dir-entry flex flex-col items-center overflow-hidden dir-entry-width p-2"
:class="{ 'dir-entry-selected': entry.selected, '!border-t-transparent': suppressBorderT, '!border-b-transparent': suppressBorderB, '!border-l-transparent': suppressBorderL, '!border-r-transparent': suppressBorderR }">
@ -158,6 +158,10 @@ export default {
emit('setEntryProp', 'DOMElement', selectIntersectElement.value);
});
onUpdated(() => {
emit('setEntryProp', 'DOMElement', selectIntersectElement.value);
});
return {
settings,
icon,
@ -186,7 +190,6 @@ export default {
ChevronUpIcon,
},
emits: [
'toggleSelected',
'startProcessing',
'stopProcessing',
'setEntryProp',

View File

@ -1,7 +1,7 @@
<template>
<DirectoryEntry v-for="entry, index in visibleEntries" :key="entry.path" :host="host" :entry="entry"
:inheritedSortCallback="sortCallback" :searchFilterRegExp="searchFilterRegExp"
@toggleSelected="(...args) => $emit('toggleSelected', ...args)" @sortEntries="sortEntries"
@sortEntries="sortEntries"
@startProcessing="(...args) => $emit('startProcessing', ...args)"
@stopProcessing="(...args) => $emit('stopProcessing', ...args)" ref="entryRefs" :level="level" :selectedCount="selectedCount"
@setEntryProp="(prop, value) => entry[prop] = value"
@ -19,14 +19,14 @@
</tr>
<Teleport to="#footer-text" v-if="selectedCount === 0">
<div>
<span v-if="level > 0">{{ path.split('/').slice(-1 * (level+1)).join('/') }}:</span>
<span v-if="level > 0">{{ path.split('/').slice(-1 * (level)).join('/') }}:</span>
{{ stats }}
</div>
</Teleport>
</template>
<script>
import { ref, reactive, computed, inject, watch, onBeforeUnmount } from 'vue';
import { ref, reactive, computed, inject, watch, onBeforeUnmount, onMounted, nextTick } from 'vue';
import { errorStringHTML } from '@45drives/cockpit-helpers';
import { notificationsInjectionKey, settingsInjectionKey, clipboardInjectionKey } from '../keys';
import DirectoryEntry from './DirectoryEntry.vue';
@ -117,8 +117,12 @@ export default {
if (props.path !== cwd) {
return; // changed directory before could finish
}
const selectedIds = gatherEntries([], false).filter(entry => entry.selected).map(entry => entry.uniqueId);
const clipboardIds = clipboard.content.map(entry => entry.uniqueId);
tmpEntries.map(entry => entry.cut = clipboardIds.includes(entry.uniqueId));
tmpEntries.map(entry => {
entry.selected = selectedIds.includes(entry.uniqueId);
entry.cut = clipboardIds.includes(entry.uniqueId);
});
entries.value = [...tmpEntries.sort(sortCallbackComputed.value)];
} catch (error) {
entries.value = [];
@ -165,15 +169,6 @@ export default {
);
}
const getBorderSuppression = (entry, index) => {
return {
top: visibleEntries.value[index - props.cols]?.selected && !(visibleEntries.value[index - props.cols]?.dirOpen),
bottom: visibleEntries.value[index + props.cols]?.selected && !(entry.dirOpen),
left: settings.directoryView.view !== 'list' && (visibleEntries.value[index - 1]?.selected && (index) % props.cols !== 0),
right: settings.directoryView.view !== 'list' && (visibleEntries.value[index + 1]?.selected && (index + 1) % props.cols !== 0),
}
};
const fileSystemWatcher = FileSystemWatcher(props.path, { superuser: 'try', host: props.host, ignoreSelf: true });
fileSystemWatcher.onCreated = async (eventObj) => {
@ -202,7 +197,7 @@ export default {
if (!newContent)
return; // temp file deleted too quickly
const attrsChanged = ["name", "owner", "group", "size", "ctime", "mtime", "atime"].map(key => String(entry[key]) !== String(newContent[key])).includes(true);
Object.assign(entry, newContent);
Object.assign(entry, newContent, { cut: entry.cut, selected: entry.selected });
if (attrsChanged) sortEntries();
}
else
@ -237,6 +232,7 @@ export default {
return _stats;
}, { files: 0, dirs: 0, size: 0 });
stats.value = `${_stats.files} file${_stats.files === 1 ? '' : 's'}, ${_stats.dirs} director${_stats.dirs === 1 ? 'y' : 'ies'} (${cockpit.format_bytes(_stats.size, 1000).replace(/(?<!B)$/, ' B')})`;
nextTick(() => emit('tallySelected'));
});
watch(() => props.path, (current, old) => {
@ -263,7 +259,6 @@ export default {
sortEntries,
entryFilterCallback,
gatherEntries,
getBorderSuppression,
}
},
components: {
@ -275,6 +270,7 @@ export default {
'cancelShowEntries',
'deselectAll',
'entryAction',
'tallySelected',
]
}
</script>

View File

@ -1,6 +1,6 @@
<template>
<div class="h-full" @keydown="keyHandler($event)" tabindex="-1" :class="{ '!cursor-wait': processing }">
<DragSelectArea class="h-full" @selectRectangle="selectRectangle" @mouseup.exact="deselectAll()">
<DragSelectArea class="h-full" @selectRectangle="selectRectangle" @mouseup.exact="deselectAll">
<Table :key="host + path" v-if="settings.directoryView?.view === 'list'" emptyText="No entries." noHeader stickyHeaders
noShrink noShrinkHeight="h-full">
<template #thead>
@ -65,9 +65,10 @@
<template #tbody>
<DirectoryEntryList :host="host" :path="path" :sortCallback="sortCallback"
:searchFilterRegExp="searchFilterRegExp" @cd="(...args) => $emit('cd', ...args)"
@edit="(...args) => $emit('edit', ...args)" @toggleSelected="toggleSelected"
@edit="(...args) => $emit('edit', ...args)"
@startProcessing="processing++" @stopProcessing="processing--"
@entryAction="(...args) => $emit('entryAction', ...args)" ref="directoryEntryListRef"
@entryAction="handleEntryAction" ref="directoryEntryListRef"
@tallySelected="tallySelected"
:level="0" :cols="1" :selectedCount="selectedCount" />
</template>
</Table>
@ -107,18 +108,23 @@
@wheel="scrollHandler">
<DirectoryEntryList :host="host" :path="path" :sortCallback="sortCallback"
:searchFilterRegExp="searchFilterRegExp"
@toggleSelected="toggleSelected"
@startProcessing="processing++" @stopProcessing="processing--"
@entryAction="(...args) => $emit('entryAction', ...args)" ref="directoryEntryListRef"
@entryAction="handleEntryAction"
@tallySelected="tallySelected" ref="directoryEntryListRef"
:level="0" :cols="cols" :selectedCount="selectedCount" />
</div>
</div>
</DragSelectArea>
</div>
<Teleport to="#footer-text">
<div v-if="selectedCount > 1">
{{ selectedCount }} items selected
</div>
</Teleport>
</template>
<script>
import { ref, inject, watch, onMounted, computed, onBeforeUnmount, onUpdated } from 'vue';
import { ref, inject, watch, onMounted, computed, onBeforeUnmount, onUpdated, nextTick } from 'vue';
import Table from './Table.vue';
import { clipboardInjectionKey, notificationsInjectionKey, settingsInjectionKey } from '../keys';
import LoadingSpinner from './LoadingSpinner.vue';
@ -132,7 +138,7 @@ export default {
path: String,
searchFilterRegExp: RegExp,
},
setup(props) {
setup(props, { emit }) {
/**
* @type {NavigatorSettings}
*/
@ -162,7 +168,9 @@ export default {
const getSelected = () => directoryEntryListRef.value?.gatherEntries().filter(entry => entry.selected) ?? [];
const tallySelected = async () => selectedCount.value = getSelected().reduce((n) => n + 1, 0);
const tallySelected = () => {
selectedCount.value = getSelected().length;
}
let lastSelectedEntry = null;
const toggleSelected = (entry, { ctrlKey, shiftKey }) => {
@ -193,13 +201,12 @@ export default {
}
const selectAll = () => {
directoryEntryListRef.value?.gatherEntries().map(entry => entry.selected = true);
tallySelected();
selectedCount.value = directoryEntryListRef.value?.gatherEntries().map(entry => entry.selected = true).length ?? 0;
}
const deselectAll = () => {
const deselectAll = (event) => {
directoryEntryListRef.value?.gatherEntries([], false).map(entry => entry.selected = false);
tallySelected();
selectedCount.value = 0;
}
const selectRectangle = (rect, { ctrlKey, shiftKey }) => {
@ -216,6 +223,7 @@ export default {
return;
entry.selected = ctrlKey ? !entry.selected : true;
});
tallySelected();
}
/**
@ -294,6 +302,17 @@ export default {
}
}
const handleEntryAction = (action, entry, event, ...args) => {
switch (action) {
case 'toggleSelected':
toggleSelected(entry, event);
break;
default:
emit('entryAction', action, entry, event, ...args);
break;
}
}
const getCols = () => {
const gridWidth = gridRef.value?.clientWidth;
if (!gridWidth)
@ -334,6 +353,8 @@ export default {
selectAll,
deselectAll,
selectRectangle,
handleEntryAction,
tallySelected,
}
},
components: {