This commit is contained in:
Timothy Jaeryang Baek committed 2026-08-09 13:22:46 -06:00
1 parent 5caa91a493
commit 2dadc5435a
7 files changed
+101 -38

No files matched your search

@@ -7,6 +7,7 @@
export let count: number = 0;
export let hasFiles: boolean = false;
export let canDelete = true;
export let onDelete: () => void = () => {};
export let onDownload: () => void = () => {};
@@ -64,8 +65,9 @@
<Tooltip content={$i18n.t('Delete')}>
<button
class="p-1 rounded transition text-gray-400 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-600 dark:hover:text-gray-400"
class="p-1 rounded transition text-gray-400 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-600 dark:hover:text-gray-400 disabled:opacity-30 disabled:hover:bg-transparent"
on:click={onDelete}
disabled={!canDelete}
aria-label={$i18n.t('Delete')}
>
<GarbageBin className="size-3.5" />
@@ -32,6 +32,10 @@
export let onSelect: (entry: FileEntry, event: MouseEvent) => void = () => {};
export let onLongPress: () => void = () => {};
export let showDate: boolean = false;
export let parentWritable = true;
$: writable = entry.writable !== false;
$: canMutate = parentWritable && writable;
const formatRelativeTime = (epoch: number): string => {
const diff = Math.floor(Date.now() / 1000) - epoch;
@@ -143,6 +147,7 @@
role={entry.type === 'directory' ? 'button' : undefined}
on:dragover={(e) => {
if (entry.type !== 'directory') return;
if (!writable) return;
if (!e.dataTransfer?.types.includes('application/x-terminal-file-move')) return;
e.preventDefault();
e.stopPropagation();
@@ -155,6 +160,7 @@
}}
on:drop={(e) => {
if (entry.type !== 'directory') return;
if (!writable) return;
const raw = e.dataTransfer?.getData('application/x-terminal-file-move');
if (!raw) return;
e.preventDefault();
@@ -173,8 +179,12 @@
>
<button
class="flex-1 flex items-center gap-2 px-3 py-1.5 text-left min-w-0"
draggable={true}
draggable={canMutate}
on:dragstart={(e) => {
if (!canMutate) {
e.preventDefault();
return;
}
const filePath = `${currentPath}${entry.name}`;
// If dragging a selected item, drag all selected
if (selected && selectedPaths.size > 1) {
@@ -282,6 +292,9 @@
{entry.name}
</span>
{/if}
{#if !writable && !renaming}
<span class="text-[10px] text-gray-400 shrink-0">Read-only</span>
{/if}
{#if entry.type === 'file' && entry.size !== undefined && !renaming}
{#if showDate && entry.modified}
<span class="text-[10px] text-gray-400 shrink-0"
@@ -354,9 +367,11 @@
<button
type="button"
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] hover:bg-gray-50/40 dark:hover:bg-gray-800/40 transition"
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] hover:bg-gray-50/40 dark:hover:bg-gray-800/40 transition disabled:opacity-40 disabled:hover:bg-transparent"
disabled={!canMutate}
on:click={(e) => {
e.stopPropagation();
if (!canMutate) return;
startRename();
}}
>
@@ -366,9 +381,11 @@
<button
type="button"
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] hover:bg-gray-50/40 dark:hover:bg-gray-800/40 transition"
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] hover:bg-gray-50/40 dark:hover:bg-gray-800/40 transition disabled:opacity-40 disabled:hover:bg-transparent"
disabled={!canMutate}
on:click={(e) => {
e.stopPropagation();
if (!canMutate) return;
onDelete(`${currentPath}${entry.name}`, entry.name);
}}
>
@@ -14,6 +14,7 @@
export let breadcrumbs: { label: string; path: string }[] = [];
export let selectedFile: string | null = null;
export let loading = false;
export let writable = true;
export let onNavigate: (path: string) => void = () => {};
export let onRefresh: () => void = () => {};
@@ -114,6 +115,7 @@
: ''}"
on:click={() => onNavigate(crumb.path)}
on:dragover={(e) => {
if (!writable) return;
if (!e.dataTransfer?.types.includes('application/x-terminal-file-move')) return;
e.preventDefault();
e.stopPropagation();
@@ -123,6 +125,7 @@
if (dragOverCrumb === i) dragOverCrumb = null;
}}
on:drop={(e) => {
if (!writable) return;
const raw = e.dataTransfer?.getData('application/x-terminal-file-move');
if (!raw) return;
e.preventDefault();
@@ -145,6 +148,11 @@
</span>
{/if}
</div>
{#if !writable}
<span class="text-[10px] text-gray-400 dark:text-gray-500 shrink-0">
Read-only
</span>
{/if}
<Tooltip content={$i18n.t('Refresh')}>
<button
@@ -240,8 +248,9 @@
</Dropdown>
<Tooltip content={$i18n.t('New Folder')}>
<button
class="shrink-0 p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400"
class="shrink-0 p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 disabled:opacity-30 disabled:hover:bg-transparent"
on:click={onNewFolder}
disabled={!writable}
aria-label={$i18n.t('New Folder')}
>
<NewFolderAlt className="size-3.5" />
@@ -249,8 +258,9 @@
</Tooltip>
<Tooltip content={$i18n.t('New File')}>
<button
class="shrink-0 p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400"
class="shrink-0 p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 disabled:opacity-30 disabled:hover:bg-transparent"
on:click={onNewFile}
disabled={!writable}
aria-label={$i18n.t('New File')}
>
<FilePlusAlt className="size-3.5" />
@@ -279,8 +289,9 @@
</Tooltip>
<Tooltip content={$i18n.t('Upload')}>
<button
class="shrink-0 p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400"
class="shrink-0 p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 disabled:opacity-30 disabled:hover:bg-transparent"
on:click={() => uploadInput?.click()}
disabled={!writable}
aria-label={$i18n.t('Upload')}
>
<svg
@@ -305,7 +316,7 @@
multiple
hidden
on:change={async () => {
if (!uploadInput?.files?.length) return;
if (!writable || !uploadInput?.files?.length) return;
onUploadFiles(Array.from(uploadInput.files));
uploadInput.value = '';
}}
@@ -41,6 +41,7 @@
export let onSheetChange: ((sheet: string) => void) | null = null;
export let overlay = false;
export let readOnly = false;
export let onSave: ((content: string) => Promise<void>) | null = null;
@@ -59,6 +60,7 @@
};
export const startEdit = async () => {
if (readOnly) return;
editContent = fileContent ?? '';
editing = true;
showRaw = true;
@@ -67,7 +69,7 @@
};
export const saveEdit = async () => {
if (!onSave) return;
if (!onSave || readOnly) return;
saving = true;
await onSave(editContent);
saving = false;
@@ -81,7 +83,7 @@
/** Save code file directly from CodeMirror */
export const saveCodeFile = async () => {
if (!onSave) return;
if (!onSave || readOnly) return;
saving = true;
const content = fileCodeEditorRef?.getValue() ?? '';
await onSave(content);
@@ -426,7 +428,7 @@
bind:this={fileCodeEditorRef}
value={fileContent ?? ''}
filePath={selectedFile}
{onSave}
onSave={readOnly ? null : onSave}
/>
</div>
{:else if isMarkdown && !showRaw}
@@ -439,7 +441,7 @@
bind:this={fileCodeEditorRef}
value={fileContent ?? ''}
filePath={selectedFile}
{onSave}
onSave={readOnly ? null : onSave}
/>
</div>
{:else if isCsv && !showRaw && csvRows.length > 0}
@@ -496,7 +498,7 @@
bind:this={fileCodeEditorRef}
value={fileContent ?? ''}
filePath={selectedFile}
{onSave}
onSave={readOnly ? null : onSave}
/>
</div>
{:else if isSvg && highlightedHtml && !showRaw}