mirror of
https://github.com/45Drives/cockpit-navigator.git
synced 2025-09-29 04:38:43 +02:00
230 lines
8.1 KiB
Vue
230 lines
8.1 KiB
Vue
<template>
|
|
<template v-if="settings.directoryView?.view === 'list'">
|
|
<tr v-show="show || showEntries" @dblclick="doubleClickCallback"
|
|
@click.prevent="$emit('toggleSelected', entry, $event)"
|
|
:class="['hover:!bg-red-600/10 select-none dir-entry', entry.selected ? 'dir-entry-selected' : '']"
|
|
ref="selectIntersectElement">
|
|
<td class="!pl-1" :class="{ '!border-t-0': suppressBorders.top, '!border-b-0': suppressBorders.bottom }">
|
|
<div :class="[entry.cut ? 'line-through' : '', 'flex items-center gap-1']">
|
|
<div :style="{ width: `${24 * level}px` }"></div>
|
|
<div class="relative w-6">
|
|
<component :is="icon" class="size-icon icon-default" :class="{ 'text-gray-500/50': entry.cut }" />
|
|
<LinkIcon v-if="entry.type === 'l'" class="w-2 h-2 absolute right-0 bottom-0 text-default" />
|
|
</div>
|
|
<button v-if="directoryLike" @click.stop="toggleShowEntries">
|
|
<ChevronDownIcon v-if="!showEntries" class="size-icon icon-default" />
|
|
<ChevronUpIcon v-else class="size-icon icon-default" />
|
|
</button>
|
|
<div v-html="escapeStringHTML(entry.name)" :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"
|
|
class="icon-danger size-icon-sm absolute inset-x-0 bottom-0" />
|
|
</div>
|
|
<div v-html="escapeStringHTML(entry.target?.rawPath ?? '')" :title="entry.target.rawPath"></div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td v-if="settings?.directoryView?.cols?.mode" class="font-mono"
|
|
:class="{ '!border-t-0': suppressBorders.top, '!border-b-0': suppressBorders.bottom }">{{ entry.modeStr
|
|
}}</td>
|
|
<td v-if="settings?.directoryView?.cols?.owner"
|
|
:class="{ '!border-t-0': suppressBorders.top, '!border-b-0': suppressBorders.bottom }">{{ entry.owner }}
|
|
</td>
|
|
<td v-if="settings?.directoryView?.cols?.group"
|
|
:class="{ '!border-t-0': suppressBorders.top, '!border-b-0': suppressBorders.bottom }">{{ entry.group }}
|
|
</td>
|
|
<td v-if="settings?.directoryView?.cols?.size" class="font-mono text-right"
|
|
:class="{ '!border-t-0': suppressBorders.top, '!border-b-0': suppressBorders.bottom }">{{
|
|
entry.sizeHuman
|
|
}}</td>
|
|
<td v-if="settings?.directoryView?.cols?.ctime"
|
|
:class="{ '!border-t-0': suppressBorders.top, '!border-b-0': suppressBorders.bottom }">{{
|
|
entry.ctime?.toLocaleString() ?? '-'
|
|
}}</td>
|
|
<td v-if="settings?.directoryView?.cols?.mtime"
|
|
:class="{ '!border-t-0': suppressBorders.top, '!border-b-0': suppressBorders.bottom }">{{
|
|
entry.mtime?.toLocaleString() ?? '-'
|
|
}}</td>
|
|
<td v-if="settings?.directoryView?.cols?.atime"
|
|
:class="{ '!border-t-0': suppressBorders.top, '!border-b-0': suppressBorders.bottom }">{{
|
|
entry.atime?.toLocaleString() ?? '-'
|
|
}}</td>
|
|
</tr>
|
|
<component :show="show || showEntries" :is="DirectoryEntryList" v-if="directoryLike && showEntries" :host="host"
|
|
:path="entry.path" :isChild="true" :sortCallback="inheritedSortCallback"
|
|
:searchFilterRegExp="searchFilterRegExp" @cd="(...args) => $emit('cd', ...args)"
|
|
@edit="(...args) => $emit('edit', ...args)"
|
|
@startProcessing="(...args) => $emit('startProcessing', ...args)"
|
|
@stopProcessing="(...args) => $emit('stopProcessing', ...args)" @cancelShowEntries="showEntries = false"
|
|
ref="directoryEntryListRef" :level="level + 1"
|
|
@toggleSelected="(...args) => $emit('toggleSelected', ...args)" />
|
|
</template>
|
|
<template v-else>
|
|
<div v-show="show" @dblclick="doubleClickCallback"
|
|
@click.prevent="$emit('toggleSelected', entry, $event)"
|
|
ref="selectIntersectElement"
|
|
class="hover:!bg-red-600/10 select-none dir-entry flex flex-col items-center overflow-hidden entry-width p-2"
|
|
:class="{ 'dir-entry-selected': entry.selected, '!border-t-transparent': suppressBorders.top, '!border-b-transparent': suppressBorders.bottom, '!border-l-transparent': suppressBorders.left, '!border-r-transparent': suppressBorders.right }">
|
|
<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 ?? '?'}`">
|
|
<LinkIcon v-if="entry.type === 'l'"
|
|
:class="[entry.target?.broken ? 'text-red-300 dark:text-red-800' : 'text-gray-100 dark:text-gray-900']" />
|
|
</div>
|
|
</div>
|
|
<div class="text-center w-full" :class="{ truncate: !entry.selected, 'line-through': entry.cut }" style="overflow-wrap: break-word;" v-html="escapeStringHTML(entry.name)" :title="entry.name"></div>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
|
|
<script>
|
|
import { ref, inject, watch, nextTick, onBeforeUnmount, onMounted, onActivated, onDeactivated } from 'vue';
|
|
import { DocumentIcon, FolderIcon, LinkIcon, DocumentRemoveIcon, ArrowNarrowRightIcon, XIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/solid';
|
|
import { settingsInjectionKey } from '../keys';
|
|
import DirectoryEntryList from './DirectoryEntryList.vue';
|
|
import { escapeStringHTML } from '../functions/escapeStringHTML';
|
|
|
|
export default {
|
|
name: 'DirectoryEntry',
|
|
props: {
|
|
host: String,
|
|
entry: Object,
|
|
show: Boolean,
|
|
searchFilterRegExp: RegExp,
|
|
inheritedSortCallback: {
|
|
type: Function,
|
|
required: false,
|
|
default: null,
|
|
},
|
|
level: Number,
|
|
suppressBorders: {
|
|
type: Object,
|
|
required: false,
|
|
default: {
|
|
top: false,
|
|
bottom: false,
|
|
left: false,
|
|
right: false,
|
|
}
|
|
},
|
|
},
|
|
setup(props, { emit }) {
|
|
const settings = inject(settingsInjectionKey);
|
|
const icon = ref(FolderIcon);
|
|
const directoryLike = ref(false);
|
|
const showEntries = ref(false);
|
|
const directoryEntryListRef = ref();
|
|
const selectIntersectElement = ref();
|
|
|
|
// const selectedClasses = ref([]);
|
|
|
|
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('cd', props.entry.path);
|
|
} else {
|
|
emit('edit', props.entry.path);
|
|
}
|
|
}
|
|
|
|
const refresh = () => {
|
|
return directoryEntryListRef.value?.refresh?.();
|
|
}
|
|
|
|
const toggleShowEntries = () => {
|
|
showEntries.value = !showEntries.value;
|
|
emit('setEntryProp', 'dirOpen', showEntries.value);
|
|
}
|
|
|
|
/**
|
|
* Recursive get all entries for browser
|
|
*
|
|
* @param {DirectoryEntryObj[]} - Holds all entries
|
|
*
|
|
* @returns {DirectoryEntryObj[]} - the accumulator
|
|
*/
|
|
const gatherEntries = (accumulator = [], onlyVisible = true) => {
|
|
return directoryEntryListRef.value?.gatherEntries(accumulator, onlyVisible) ?? accumulator;
|
|
}
|
|
|
|
onMounted(() => {
|
|
watch(selectIntersectElement, () =>
|
|
emit('setEntryProp', 'DOMElement', selectIntersectElement.value), { immediate: true }
|
|
);
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
emit('setEntryProp', 'dirOpen', false)
|
|
emit('setEntryProp', 'DOMElement', undefined), { immediate: true }
|
|
});
|
|
|
|
return {
|
|
settings,
|
|
icon,
|
|
directoryLike,
|
|
showEntries,
|
|
directoryEntryListRef,
|
|
doubleClickCallback,
|
|
refresh,
|
|
toggleShowEntries,
|
|
gatherEntries,
|
|
escapeStringHTML,
|
|
DirectoryEntryList,
|
|
nextTick,
|
|
selectIntersectElement,
|
|
}
|
|
},
|
|
components: {
|
|
DocumentIcon,
|
|
FolderIcon,
|
|
LinkIcon,
|
|
DocumentRemoveIcon,
|
|
ArrowNarrowRightIcon,
|
|
XIcon,
|
|
DirectoryEntryList,
|
|
ChevronDownIcon,
|
|
ChevronUpIcon,
|
|
},
|
|
emits: [
|
|
'cd',
|
|
'edit',
|
|
'toggleSelected',
|
|
'startProcessing',
|
|
'stopProcessing',
|
|
'setEntryProp',
|
|
]
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
tr.dir-entry>td {
|
|
@apply border-solid border-y-red-600/50 border-y-0 first:border-l last:border-r first:border-l-transparent last:border-r-transparent;
|
|
}
|
|
|
|
tr.dir-entry-selected>td {
|
|
@apply border-y first:border-l-red-600/50 last:border-red-600/50 bg-red-600/10;
|
|
}
|
|
|
|
div.dir-entry {
|
|
@apply border-solid border border-transparent;
|
|
}
|
|
|
|
div.dir-entry-selected {
|
|
@apply border-red-600/50 bg-red-600/10;
|
|
}
|
|
|
|
div.entry-width {
|
|
width: v-bind('`${settings.directoryView?.gridEntrySize ?? 80}px`');
|
|
}
|
|
</style>
|