This commit is contained in:
Timothy Jaeryang Baek
2026-03-01 02:29:37 -06:00
parent 234306ff57
commit 3d535db304
3 changed files with 123 additions and 3 deletions

View File

@@ -441,6 +441,18 @@
{filePdfData}
{fileContent}
onDownload={() => downloadFile(selectedFile)}
onSave={async (content) => {
const terminal = selectedTerminal;
if (!terminal || !selectedFile) return;
const fileName = selectedFile.split('/').pop() ?? 'file';
const dir = selectedFile.substring(0, selectedFile.lastIndexOf('/') + 1) || '/';
const file = new File([content], fileName, { type: 'text/plain' });
const result = await uploadToTerminal(terminal.url, terminal.key, dir, file);
toast[result ? 'success' : 'error'](
$i18n.t(result ? 'File saved' : 'Failed to save file')
);
if (result) fileContent = content;
}}
/>
{:else}
{#if uploading}

View File

@@ -7,6 +7,7 @@
import PDFViewer from '../../common/PDFViewer.svelte';
import Tooltip from '../../common/Tooltip.svelte';
import Reset from '../../icons/Reset.svelte';
import PenAlt from '../../icons/PenAlt.svelte';
const i18n = getContext('i18n');
@@ -17,6 +18,41 @@
export let fileContent: string | null = null;
export let onDownload: () => void = () => {};
export let onSave: ((content: string) => Promise<void>) | null = null;
let editing = false;
let editContent = '';
let saving = false;
// Reset edit state when switching files
$: selectedFile, resetEdit();
const resetEdit = () => {
editing = false;
editContent = '';
saving = false;
};
const startEdit = () => {
editContent = fileContent ?? '';
editing = true;
showRaw = true;
};
const saveEdit = async () => {
if (!onSave) return;
saving = true;
await onSave(editContent);
saving = false;
editing = false;
};
const cancelEdit = () => {
editing = false;
editContent = '';
};
$: isTextFile = fileContent !== null && fileImageUrl === null && filePdfData === null;
const MD_EXTS = new Set(['md', 'markdown', 'mdx']);
const CSV_EXTS = new Set(['csv', 'tsv']);
@@ -127,7 +163,10 @@
<Tooltip content={showRaw ? $i18n.t('Preview') : $i18n.t('Source')}>
<button
class="p-1.5 rounded-lg bg-white/80 dark:bg-gray-850/80 backdrop-blur-sm shadow-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-500 dark:text-gray-400"
on:click={() => (showRaw = !showRaw)}
on:click={() => {
if (editing) cancelEdit();
showRaw = !showRaw;
}}
aria-label={showRaw ? $i18n.t('Preview') : $i18n.t('Source')}
>
{#if showRaw}
@@ -171,6 +210,49 @@
</button>
</Tooltip>
{/if}
{#if isTextFile && onSave}
{#if editing}
<Tooltip content={$i18n.t('Cancel')}>
<button
class="p-1.5 rounded-lg bg-white/80 dark:bg-gray-850/80 backdrop-blur-sm shadow-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-500 dark:text-gray-400"
on:click={cancelEdit}
aria-label={$i18n.t('Cancel')}
>
<!-- X icon -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-4">
<path d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z" />
</svg>
</button>
</Tooltip>
<Tooltip content={$i18n.t('Save')}>
<button
class="p-1.5 rounded-lg bg-white/80 dark:bg-gray-850/80 backdrop-blur-sm shadow-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-500 dark:text-gray-400"
on:click={saveEdit}
disabled={saving}
aria-label={$i18n.t('Save')}
>
{#if saving}
<Spinner className="size-4" />
{:else}
<!-- Check icon -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-4">
<path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 0 1 .143 1.052l-8 10.5a.75.75 0 0 1-1.127.075l-4.5-4.5a.75.75 0 0 1 1.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 0 1 1.05-.143Z" clip-rule="evenodd" />
</svg>
{/if}
</button>
</Tooltip>
{:else}
<Tooltip content={$i18n.t('Edit')}>
<button
class="p-1.5 rounded-lg bg-white/80 dark:bg-gray-850/80 backdrop-blur-sm shadow-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-500 dark:text-gray-400"
on:click={startEdit}
aria-label={$i18n.t('Edit')}
>
<PenAlt className="size-4" />
</button>
</Tooltip>
{/if}
{/if}
<button
class="p-1.5 rounded-lg bg-white/80 dark:bg-gray-850/80 backdrop-blur-sm shadow-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-500 dark:text-gray-400"
on:click={onDownload}
@@ -240,8 +322,16 @@
</table>
</div>
{:else}
<pre
class="text-xs font-mono text-gray-800 dark:text-gray-200 whitespace-pre-wrap break-all leading-relaxed p-3">{fileContent}</pre>
{#if editing}
<textarea
bind:value={editContent}
class="w-full h-full text-xs font-mono text-gray-800 dark:text-gray-200 whitespace-pre break-all leading-relaxed p-3 bg-transparent border-none outline-none resize-none"
spellcheck="false"
/>
{:else}
<pre
class="text-xs font-mono text-gray-800 dark:text-gray-200 whitespace-pre-wrap break-all leading-relaxed p-3">{fileContent}</pre>
{/if}
{/if}
{:else}
<div class="text-sm text-gray-400 text-center pt-8">

View File

@@ -0,0 +1,18 @@
<script lang="ts">
export let className = 'size-4';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class={className}
>
<path
d="M14.3632 5.65156L15.8431 4.17157C16.6242 3.39052 17.8905 3.39052 18.6716 4.17157L20.0858 5.58579C20.8668 6.36683 20.8668 7.63316 20.0858 8.41421L18.6058 9.8942M14.3632 5.65156L4.74749 15.2672C4.41542 15.5993 4.21079 16.0376 4.16947 16.5054L3.92738 19.2459C3.87261 19.8659 4.39148 20.3848 5.0115 20.33L7.75191 20.0879C8.21972 20.0466 8.65806 19.8419 8.99013 19.5099L18.6058 9.8942M14.3632 5.65156L18.6058 9.8942"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>