fix HTML formatting and add GPL headers

This commit is contained in:
joshuaboud 2022-06-20 11:20:02 -03:00
parent 116a422320
commit ec57d3f5de
No known key found for this signature in database
GPG Key ID: 17EFB59E2A8BF50E
29 changed files with 1169 additions and 349 deletions

View File

@ -1,17 +1,43 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="text-default bg-default h-full flex flex-col items-stretch">
<router-view v-if="providesValid" />
<div class="flex flex-row items-center px-4 py-2 gap-2">
<div id="footer-text" class="flex flex-row flex-wrap gap-x-4 gap-y-0 text-xs grow basis-0"></div>
<div
id="footer-text"
class="flex flex-row flex-wrap gap-x-4 gap-y-0 text-xs grow basis-0"
></div>
<div class="grow-0">
45Drives
</div>
<div id="footer-buttons" class="flex flex-row-reverse gap-buttons grow basis-0">
<div
id="footer-buttons"
class="flex flex-row-reverse gap-buttons grow basis-0"
>
<SettingsMenu />
</div>
</div>
</div>
<Notifications :notificationFIFO="notificationFIFO" ref="notifications" />
<Notifications
:notificationFIFO="notificationFIFO"
ref="notifications"
/>
</template>
<script setup>

View File

@ -1,42 +1,102 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<transition enter-active-class="origin-top-left transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100" leave-active-class="origin-top-left transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
<div v-if="show" class="fixed inset-0 bg-transparent" @click="$emit('hide')">
<div class="fixed z-20 max-w-sm flex flex-col items-stretch bg-default shadow-lg divide-y divide-default position-contextmenu">
<transition
enter-active-class="origin-top-left transition ease-out duration-100"
enter-from-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100"
leave-active-class="origin-top-left transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95"
>
<div
v-if="show"
class="fixed inset-0 bg-transparent"
@click="$emit('hide')"
>
<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">
<button :disabled="!pathHistory?.backAllowed()" @click="$emit('browserAction', 'back')" :class="{'grow flex items-center justify-center p-2': true, 'hover:bg-red-600/10': pathHistory?.backAllowed()}">
<button
:disabled="!pathHistory?.backAllowed()"
:class="{ 'grow flex items-center justify-center p-2': true, 'hover:bg-red-600/10': pathHistory?.backAllowed() }"
@click="$emit('browserAction', 'back')"
>
<ArrowLeftIcon class="size-icon icon-default" />
</button>
<button :disabled="!pathHistory?.forwardAllowed()" @click="$emit('browserAction', 'forward')" :class="{'grow flex items-center justify-center p-2': true, 'hover:bg-red-600/10': pathHistory?.forwardAllowed()}">
<button
:disabled="!pathHistory?.forwardAllowed()"
:class="{ 'grow flex items-center justify-center p-2': true, 'hover:bg-red-600/10': pathHistory?.forwardAllowed() }"
@click="$emit('browserAction', 'forward')"
>
<ArrowRightIcon class="size-icon icon-default" />
</button>
<button @click="$emit('browserAction', 'up')" class="grow hover:bg-red-600/10 flex items-center justify-center p-2">
<button
class="grow hover:bg-red-600/10 flex items-center justify-center p-2"
@click="$emit('browserAction', 'up')"
>
<ArrowUpIcon class="size-icon icon-default" />
</button>
</div>
<div class="flex flex-col items-stretch">
<!-- general actions -->
<button v-if="entry?.path !== '/'" @click="$emit('browserAction', 'editPermissions', entry)" class="context-menu-button">
<button
v-if="entry?.path !== '/'"
class="context-menu-button"
@click="$emit('browserAction', 'editPermissions', entry)"
>
Edit permissions
</button>
</div>
<div v-if="entry?.resolvedType === 'f'" class="flex flex-col items-stretch">
<div
v-if="entry?.resolvedType === 'f'"
class="flex flex-col items-stretch"
>
<!-- regular file actions -->
<button @click="$emit('browserAction', 'edit', entry)" class="context-menu-button">
<button
class="context-menu-button"
@click="$emit('browserAction', 'edit', entry)"
>
Edit contents
</button>
<button @click="$emit('browserAction', 'download', entry)" class="context-menu-button">
<button
class="context-menu-button"
@click="$emit('browserAction', 'download', entry)"
>
Download
</button>
</div>
<div v-else-if="entry?.resolvedType === 'd'" class="flex flex-col items-stretch">
<div
v-else-if="entry?.resolvedType === 'd'"
class="flex flex-col items-stretch"
>
<!-- directory actions -->
<button @click="$emit('browserAction', 'cd', entry)" class="context-menu-button">
<button
class="context-menu-button"
@click="$emit('browserAction', 'cd', entry)"
>
Open
</button>
</div>
<div v-if="entry?.type === 'l'" class="flex flex-col items-stretch">
<div
v-if="entry?.type === 'l'"
class="flex flex-col items-stretch"
>
<!-- link actions -->
</div>
</div>
@ -76,11 +136,15 @@ export default {
<style scoped>
div.position-contextmenu {
top: v-bind(`${event?.clientY ?? 0}px`);
left: v-bind(`${event?.clientX ?? 0}px`);
top: v-bind("`${event?.clientY ?? 0}px`");
left: v-bind("`${event?.clientX ?? 0}px`");
}
button.context-menu-button {
@apply text-default hover:bg-red-600/10 font-normal px-4 py-2 text-sm text-left;
@apply text-default font-normal px-4 py-2 text-sm text-left;
}
button.context-menu-button:hover {
@apply bg-red-600/10;
}
</style>

View File

@ -1,73 +1,173 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<template v-if="settings.directoryView?.view === 'list'">
<tr
:class="{ 'select-none dir-entry': true, '!bg-red-600/10': hover && !entry.selected, '!bg-red-600/20': hover && entry.selected, 'dir-entry-selected': entry.selected, 'suppress-border-t': suppressBorderT, 'suppress-border-b': suppressBorderB }">
<td class="!pl-1 relative">
<div :class="[entry.cut ? 'line-through' : '', 'flex items-center gap-1']">
<div class="w-6" v-for="i in Array(level).fill(0).keys()" :key="i" v-memo="[level]"></div>
<div class="relative w-6" :class="[entry.cut ? 'text-gray-500/50' : 'icon-default']">
<FolderIcon v-if="entry.resolvedType === 'd'" class="size-icon" />
<DocumentIcon v-else class="size-icon" />
<LinkIcon v-if="entry.type === 'l'" class="w-2 h-2 absolute right-0 bottom-0 text-default" />
<div class="indent-gap" />
<div
class="relative w-6"
:class="[entry.cut ? 'text-gray-500/50' : 'icon-default']"
>
<FolderIcon
v-if="entry.resolvedType === 'd'"
class="size-icon"
/>
<DocumentIcon
v-else
class="size-icon"
/>
<LinkIcon
v-if="entry.type === 'l'"
class="w-2 h-2 absolute right-0 bottom-0 text-default"
/>
</div>
<button class="z-10 icon-default" v-if="entry.resolvedType === 'd'" @click.stop="toggleShowEntries"
@mouseenter="hover = true" @mouseleave="hover = false">
<ChevronDownIcon v-if="!showEntries" class="size-icon" />
<ChevronUpIcon v-else class="size-icon" />
<button
class="z-10 icon-default"
v-if="entry.resolvedType === 'd'"
@click.stop="toggleShowEntries"
@mouseenter="hover = true"
@mouseleave="hover = false"
>
<ChevronDownIcon
v-if="!showEntries"
class="size-icon"
/>
<ChevronUpIcon
v-else
class="size-icon"
/>
</button>
<div v-html="entry.nameHTML" :title="entry.name"></div>
<div v-if="entry.type === 'l'" class="inline-flex gap-1 items-center">
<div
v-html="entry.nameHTML"
: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.linkBroken"
class="icon-danger size-icon-sm absolute inset-x-0 bottom-0" />
<XIcon
v-if="entry.linkBroken"
class="icon-danger size-icon-sm absolute inset-x-0 bottom-0"
/>
</div>
<div v-html="entry.linkRawPathHTML ?? ''" :title="entry.linkRawPath"></div>
<div
v-html="entry.linkRawPathHTML ?? ''"
:title="entry.linkRawPath"
></div>
</div>
</div>
<div class="absolute left-0 top-0 bottom-0 w-full max-w-[50vw]" @mouseup.stop
<div
class="absolute left-0 top-0 bottom-0 w-full max-w-[50vw]"
@mouseup.stop
@click.prevent="$emit('directoryViewAction', 'toggleSelected', entry, $event)"
@contextmenu.prevent.stop="$emit('browserAction', 'contextMenu', entry, $event)"
@dblclick="doubleClickCallback" @mouseenter="hover = true" @mouseleave="hover = false"
ref="selectIntersectElement" />
@dblclick="doubleClickCallback"
@mouseenter="hover = true"
@mouseleave="hover = false"
ref="selectIntersectElement"
/>
</td>
<td v-if="settings?.directoryView?.cols?.mode" class="font-mono">{{ entry.modeStr }}</td>
<td
v-if="settings?.directoryView?.cols?.mode"
class="font-mono"
>{{ entry.modeStr }}</td>
<td v-if="settings?.directoryView?.cols?.owner">{{ entry.owner }}</td>
<td v-if="settings?.directoryView?.cols?.group">{{ entry.group }} </td>
<td v-if="settings?.directoryView?.cols?.size" class="font-mono text-right">{{ entry.sizeHuman }}</td>
<td
v-if="settings?.directoryView?.cols?.size"
class="font-mono text-right"
>{{ entry.sizeHuman }}</td>
<td v-if="settings?.directoryView?.cols?.btime">{{ entry.btimeStr }}</td>
<td v-if="settings?.directoryView?.cols?.mtime">{{ entry.mtimeStr }}</td>
<td v-if="settings?.directoryView?.cols?.atime">{{ entry.atimeStr }}</td>
</tr>
<component :is="DirectoryEntryList" v-if="entry.resolvedType === 'd' && showEntries" :host="host"
:path="entry.path" :isChild="true" :sortCallback="inheritedSortCallback"
:searchFilterRegExp="searchFilterRegExp" @startProcessing="(...args) => $emit('startProcessing', ...args)"
@stopProcessing="(...args) => $emit('stopProcessing', ...args)" @cancelShowEntries="showEntries = false"
ref="directoryEntryListRef" :level="level + 1" :selectedCount="selectedCount"
<component
:is="DirectoryEntryList"
v-if="entry.resolvedType === 'd' && showEntries"
:host="host"
:path="entry.path"
:isChild="true"
:sortCallback="inheritedSortCallback"
:searchFilterRegExp="searchFilterRegExp"
:level="level + 1"
:selectedCount="selectedCount"
@browserAction="(...args) => $emit('browserAction', ...args)"
@directoryViewAction="(...args) => $emit('directoryViewAction', ...args)" />
@directoryViewAction="(...args) => $emit('directoryViewAction', ...args)"
@startProcessing="(...args) => $emit('startProcessing', ...args)"
@stopProcessing="(...args) => $emit('stopProcessing', ...args)"
@cancelShowEntries="showEntries = false"
ref="directoryEntryListRef"
/>
</template>
<div v-else class="select-none dir-entry flex flex-col items-center overflow-hidden dir-entry-width p-2"
:class="{ '!bg-red-600/10': hover && !entry.selected, '!bg-red-600/20': hover && entry.selected, 'dir-entry-selected': entry.selected, '!border-t-transparent': suppressBorderT, '!border-b-transparent': suppressBorderB, '!border-l-transparent': suppressBorderL, '!border-r-transparent': suppressBorderR }">
<div class="w-full" @dblclick="doubleClickCallback"
@click.prevent="$emit('directoryViewAction', 'toggleSelected', entry, $event)" @mouseup.stop
<div
v-else
class="select-none dir-entry flex flex-col items-center overflow-hidden dir-entry-width p-2"
:class="{ '!bg-red-600/10': hover && !entry.selected, '!bg-red-600/20': hover && entry.selected, 'dir-entry-selected': entry.selected, '!border-t-transparent': suppressBorderT, '!border-b-transparent': suppressBorderB, '!border-l-transparent': suppressBorderL, '!border-r-transparent': suppressBorderR }"
>
<div
class="w-full"
@dblclick="doubleClickCallback"
@click.prevent="$emit('directoryViewAction', 'toggleSelected', entry, $event)"
@mouseup.stop
@contextmenu.prevent.stop="$emit('browserAction', 'contextMenu', entry, $event)"
@mouseenter="hover = true" @mouseleave="hover = false" ref="selectIntersectElement">
<div class="relative w-full" :class="[entry.cut ? 'text-gray-500/50' : 'icon-default']">
<FolderIcon v-if="entry.resolvedType === 'd'" class="w-full h-auto" />
<DocumentIcon v-else class="w-full h-auto" />
<div :class="[entry.resolvedType === 'd' ? 'right-[15%] bottom-[25%]' : 'right-[25%] bottom-[15%]', 'inline absolute w-[20%]']"
:title="`-> ${entry.linkRawPath ?? '?'}`">
<LinkIcon v-if="entry.type === 'l'"
:class="[entry.linkBroken ? 'text-red-300 dark:text-red-800' : 'text-gray-100 dark:text-gray-900']" />
@mouseenter="hover = true"
@mouseleave="hover = false"
ref="selectIntersectElement"
>
<div
class="relative w-full"
:class="[entry.cut ? 'text-gray-500/50' : 'icon-default']"
>
<FolderIcon
v-if="entry.resolvedType === 'd'"
class="w-full h-auto"
/>
<DocumentIcon
v-else
class="w-full h-auto"
/>
<div
:class="[entry.resolvedType === 'd' ? 'right-[15%] bottom-[25%]' : 'right-[25%] bottom-[15%]', 'inline absolute w-[20%]']"
:title="`-> ${entry.linkRawPath ?? '?'}`"
>
<LinkIcon
v-if="entry.type === 'l'"
:class="[entry.linkBroken ? 'text-red-300 dark:text-red-800' : 'text-gray-100 dark:text-gray-900']"
/>
</div>
</div>
<div class="text-center w-full text-sm break-words"
<div
class="text-center w-full text-sm break-words"
:class="{ 'multiline-ellipsis': entry.selected && selectedCount > 1, 'truncate': !entry.selected, 'line-through': entry.cut }"
v-html="entry.nameHTML" :title="entry.name"></div>
:title="entry.name"
v-html="entry.nameHTML"
></div>
</div>
</div>
<Teleport to="#footer-text" v-if="entry.selected && selectedCount === 1">
<Teleport
to="#footer-text"
v-if="entry.selected && selectedCount === 1"
>
<div>
<span v-if="level > 0">{{ entry.path.split('/').slice(-1 * (level + 1)).join('/') }}:</span>
<span v-else>{{ entry.name }}:</span>
@ -78,7 +178,7 @@
</template>
<script>
import { ref, inject, watch, nextTick, onBeforeUnmount, onMounted, onActivated, onDeactivated, onUpdated } from 'vue';
import { ref, inject, nextTick, onMounted, onUpdated } from 'vue';
import { DocumentIcon, FolderIcon, LinkIcon, DocumentRemoveIcon, ArrowNarrowRightIcon, XIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/solid';
import { notificationsInjectionKey, settingsInjectionKey } from '../keys';
import DirectoryEntryList from './DirectoryEntryList.vue';
@ -181,3 +281,9 @@ export default {
]
}
</script>
<style>
.indent-gap {
width: v-bind("`${24 * level}px`");
}
</style>

View File

@ -1,24 +1,59 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<DirectoryEntry v-for="entry, index in visibleEntries" :key="entry.path" :host="host" :entry="entry"
:inheritedSortCallback="sortCallback" :searchFilterRegExp="searchFilterRegExp"
@sortEntries="sortEntries"
@startProcessing="(...args) => $emit('startProcessing', ...args)"
@stopProcessing="(...args) => $emit('stopProcessing', ...args)" ref="entryRefs" :level="level" :selectedCount="selectedCount"
@setEntryProp="(prop, value) => entry[prop] = value"
@browserAction="(...args) => $emit('browserAction', ...args)"
@directoryViewAction="(...args) => $emit('directoryViewAction', ...args)"
<DirectoryEntry
v-for="entry, index in visibleEntries"
:key="entry.path"
:host="host"
:entry="entry"
:inheritedSortCallback="sortCallback"
:searchFilterRegExp="searchFilterRegExp"
:level="level"
:selectedCount="selectedCount"
: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)"
:suppressBorderR="settings.directoryView.view !== 'list' && (visibleEntries[index + 1]?.selected && (index + 1) % cols !== 0)" />
:suppressBorderR="settings.directoryView.view !== 'list' && (visibleEntries[index + 1]?.selected && (index + 1) % cols !== 0)"
@browserAction="(...args) => $emit('browserAction', ...args)"
@directoryViewAction="(...args) => $emit('directoryViewAction', ...args)"
@sortEntries="sortEntries"
@startProcessing="(...args) => $emit('startProcessing', ...args)"
@stopProcessing="(...args) => $emit('stopProcessing', ...args)"
@setEntryProp="(prop, value) => entry[prop] = value"
ref="entryRefs"
/>
<tr v-if="visibleEntries.length === 0">
<td :colspan="Object.values(settings?.directoryView?.cols ?? {}).reduce((sum, current) => current ? sum + 1 : sum, 1) ?? 100"
class="!pl-1 text-muted text-sm">
<div class="w-6" v-for="i in Array(level).fill(0)" v-memo="[level]"></div>
<td
:colspan="Object.values(settings?.directoryView?.cols ?? {}).reduce((sum, current) => current ? sum + 1 : sum, 1) ?? 100"
class="!pl-1 text-muted text-sm"
>
<div
class="w-6"
v-for="i in Array(level).fill(0)"
v-memo="[level]"
></div>
<div class="inline-block">No entries.</div>
</td>
</tr>
<Teleport to="#footer-text" v-if="selectedCount === 0">
<Teleport
to="#footer-text"
v-if="selectedCount === 0"
>
<div>
<span v-if="level > 0">{{ path.split('/').slice(-1 * (level)).join('/') }}:</span>
{{ stats }}
@ -27,7 +62,7 @@
</template>
<script>
import { ref, reactive, computed, inject, watch, onBeforeUnmount, onMounted, nextTick, onUnmounted } from 'vue';
import { ref, reactive, computed, inject, watch, onBeforeUnmount, nextTick } from 'vue';
import { errorStringHTML } from '@45drives/cockpit-helpers';
import { notificationsInjectionKey, settingsInjectionKey, clipboardInjectionKey } from '../keys';
import DirectoryEntry from './DirectoryEntry.vue';

