2022-06-13 15:52:07 -03:00

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>