View File

@ -1,118 +1,235 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="h-full" @keydown="keyHandler($event)" tabindex="-1" :class="{ '!cursor-wait': processing }">
<DragSelectArea class="h-full" @selectRectangle="selectRectangle" @mouseup.exact="deselectAll" @contextmenu.prevent="$emit('browserAction', 'contextMenu', { host, path, name: `Current directory (${path.split('/').pop() || '/'})` }, $event)">
<Table :key="host + path" v-if="settings.directoryView?.view === 'list'" emptyText="No entries." noHeader stickyHeaders
noShrink noShrinkHeight="h-full">
<div
class="h-full"
@keydown="keyHandler($event)"
tabindex="-1"
:class="{ '!cursor-wait': processing }"
>
<DragSelectArea
class="h-full"
@selectRectangle="selectRectangle"
@mouseup.exact="deselectAll"
@contextmenu.prevent="$emit('browserAction', 'contextMenu', { host, path, name: `Current directory (${path.split('/').pop() || '/'})` }, $event)"
>
<Table
:key="host + path"
v-if="settings.directoryView?.view === 'list'"
emptyText="No entries."
noHeader
stickyHeaders
noShrink
noShrinkHeight="h-full"
>
<template #thead>
<tr>
<th class="!pl-1 border-l-2 border-l-transparent last:border-r-2 last:border-r-transparent">
<div class="flex flex-row flex-nowrap gap-1 items-center">
<div class="flex items-center justify-center w-6">
<LoadingSpinner v-if="processing" class="size-icon" />
<LoadingSpinner
v-if="processing"
class="size-icon"
/>
</div>
<div class="grow">Name</div>
<SortCallbackButton initialFuncIsMine v-model="sortCallback"
:compareFunc="sortCallbacks.name" />
<SortCallbackButton
initialFuncIsMine
v-model="sortCallback"
:compareFunc="sortCallbacks.name"
/>
</div>
</th>
<th class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.mode">
<th
class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.mode"
>
Mode</th>
<th class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.owner">
<th
class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.owner"
>
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div class="grow">Owner</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.owner" />
<SortCallbackButton
v-model="sortCallback"
:compareFunc="sortCallbacks.owner"
/>
</div>
</th>
<th class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.group">
<th
class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.group"
>
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div class="grow">Group</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.group" />
<SortCallbackButton
v-model="sortCallback"
:compareFunc="sortCallbacks.group"
/>
</div>
</th>
<th class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.size">
<th
class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.size"
>
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div class="grow text-right">Size</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.size" />
<SortCallbackButton
v-model="sortCallback"
:compareFunc="sortCallbacks.size"
/>
</div>
</th>
<th class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.btime">
<th
class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.btime"
>
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div class="grow">Created</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.btime" />
<SortCallbackButton
v-model="sortCallback"
:compareFunc="sortCallbacks.btime"
/>
</div>
</th>
<th class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.mtime">
<th
class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.mtime"
>
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div class="grow">Modified</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.mtime" />
<SortCallbackButton
v-model="sortCallback"
:compareFunc="sortCallbacks.mtime"
/>
</div>
</th>
<th class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.atime">
<th
class="last:border-r-2 last:border-r-transparent"
v-if="settings?.directoryView?.cols?.atime"
>
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div class="grow">Accessed</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.atime" />
<SortCallbackButton
v-model="sortCallback"
:compareFunc="sortCallbacks.atime"
/>
</div>
</th>
</tr>
</template>
<template #tbody>
<DirectoryEntryList :host="host" :path="path" :sortCallback="sortCallback"
<DirectoryEntryList
:host="host"
:path="path"
:sortCallback="sortCallback"
:searchFilterRegExp="searchFilterRegExp"
@startProcessing="processing++" @stopProcessing="processing--"
@browserAction="(...args) => $emit('browserAction', ...args)"
@directoryViewAction="handleAction" ref="directoryEntryListRef"
:level="0"
:cols="1"
:selectedCount="selectedCount"
@tallySelected="tallySelected"
:level="0" :cols="1" :selectedCount="selectedCount" />
@startProcessing="processing++"
@stopProcessing="processing--"
@browserAction="(...args) => $emit('browserAction', ...args)"
@directoryViewAction="handleAction"
ref="directoryEntryListRef"
/>
</template>
</Table>
<div v-else class="h-full flex flex-col items-stretch">
<div class="grow-0 flex flex-row justify-start items-center px-6 py-2 gap-4 text-sm border-t border-default">
<div
v-else
class="h-full flex flex-col items-stretch"
>
<div
class="grow-0 flex flex-row justify-start items-center px-6 py-2 gap-4 text-sm border-t border-default">
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div>Name</div>
<SortCallbackButton initialFuncIsMine v-model="sortCallback"
:compareFunc="sortCallbacks.name" />
<SortCallbackButton
initialFuncIsMine
v-model="sortCallback"
:compareFunc="sortCallbacks.name"
/>
</div>
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div>Owner</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.owner" />
<SortCallbackButton
v-model="sortCallback"
:compareFunc="sortCallbacks.owner"
/>
</div>
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div>Group</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.group" />
<SortCallbackButton
v-model="sortCallback"
:compareFunc="sortCallbacks.group"
/>
</div>
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div>Size</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.size" />
<SortCallbackButton
v-model="sortCallback"
:compareFunc="sortCallbacks.size"
/>
</div>
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div>Created</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.btime" />
<SortCallbackButton
v-model="sortCallback"
:compareFunc="sortCallbacks.btime"
/>
</div>
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div>Modified</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.mtime" />
<SortCallbackButton
v-model="sortCallback"
:compareFunc="sortCallbacks.mtime"
/>
</div>
<div class="flex flex-row flex-nowrap gap-2 items-center">
<div>Accessed</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.atime" />
<SortCallbackButton
v-model="sortCallback"
:compareFunc="sortCallbacks.atime"
/>
</div>
</div>
<div :key="host + path" class="grow flex flex-wrap bg-well overflow-y-auto content-start" ref="gridRef"
@wheel="scrollHandler">
<DirectoryEntryList :host="host" :path="path" :sortCallback="sortCallback"
<div
:key="host + path"
class="grow flex flex-wrap bg-well overflow-y-auto content-start"
ref="gridRef"
@wheel="scrollHandler"
>
<DirectoryEntryList
:host="host"
:path="path"
:sortCallback="sortCallback"
:searchFilterRegExp="searchFilterRegExp"
@startProcessing="processing++" @stopProcessing="processing--"
:level="0"
:cols="cols"
:selectedCount="selectedCount"
@startProcessing="processing++"
@stopProcessing="processing--"
@browserAction="(...args) => $emit('browserAction', ...args)"
@directoryViewAction="handleAction"
@tallySelected="tallySelected" ref="directoryEntryListRef"
:level="0" :cols="cols" :selectedCount="selectedCount" />
@tallySelected="tallySelected"
ref="directoryEntryListRef"
/>
</div>
</div>
</DragSelectArea>
@ -125,7 +242,7 @@
</template>
<script>
import { ref, inject, watch, onMounted, computed, onBeforeUnmount, onUpdated, nextTick } from 'vue';
import { ref, inject, watch, onMounted, onBeforeUnmount, onUpdated } from 'vue';
import Table from './Table.vue';
import { clipboardInjectionKey, notificationsInjectionKey, settingsInjectionKey } from '../keys';
import LoadingSpinner from './LoadingSpinner.vue';
@ -279,7 +396,7 @@ export default {
};
});
if (event.shiftKey)
clipboard.content = [ ...newContent, ...clipboard.content ].filter((a, index, arr) => arr.findIndex(b => b.uniqueId === a.uniqueId) === index);
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
@ -397,11 +514,11 @@ export default {
<style>
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;
@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;
@apply border-y first: border-l-red-600/50 last:border-red-600/50 bg-red-600/10;
}
div.dir-entry {
@ -413,19 +530,19 @@ div.dir-entry-selected {
}
tr.dir-entry-selected.suppress-border-t>td {
@apply !border-t-0;
@apply !border-t-0;
}
tr.dir-entry-selected.suppress-border-b>td {
@apply !border-b-0;
@apply !border-b-0;
}
tr.dir-entry-selected.suppress-border-l>td {
@apply !border-l-0;
@apply !border-l-0;
}
tr.dir-entry-selected.suppress-border-r>td {
@apply !border-r-0;
@apply !border-r-0;
}
div.dir-entry-width {

View File

@ -1,12 +1,36 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div v-show="dragging" class="fixed border-red-600/50 border border-solid bg-red-600/10 rounded-lg" ref="selectionBox"></div>
<div @mousedown="startDrag" v-bind="$attrs">
<div
v-show="dragging"
class="fixed border-red-600/50 border border-solid bg-red-600/10 rounded-lg"
ref="selectionBox"
></div>
<div
v-bind="$attrs"
@mousedown="startDrag"
>
<slot />
</div>
</template>
<script>
import { ref, reactive, watch, onMounted, nextTick, onBeforeUnmount } from 'vue'
import { ref, reactive, watch, onMounted } from 'vue'
export default {
props: {
areaThreshold: {

View File

@ -1,17 +1,17 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit File Sharing.
This file is part of Cockpit Navigator.
Cockpit File Sharing is free software: you can redistribute it and/or modify it under the terms
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit File Sharing is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit File Sharing.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
@ -23,19 +23,55 @@ If not, see <https://www.gnu.org/licenses/>.
<label class="text-label">Execute</label>
<label class="justify-self-start text-label">Owner</label>
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.owner.read" />
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.owner.write" />
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.owner.execute" />
<input
type="checkbox"
class="input-checkbox"
v-model="modeMatrix.owner.read"
/>
<input
type="checkbox"
class="input-checkbox"
v-model="modeMatrix.owner.write"
/>
<input
type="checkbox"
class="input-checkbox"
v-model="modeMatrix.owner.execute"
/>
<label class="justify-self-start text-label">Group</label>
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.group.read" />
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.group.write" />
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.group.execute" />
<input
type="checkbox"
class="input-checkbox"
v-model="modeMatrix.group.read"
/>
<input
type="checkbox"
class="input-checkbox"
v-model="modeMatrix.group.write"
/>
<input
type="checkbox"
class="input-checkbox"
v-model="modeMatrix.group.execute"
/>
<label class="justify-self-start text-label">Other</label>
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.other.read" />
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.other.write" />
<input type="checkbox" class="input-checkbox" v-model="modeMatrix.other.execute" />
<input
type="checkbox"
class="input-checkbox"
v-model="modeMatrix.other.read"
/>
<input
type="checkbox"
class="input-checkbox"
v-model="modeMatrix.other.write"
/>
<input
type="checkbox"
class="input-checkbox"
v-model="modeMatrix.other.execute"
/>
<label class="justify-self-start text-label">Mode</label>
<span class="col-span-3 font-mono font-medium whitespace-nowrap">{{ modeStr }}</span>

View File

@ -1,34 +1,53 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit File Sharing.
This file is part of Cockpit Navigator.
Cockpit File Sharing is free software: you can redistribute it and/or modify it under the terms
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit File Sharing is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit File Sharing.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<ModalPopup :showModal="show" :headerText="entry?.nameHTML ?? entry?.name" @apply="apply" @cancel="$emit('hide')">
<ModalPopup
:showModal="show"
:headerText="entry?.nameHTML ?? entry?.name"
@apply="apply"
@cancel="$emit('hide')"
>
<div class="flex flex-col space-y-content items-start">
<FileModeMatrix v-model="mode" />
<div>
<label class="block text-sm font-medium">Owner</label>
<select class="input-textlike" v-model="owner">
<option v-for="user in users" :key="user.pretty" :value="user.user">{{ user.pretty }}</option>
<select
class="input-textlike"
v-model="owner"
>
<option
v-for="user in users"
:key="user.pretty"
:value="user.user"
>{{ user.pretty }}</option>
</select>
</div>
<div>
<label class="block text-sm font-medium">Group</label>
<select class="input-textlike" v-model="group">
<option v-for="group in groups" :key="group.pretty" :value="group.group">{{ group.pretty }}</option>
<select
class="input-textlike"
v-model="group"
>
<option
v-for="group in groups"
:key="group.pretty"
:value="group.group"
>{{ group.pretty }}</option>
</select>
</div>
</div>

View File

@ -1,5 +1,22 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<button @click="$emit('update:modelValue', modelValue === trueValue ? falseValue: trueValue)">
<button @click="$emit('update:modelValue', modelValue === trueValue ? falseValue : trueValue)">
<slot :value="modelValue === trueValue">
</slot>

View File

@ -16,19 +16,29 @@ If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<SwitchGroup as="div" :class="[labelRight ? 'flex-row-reverse' : 'flex-row', 'inline-flex items-center']">
<SwitchGroup
as="div"
:class="[labelRight ? 'flex-row-reverse' : 'flex-row', 'inline-flex items-center']"
>
<span :class="[labelRight ? 'grow' : '', 'flex flex-col']">
<SwitchLabel as="div" class="text-label"><slot /></SwitchLabel>
<SwitchLabel
as="div"
class="text-label"
>
<slot />
</SwitchLabel>
<SwitchDescription
as="div"
class="text-sm text-muted"
><slot name="description" /></SwitchDescription>
>
<slot name="description" />
</SwitchDescription>
</span>
<div :class="[labelRight ? '' : 'grow', 'w-4']"></div> <!-- spacer -->
<Switch
:modelValue="modelValue"
@update:modelValue="newValue => { $emit('update:modelValue', newValue); $emit('change', newValue); $emit('input', newValue); }"
:class="[modelValue ? 'bg-45d' : 'bg-well', 'inline-flex shrink-0 h-6 w-11 p-[2px] rounded-full cursor-pointer shadow-inner']"
@update:modelValue="newValue => { $emit('update:modelValue', newValue); $emit('change', newValue); $emit('input', newValue); }"
>
<span
aria-hidden="true"
@ -40,7 +50,6 @@ If not, see <https://www.gnu.org/licenses/>.
<script>
import { Switch, SwitchDescription, SwitchGroup, SwitchLabel } from '@headlessui/vue'
import { watch, ref } from 'vue';
export default {
props: {
modelValue: Boolean,

View File

@ -16,5 +16,6 @@ If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="aspect-square animate-spin border-neutral-300 border-t-neutral-500 dark:border-neutral-500 dark:border-t-neutral-200 rounded-full"></div>
<div
class="aspect-square animate-spin border-neutral-300 border-t-neutral-500 dark:border-neutral-500 dark:border-t-neutral-200 rounded-full" />
</template>

View File

@ -16,25 +16,45 @@ If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<TransitionRoot as="div" class="fixed inset-0 z-10 overflow-visible" :show="showModal">
<TransitionChild as="template" enter="ease-out duration-500" enter-from="opacity-0" enter-to="opacity-100"
leave="ease-in duration-500" leave-from="opacity-100" leave-to="opacity-0">
<TransitionRoot
as="div"
class="fixed inset-0 z-10 overflow-visible"
:show="showModal"
>
<TransitionChild
as="template"
enter="ease-out duration-500"
enter-from="opacity-0"
enter-to="opacity-100"
leave="ease-in duration-500"
leave-from="opacity-100"
leave-to="opacity-0"
>
<div class="fixed z-10 inset-0 bg-neutral-500/75 dark:bg-black/50 transition-opacity pointer" />
</TransitionChild>
<div @click.self="$emit('close')"
class="fixed z-10 inset-0 overflow-hidden flex items-end sm:items-center justify-center px-4 pb-20 sm:p-0">
<TransitionChild as="template" enter="ease-out duration-300"
<div
class="fixed z-10 inset-0 overflow-hidden flex items-end sm:items-center justify-center px-4 pb-20 sm:p-0"
@click.self="$emit('close')"
>
<TransitionChild
as="template"
enter="ease-out duration-300"
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-90"
enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-100"
enter-to="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-100"
leave-from="opacity-100 translate-y-0 sm:scale-100"
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-75">
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-75"
>
<div
:class="[autoWidth ? 'sm:max-w-full' : 'sm:max-w-lg', 'inline-flex flex-col items-stretch overflow-hidden transform transition-all text-left z-10']">
<div class="block w-[512px]" /> <!-- set min width of div -->
<div class="card flex flex-col items-stretch overflow-hidden">
<div class="card-header">
<slot name="header">
<h3 class="text-header" v-html="headerText" />
<h3
class="text-header"
v-html="headerText"
/>
</slot>
</div>
<div class="card-body flex flex-row items-center gap-2">
@ -45,12 +65,20 @@ If not, see <https://www.gnu.org/licenses/>.
</div>
<div class="card-footer button-group-row justify-end">
<slot name="footer">
<button v-if="!noCancel" type="button" class="btn btn-secondary"
@click="$emit('cancel'); $emit('close')">
<button
v-if="!noCancel"
type="button"
class="btn btn-secondary"
@click="$emit('cancel'); $emit('close')"
>
{{ cancelText }}
</button>
<button type="button" :class="['btn', applyDangerous ? 'btn-danger' : 'btn-primary']"
@click="$emit('apply'); $emit('close')" :disabled="disableContinue">
<button
type="button"
:class="['btn', applyDangerous ? 'btn-danger' : 'btn-primary']"
:disabled="disableContinue"
@click="$emit('apply'); $emit('close')"
>
{{ applyText }}
</button>
</slot>

View File

@ -16,49 +16,93 @@ If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div aria-live="assertive"
class="fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start z-20 h-screen overflow-y-auto">
<transition-group tag="div" class="w-full flex flex-col-reverse items-center sm:items-end sm:flex-col space-y-content"
<div
aria-live="assertive"
class="fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start z-20 h-screen overflow-y-auto"
>
<transition-group
tag="div"
class="w-full flex flex-col-reverse items-center sm:items-end sm:flex-col space-y-content"
enter-active-class="transition-all transform ease-out duration-300"
enter-from-class="translate-y-8 opacity-0 scale-95 sm:translate-y-0 sm:translate-x-8"
enter-to-class="translate-y-0 opacity-100 scale-100 sm:translate-x-0"
leave-active-class="transition-all transform ease-in duration-100"
leave-from-class="opacity-100 scale-100 sm:translate-x-0"
leave-to-class="opacity-0 scale-95 sm:translate-x-8">
<div v-for="notification in notificationList" :key="notification.id"
leave-to-class="opacity-0 scale-95 sm:translate-x-8"
>
<div
v-for="notification in notificationList"
:key="notification.id"
class="max-w-sm w-full shadow-lg pointer-events-auto overflow-hidden bg-default text-default"
@mouseenter="notification.clearTimeouts?.()" @mouseleave="notification.setTimeouts?.()">
@mouseenter="notification.clearTimeouts?.()"
@mouseleave="notification.setTimeouts?.()"
>
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0" aria-hidden="true">
<ExclamationCircleIcon v-if="notification.level === 'error'" class="icon-error size-icon-lg"
aria-hidden="true" />
<ExclamationCircleIcon v-else-if="notification.level === 'warning'"
class="icon-warning size-icon-lg" aria-hidden="true" />
<CheckCircleIcon v-else-if="notification.level === 'success'"
class="icon-success size-icon-lg" aria-hidden="true" />
<MinusCircleIcon v-else-if="notification.level === 'denied'" class="icon-error size-icon-lg"
aria-hidden="true" />
<InformationCircleIcon v-else class="icon-info size-icon-lg" />
<div
class="flex-shrink-0"
aria-hidden="true"
>
<ExclamationCircleIcon
v-if="notification.level === 'error'"
class="icon-error size-icon-lg"
aria-hidden="true"
/>
<ExclamationCircleIcon
v-else-if="notification.level === 'warning'"
class="icon-warning size-icon-lg"
aria-hidden="true"
/>
<CheckCircleIcon
v-else-if="notification.level === 'success'"
class="icon-success size-icon-lg"
aria-hidden="true"
/>
<MinusCircleIcon
v-else-if="notification.level === 'denied'"
class="icon-error size-icon-lg"
aria-hidden="true"
/>
<InformationCircleIcon
v-else
class="icon-info size-icon-lg"
/>
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium">{{ notification.title }}</p>
<p class="mt-1 text-sm text-muted whitespace-pre-wrap" v-html="notification.body">
<p
class="mt-1 text-sm text-muted whitespace-pre-wrap"
v-html="notification.body"
>
</p>
<div v-if="notification.actions?.length" class="mt-3 flex space-x-7">
<button v-for="action in notification.actions" @click="action.callback"
class="rounded-md text-sm font-medium text-primary">
<div
v-if="notification.actions?.length"
class="mt-3 flex space-x-7"
>
<button
v-for="action in notification.actions"
class="rounded-md text-sm font-medium text-primary"
@click="action.callback"
>
{{ action.text }}
</button>
<button @click="notification.show = false" type="button"
class="rounded-md text-sm font-medium text-secondary">Dismiss</button>
<button
type="button"
class="rounded-md text-sm font-medium text-secondary"
@click="notification.show = false"
>Dismiss</button>
</div>
</div>
<div class="ml-4 flex-shrink-0 flex">
<button @click="notification.show = false"
class="icon-default">
<button
class="icon-default"
@click="notification.show = false"
>
<span class="sr-only">Close</span>
<XIcon class="size-icon" aria-hidden="true" />
<XIcon
class="size-icon"
aria-hidden="true"
/>
</button>
</div>
</div>
@ -69,7 +113,7 @@ If not, see <https://www.gnu.org/licenses/>.
</template>
<script>
import { ref, watch, reactive, onUnmounted } from 'vue';
import { ref, watch, reactive } from 'vue';
import { InformationCircleIcon, ExclamationCircleIcon, MinusCircleIcon, CheckCircleIcon } from '@heroicons/vue/outline';
import { XIcon } from '@heroicons/vue/solid';
import { FIFO } from '@45drives/cockpit-helpers';

View File

@ -1,26 +1,73 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="flex items-center gap-2">
<div class="inline relative">
<input type="text" class="input-textlike pr-10" v-model="hostInput" @change="hostChangeCallback"
ref="hostInputRef" />
<input
type="text"
class="input-textlike pr-10"
v-model="hostInput"
ref="hostInputRef"
@change="hostChangeCallback"
/>
<div class="absolute right-0 inset-y-0 pr-3 flex items-center pointer-events-none">
<ServerIcon class="size-icon icon-default" />
</div>
</div>
<div class="flex items-center cursor-text h-10 grow" @click="typing = true">
<div
class="flex items-center cursor-text h-10 grow"
@click="typing = true"
>
<template v-if="typing">
<input v-model="pathInput" type="text" list="pathInputSuggestions" class="block w-full input-textlike"
@change="pathChangeCallback" ref="pathInputRef" @focusout="typing = false" />
<input
v-model="pathInput"
type="text"
list="pathInputSuggestions"
class="block w-full input-textlike"
@focusout="typing = false"
@change="pathChangeCallback"
ref="pathInputRef"
/>
<datalist id="pathInputSuggestions">
<option v-for="path in pathSuggestions" :value="path" />
<option
v-for="path in pathSuggestions"
:value="path"
/>
</datalist>
</template>
<div v-else class="inline-flex items-center gap-1">
<template v-for="segment, index in pathArr" :key="index">
<ChevronRightIcon v-if="index > 0" class="size-icon icon-default" />
<button @click.prevent.stop="$emit('cd', { path: `/${pathArr.slice(1, index + 1).join('/')}` })"
class="p-2 hover:bg-accent rounded-lg cursor-pointer" v-html="escapeStringHTML(segment)"
:title="escapeString(`/${pathArr.slice(1, index + 1).join('/')}`)"></button>
<div
v-else
class="inline-flex items-center gap-1"
>
<template
v-for="segment, index in pathArr"
:key="index"
>
<ChevronRightIcon
v-if="index > 0"
class="size-icon icon-default"
/>
<button
class="p-2 hover:bg-accent rounded-lg cursor-pointer"
v-html="escapeStringHTML(segment)"
:title="escapeString(`/${pathArr.slice(1, index + 1).join('/')}`)"
@click.prevent.stop="$emit('cd', { path: `/${pathArr.slice(1, index + 1).join('/')}` })"
></button>
</template>
</div>
</div>

View File

@ -1,3 +1,20 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<button @click="showMenu = true">
<AdjustmentsIcon class="size-icon icon-default" />
@ -16,15 +33,16 @@
<LabelledSwitch v-model="darkMode">Dark mode</LabelledSwitch>
<LabelledSwitch v-model="settings.directoryView.showHidden">Show hidden files</LabelledSwitch>
<LabelledSwitch v-model="booleanAnalogs.directoryView.view.bool">List view</LabelledSwitch>
<LabelledSwitch
v-model="settings.directoryView.separateDirs"
>Separate directories while sorting</LabelledSwitch>
<LabelledSwitch v-model="settings.directoryView.separateDirs">Separate directories while sorting
</LabelledSwitch>
</div>
<div v-if="booleanAnalogs.directoryView.view.bool" class="self-stretch">
<div
v-if="booleanAnalogs.directoryView.view.bool"
class="self-stretch"
>
<div>List view columns</div>
<div
class="flex justify-start text-sm rounded-lg divide-x divide-default border border-default shadow"
>
class="flex justify-start text-sm rounded-lg divide-x divide-default border border-default shadow">
<div class="flex flex-col grow items-stretch">
<div class="font-semibold px-2">Visible</div>
<div

View File

@ -1,13 +1,35 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<button @click="updateModel()">
<SortDescendingIcon v-if="reverse" :class="[funcIsMine ? 'icon-45d' : 'icon-default', 'size-icon']" />
<SortAscendingIcon v-else :class="[funcIsMine ? 'icon-45d' : 'icon-default', 'size-icon']" />
<SortDescendingIcon
v-if="reverse"
:class="[funcIsMine ? 'icon-45d' : 'icon-default', 'size-icon']"
/>
<SortAscendingIcon
v-else
:class="[funcIsMine ? 'icon-45d' : 'icon-default', 'size-icon']"
/>
</button>
</template>
<script>
import { nextTick, ref, watch } from 'vue';
import { SortAscendingIcon, SortDescendingIcon } from "@heroicons/vue/solid";
import { SortDescendingIcon } from "@heroicons/vue/solid";
export default {
props: {

View File

@ -40,7 +40,10 @@ If not, see <https://www.gnu.org/licenses/>.
<tbody class="bg-default w-full">
<slot name="tbody">
<tr>
<td colspan="100%" class="text-center align-middle text-muted text-sm">{{ emptyText }}</td>
<td
colspan="100%"
class="text-center align-middle text-muted text-sm"
>{{ emptyText }}</td>
</tr>
</slot>
</tbody>
@ -97,7 +100,7 @@ table.houston-table thead.use-sticky tr th {
table.houston-table th,
table.houston-table td {
@apply py-2 px-4 lg:px-6 whitespace-nowrap text-sm;
@apply py-2 px-4 lg: px-6 whitespace-nowrap text-sm;
}
table.houston-table th:not(.text-right):not(.text-center),
@ -110,6 +113,6 @@ table.houston-table th {
}
table.houston-table tr {
@apply even:bg-accent;
@apply even: bg-accent;
}
</style>

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
*
* This file is part of Cockpit Navigator.
*
* Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Cockpit Navigator.
* If not, see <https://www.gnu.org/licenses/>.
*/
const isObject = (obj) => typeof obj === 'object' && !Array.isArray(obj) && obj !== null;
/**

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
*
* This file is part of Cockpit Navigator.
*
* Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Cockpit Navigator.
* If not, see <https://www.gnu.org/licenses/>.
*/
/**
* Replaces all non-printable characters with an orange span containing the escaped representation of the character
*

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
*
* This file is part of Cockpit Navigator.
*
* Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Cockpit Navigator.
* If not, see <https://www.gnu.org/licenses/>.
*/
import '../globalTypedefs';
/**

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
*
* This file is part of Cockpit Navigator.
*
* Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Cockpit Navigator.
* If not, see <https://www.gnu.org/licenses/>.
*/
import { useSpawn, errorString } from "@45drives/cockpit-helpers";
import { UNIT_SEPARATOR, RECORD_SEPARATOR } from "../constants";
import { szudzikPair, hashString } from "./szudzikPair";

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
*
* This file is part of Cockpit Navigator.
*
* Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Cockpit Navigator.
* If not, see <https://www.gnu.org/licenses/>.
*/
import { useSpawn } from "@45drives/cockpit-helpers";
export async function getGroups() {

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
*
* This file is part of Cockpit Navigator.
*
* Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Cockpit Navigator.
* If not, see <https://www.gnu.org/licenses/>.
*/
import { useSpawn } from "@45drives/cockpit-helpers";
export async function getUsers() {

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
*
* This file is part of Cockpit Navigator.
*
* Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Cockpit Navigator.
* If not, see <https://www.gnu.org/licenses/>.
*/
/**
* Perform isqrt on BigInt
*

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
*
* This file is part of Cockpit Navigator.
*
* Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Cockpit Navigator.
* If not, see <https://www.gnu.org/licenses/>.
*/
import { createApp, reactive } from 'vue';
import App from './App.vue';
import { errorString, FIFO } from '@45drives/cockpit-helpers';

View File

@ -1,69 +0,0 @@
import { useSpawn, errorString } from '@45drives/cockpit-helpers';
/** Run test with given expression and return boolean result. Throws on abnormal errors.
*
* @param {String} check - Argument(s) to test for checking path (man test(1))
* @param {String} path - Path to check
* @param {Object} opts - Options for cockpit.spawn()
* @returns {Promise<Boolean>} Result of test
*/
export const test = async (check, path, opts = { superuser: 'try' }) => {
try {
await useSpawn(['test', ...check.split(/\s+/), path], opts).promise();
return true;
} catch (state) {
if (state.status === 1)
return false;
throw new Error("Failed to check path: " + path + ": " + errorString(state));
}
}
/** Returns true if path exists, false otherwise. Throws on abnormal errors.
*
* @param {String} path - Path to check
* @param {Object} opts - Options for cockpit.spawn()
* @returns {Promise<Boolean>} Result of test
*/
export const checkIfExists = (path, opts = { superuser: 'try' }) => {
return test('-e', path, opts);
}
/**
* @typedef {Object} PermissionsResult
* @property {Boolean} r - Read permission result
* @property {Boolean} w - Write permission result
* @property {Boolean} x - Execute/search permission result
*/
/** Get read, write, and execute permissions for user on path. Throws on abnormal errors.
*
* @param {String} path - Path to check
* @param {Object} opts - Options for cockpit.spawn()
* @returns {Promise<PermissionsResult>} - result of test
*/
export const testPerimssions = async (path, opts = { superuser: 'try' }) => {
return {
r: await test('-r', path, opts),
w: await test('-w', path, opts),
x: await test('-x', path, opts),
}
}
/** Check for rw permissions if path is file or rwx permissions if path is dir.
* Returns false if DNE. Throws on abnormal errors.
*
* @param {String} path - Path to check
* @param {Boolean} readOnly - Set to true to ignore write permissions
* @returns {Promise<Boolean>} Result of check
*/
export const checkIfAllowed = async (path, readOnly = false) => {
if (!await checkIfExists(path))
return false;
const permissions = await testPerimssions(path);
let result = permissions.r;
if (!readOnly)
result &= permissions.w;
if (await test('-d', path))
result &= permissions.x;
return result;
}

View File

@ -4,17 +4,28 @@
<div
class="grid grid-cols-[auto_1fr] grid-rows-[1fr 1fr] md:grid-cols-[auto_3fr_1fr] md:grid-row-[1fr] items-stretch divide-x divide-y divide-default">
<div class="button-group-row p-1 md:px-4 md:py-2 border-t border-default">
<button class="p-2 rounded-lg hover:bg-accent relative" :disabled="!pathHistory.backAllowed()"
@click="back()" @mouseenter="backHistoryDropdown.mouseEnter"
@mouseleave="backHistoryDropdown.mouseLeave">
<button
class="p-2 rounded-lg hover:bg-accent relative"
:disabled="!pathHistory.backAllowed()"
@click="back()"
@mouseenter="backHistoryDropdown.mouseEnter"
@mouseleave="backHistoryDropdown.mouseLeave"
>
<ArrowLeftIcon class="size-icon icon-default" />
<ChevronDownIcon class="w-3 h-3 icon-default absolute bottom-1 right-1"
v-if="pathHistory.backAllowed()" />
<div v-if="backHistoryDropdown.showDropdown"
class="absolute top-full left-0 flex flex-col items-stretch z-50 bg-default shadow-lg rounded-lg overflow-y-auto max-h-80">
<div v-for="item, index in pathHistory.stack.slice(0, pathHistory.index).reverse()"
:key="index" @click="pathHistory.index = pathHistory.index - index"
class="hover:text-white hover:bg-red-600 px-4 py-2 text-sm text-left whitespace-nowrap">
<ChevronDownIcon
class="w-3 h-3 icon-default absolute bottom-1 right-1"
v-if="pathHistory.backAllowed()"
/>
<div
v-if="backHistoryDropdown.showDropdown"
class="absolute top-full left-0 flex flex-col items-stretch z-50 bg-default shadow-lg rounded-lg overflow-y-auto max-h-80"
>
<div
v-for="item, index in pathHistory.stack.slice(0, pathHistory.index).reverse()"
:key="index"
class="hover:text-white hover:bg-red-600 px-4 py-2 text-sm text-left whitespace-nowrap"
@click="pathHistory.index = pathHistory.index - index"
>
<span v-if="item.host !== pathHistory.current()?.host">
{{ item.host ?? cockpit.transport.host }}:
</span>
@ -22,17 +33,28 @@
</div>
</div>
</button>
<button class="p-2 rounded-lg hover:bg-accent relative" :disabled="!pathHistory.forwardAllowed()"
@click="forward()" @mouseenter="forwardHistoryDropdown.mouseEnter"
@mouseleave="forwardHistoryDropdown.mouseLeave">
<button
class="p-2 rounded-lg hover:bg-accent relative"
:disabled="!pathHistory.forwardAllowed()"
@click="forward()"
@mouseenter="forwardHistoryDropdown.mouseEnter"
@mouseleave="forwardHistoryDropdown.mouseLeave"
>
<ArrowRightIcon class="size-icon icon-default" />
<ChevronDownIcon class="w-3 h-3 icon-default absolute bottom-1 right-1"
v-if="pathHistory.forwardAllowed()" />
<div v-if="forwardHistoryDropdown.showDropdown"
class="absolute top-full left-0 flex flex-col items-stretch z-50 bg-default shadow-lg rounded-lg overflow-y-auto max-h-80">
<div v-for="item, index in pathHistory.stack.slice(pathHistory.index + 1)" :key="index"
<ChevronDownIcon
class="w-3 h-3 icon-default absolute bottom-1 right-1"
v-if="pathHistory.forwardAllowed()"
/>
<div
v-if="forwardHistoryDropdown.showDropdown"
class="absolute top-full left-0 flex flex-col items-stretch z-50 bg-default shadow-lg rounded-lg overflow-y-auto max-h-80"
>
<div
v-for="item, index in pathHistory.stack.slice(pathHistory.index + 1)"
:key="index"
class="hover:text-white hover:bg-red-600 px-4 py-2 text-sm text-left whitespace-nowrap"
@click="pathHistory.index = pathHistory.index + index"
class="hover:text-white hover:bg-red-600 px-4 py-2 text-sm text-left whitespace-nowrap">
>
<span v-if="item.host !== pathHistory.current()?.host">
{{ item.host ?? cockpit.transport.host }}:
</span>
@ -40,72 +62,153 @@
</div>
</div>
</button>
<button class="p-2 rounded-lg hover:bg-accent" @click="up()"
:disabled="pathHistory.current() === '/'">
<button
class="p-2 rounded-lg hover:bg-accent"
:disabled="pathHistory.current() === '/'"
@click="up()"
>
<ArrowUpIcon class="size-icon icon-default" />
</button>
<button class="p-2 rounded-lg hover:bg-accent" @click="directoryViewRef.refresh()">
<button
class="p-2 rounded-lg hover:bg-accent"
@click="directoryViewRef.refresh()"
>
<RefreshIcon class="size-icon icon-default" />
</button>
</div>
<div
class="p-1 md:px-4 md:py-2 col-start-1 col-end-3 row-start-2 row-end-3 md:col-start-auto md:col-end-auto md:row-start-auto md:row-end-auto">
<PathBreadCrumbs :host="pathHistory.current()?.host" :path="pathHistory.current()?.path ?? '/'"
@cd="cd" />
<PathBreadCrumbs
:host="pathHistory.current()?.host"
:path="pathHistory.current()?.path ?? '/'"
@cd="cd"
/>
</div>
<div class="p-1 md:px-4 md:py-2">
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<SearchIcon class="size-icon icon-default" aria-hidden="true" />
<SearchIcon
class="size-icon icon-default"
aria-hidden="true"
/>
</div>
<input type="text" class="block input-textlike w-full pl-10" v-model="searchFilterStr"
placeholder="Search in directory (foo*, b?r, *.jpg)" />
<input
type="text"
class="block input-textlike w-full pl-10"
v-model="searchFilterStr"
placeholder="Search in directory (foo*, b?r, *.jpg)"
/>
</div>
</div>
</div>
<div class="grow overflow-hidden">
<DirectoryView :host="pathHistory.current()?.host" :path="pathHistory.current()?.path"
:searchFilterRegExp="searchFilterRegExp" @cd="path => cd({ path })" @edit="openEditor"
@browserAction="handleAction" ref="directoryViewRef" />
<DirectoryView
:host="pathHistory.current()?.host"
:path="pathHistory.current()?.path"
:searchFilterRegExp="searchFilterRegExp"
@cd="path => cd({ path })"
@edit="openEditor"
@browserAction="handleAction"
ref="directoryViewRef"
/>
</div>
</div>
</div>
<ModalPopup :showModal="openFilePromptModal.show" :headerText="openFilePromptModal.entry?.name ?? 'NULL'"
@close="() => openFilePromptModal.close()" autoWidth>
<ModalPopup
:showModal="openFilePromptModal.show"
:headerText="openFilePromptModal.entry?.name ?? 'NULL'"
autoWidth
@close="() => openFilePromptModal.close()"
>
What would you like to do with this {{ openFilePromptModal.entry?.resolvedTypeHuman }}?
<template #footer>
<button type="button" class="btn btn-secondary" @click="() => openFilePromptModal.close()">
<button
type="button"
class="btn btn-secondary"
@click="() => openFilePromptModal.close()"
>
Cancel
</button>
<button type="button" class="btn btn-primary" @click="() => openFilePromptModal.action('editPermissions')">
<button
type="button"
class="btn btn-primary"
@click="() => openFilePromptModal.action('editPermissions')"
>
Edit permissions
</button>
<button v-if="openFilePromptModal.entry?.resolvedType === 'f'" type="button" class="btn btn-primary"
@click="() => openFilePromptModal.action('edit')">
<button
v-if="openFilePromptModal.entry?.resolvedType === 'f'"
type="button"
class="btn btn-primary"
@click="() => openFilePromptModal.action('edit')"
>
Open for editing
</button>
<button v-if="openFilePromptModal.entry?.resolvedType === 'f'" type="button" class="btn btn-primary"
@click="() => openFilePromptModal.action('download')">
<button
v-if="openFilePromptModal.entry?.resolvedType === 'f'"
type="button"
class="btn btn-primary"
@click="() => openFilePromptModal.action('download')"
>
Download
</button>
</template>
</ModalPopup>
<FilePermissions :show="filePermissions.show" @hide="filePermissions.close" :entry="filePermissions.entry" />
<ContextMenu @browserAction="handleAction" :show="contextMenu.show" @hide="contextMenu.close" :entry="contextMenu.entry" :event="contextMenu.event" />
<FilePermissions
:show="filePermissions.show"
:entry="filePermissions.entry"
@hide="filePermissions.close"
/>
<ContextMenu
:show="contextMenu.show"
:entry="contextMenu.entry"
:event="contextMenu.event"
@browserAction="handleAction"
@hide="contextMenu.close"
/>
<Teleport to="#footer-buttons">
<IconToggle v-model="darkMode" v-slot="{ value }">
<MoonIcon v-if="value" class="size-icon icon-default" />
<SunIcon v-else class="size-icon icon-default" />
<IconToggle
v-model="darkMode"
v-slot="{ value }"
>
<MoonIcon
v-if="value"
class="size-icon icon-default"
/>
<SunIcon
v-else
class="size-icon icon-default"
/>
</IconToggle>
<IconToggle v-model="settings.directoryView.showHidden" v-slot="{ value }">
<EyeIcon v-if="value" class="size-icon icon-default" />
<EyeOffIcon v-else class="size-icon icon-default" />
<IconToggle
v-model="settings.directoryView.showHidden"
v-slot="{ value }"
>
<EyeIcon
v-if="value"
class="size-icon icon-default"
/>
<EyeOffIcon
v-else
class="size-icon icon-default"
/>
</IconToggle>
<IconToggle v-model="settings.directoryView.view" trueValue="list" falseValue="grid" v-slot="{ value }">
<ViewListIcon v-if="value" class="size-icon icon-default" />
<ViewGridIcon v-else class="size-icon icon-default" />
<IconToggle
v-model="settings.directoryView.view"
trueValue="list"
falseValue="grid"
v-slot="{ value }"
>
<ViewListIcon
v-if="value"
class="size-icon icon-default"
/>
<ViewGridIcon
v-else
class="size-icon icon-default"
/>
</IconToggle>
</Teleport>
</template>

View File

@ -1,3 +1,20 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="grow">Editing {{ path }}</div>
</template>

View File

@ -1,8 +1,25 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit Navigator.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="grow flex flex-col items-center justify-center gap-10">
<div class="text-header">{{title}}</div>
<div class="text-muted">{{message}}</div>
<div class="text-muted">Redirecting in {{counter}}...</div>
<div class="text-header">{{ title }}</div>
<div class="text-muted">{{ message }}</div>
<div class="text-muted">Redirecting in {{ counter }}...</div>
</div>
</template>