Compact editor UI, move LSP info to settings, redesign status badges
Claude-Session: https://claude.ai/code/session_01PoLmSR1pFVyHf8i5b1rNWk
This commit is contained in:
@@ -5,9 +5,7 @@
|
|||||||
import {
|
import {
|
||||||
insertText,
|
insertText,
|
||||||
prefixLines,
|
prefixLines,
|
||||||
redoEdit,
|
|
||||||
setTypstConfig,
|
setTypstConfig,
|
||||||
undoEdit,
|
|
||||||
wrapSelection,
|
wrapSelection,
|
||||||
} from "$lib/ts/editor-actions";
|
} from "$lib/ts/editor-actions";
|
||||||
import { app } from "$lib/ts/state.svelte";
|
import { app } from "$lib/ts/state.svelte";
|
||||||
@@ -37,10 +35,10 @@
|
|||||||
icon: string,
|
icon: string,
|
||||||
label: string,
|
label: string,
|
||||||
run: () => void,
|
run: () => void,
|
||||||
size = "text-base",
|
size = "text-sm",
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
class="rounded p-1.5 text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-sunken)] hover:text-[var(--color-ink)] disabled:opacity-40 disabled:hover:bg-transparent"
|
class="rounded p-1 text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-sunken)] hover:text-[var(--color-ink)] disabled:opacity-40 disabled:hover:bg-transparent"
|
||||||
title={label}
|
title={label}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
{disabled}
|
{disabled}
|
||||||
@@ -51,17 +49,12 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#snippet divider()}
|
{#snippet divider()}
|
||||||
<div class="mx-1 h-4 w-px shrink-0 bg-[var(--color-line)]"></div>
|
<div class="mx-0.5 h-3.5 w-px shrink-0 bg-[var(--color-line)]"></div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="scroll-thin flex shrink-0 items-center gap-0.5 overflow-x-auto border-b border-[var(--color-line)] bg-[var(--color-surface)] px-2 py-1"
|
class="scroll-thin flex shrink-0 items-center gap-px overflow-x-auto border-b border-[var(--color-line)] bg-[var(--color-surface)] px-1.5 py-0.5"
|
||||||
>
|
>
|
||||||
{@render action("ph:arrow-counter-clockwise", "Undo", () => undoEdit(view))}
|
|
||||||
{@render action("ph:arrow-clockwise", "Redo", () => redoEdit(view))}
|
|
||||||
|
|
||||||
{@render divider()}
|
|
||||||
|
|
||||||
{@render action("ph:text-h", "Heading", () => prefixLines(view, "= ", "Heading"))}
|
{@render action("ph:text-h", "Heading", () => prefixLines(view, "= ", "Heading"))}
|
||||||
{@render action("ph:text-b", "Bold", () => wrapSelection(view, "*", "*", "bold"))}
|
{@render action("ph:text-b", "Bold", () => wrapSelection(view, "*", "*", "bold"))}
|
||||||
{@render action("ph:text-italic", "Italic", () =>
|
{@render action("ph:text-italic", "Italic", () =>
|
||||||
@@ -103,7 +96,7 @@
|
|||||||
{@render divider()}
|
{@render divider()}
|
||||||
|
|
||||||
<select
|
<select
|
||||||
class="max-w-36 rounded border border-[var(--color-line)] bg-[var(--color-surface)] px-1.5 py-1 text-xs text-[var(--color-ink)] focus:border-[var(--color-accent)] focus:outline-none disabled:opacity-40"
|
class="max-w-24 rounded border border-[var(--color-line)] bg-[var(--color-surface)] px-1 py-0.5 text-xs text-[var(--color-ink)] focus:border-[var(--color-accent)] focus:outline-none disabled:opacity-40"
|
||||||
aria-label="Document font"
|
aria-label="Document font"
|
||||||
{disabled}
|
{disabled}
|
||||||
bind:value={selectedFont}
|
bind:value={selectedFont}
|
||||||
@@ -118,14 +111,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<button
|
{@render action("ph:file-text", "Page settings", onpagesettings)}
|
||||||
class="flex items-center gap-1.5 rounded px-2 py-1 text-xs text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-sunken)] hover:text-[var(--color-ink)] disabled:opacity-40"
|
|
||||||
{disabled}
|
|
||||||
onclick={onpagesettings}
|
|
||||||
>
|
|
||||||
<Icon icon="ph:file-text" />
|
|
||||||
Page
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
| "appearance"
|
| "appearance"
|
||||||
| "accessibility"
|
| "accessibility"
|
||||||
| "account"
|
| "account"
|
||||||
|
| "lsp"
|
||||||
| "about";
|
| "about";
|
||||||
|
|
||||||
const sections: { id: Section; label: string; icon: string }[] = [
|
const sections: { id: Section; label: string; icon: string }[] = [
|
||||||
@@ -41,9 +42,17 @@
|
|||||||
{ id: "appearance", label: "Appearance", icon: "ph:palette" },
|
{ id: "appearance", label: "Appearance", icon: "ph:palette" },
|
||||||
{ id: "accessibility", label: "Accessibility", icon: "ph:wheelchair" },
|
{ id: "accessibility", label: "Accessibility", icon: "ph:wheelchair" },
|
||||||
{ id: "account", label: "Account", icon: "ph:user-circle" },
|
{ id: "account", label: "Account", icon: "ph:user-circle" },
|
||||||
|
{ id: "lsp", label: "Language Server", icon: "ph:plugs-connected" },
|
||||||
{ id: "about", label: "About", icon: "ph:info" },
|
{ id: "about", label: "About", icon: "ph:info" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const lspLabel: Record<string, string> = {
|
||||||
|
off: "Off",
|
||||||
|
starting: "Starting…",
|
||||||
|
on: "Running",
|
||||||
|
unavailable: "Unavailable",
|
||||||
|
};
|
||||||
|
|
||||||
const accentPresets = [
|
const accentPresets = [
|
||||||
"#3b6cf6",
|
"#3b6cf6",
|
||||||
"#7c5cfc",
|
"#7c5cfc",
|
||||||
@@ -227,7 +236,9 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<span class="text-[var(--color-ink-muted)]">
|
<span class="text-[var(--color-ink-muted)]">
|
||||||
Saves the file being edited after you stop typing.
|
Saves the file being edited after you stop typing. Cloud-linked
|
||||||
|
files always save to disk every few seconds regardless of this
|
||||||
|
setting, since they sync live and have no manual Save button.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -460,6 +471,42 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if section === "lsp"}
|
||||||
|
<div class="flex flex-col gap-5">
|
||||||
|
<div class="flex items-center gap-3 rounded-md border border-[var(--color-line)] bg-[var(--color-surface-muted)] p-3">
|
||||||
|
<span
|
||||||
|
class="h-2.5 w-2.5 rounded-full
|
||||||
|
{app.lspStatus === 'on'
|
||||||
|
? 'bg-[var(--color-success)]'
|
||||||
|
: app.lspStatus === 'starting'
|
||||||
|
? 'bg-[var(--color-accent)]'
|
||||||
|
: 'bg-[var(--color-ink-muted)]'}"
|
||||||
|
></span>
|
||||||
|
<div class="text-xs">
|
||||||
|
<p class="font-medium">{lspLabel[app.lspStatus]}</p>
|
||||||
|
<p class="text-[var(--color-ink-muted)]">
|
||||||
|
Reflects the file currently open in the editor.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-xs text-[var(--color-ink-muted)]">
|
||||||
|
Typst Desktop uses <span class="font-medium">tinymist</span>, the
|
||||||
|
official Typst language server, to provide autocomplete, diagnostics,
|
||||||
|
and hover info while editing. It must be installed and available on
|
||||||
|
your system PATH — Typst Desktop does not bundle or install it for
|
||||||
|
you.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{#if app.lspStatus === "unavailable"}
|
||||||
|
<div
|
||||||
|
class="rounded-md border border-[var(--color-danger)]/30 bg-[var(--color-danger)]/10 p-3 text-xs text-[var(--color-danger)]"
|
||||||
|
>
|
||||||
|
tinymist could not be started. Confirm it's installed and on your
|
||||||
|
PATH, then reopen the file.
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
@@ -521,7 +568,8 @@
|
|||||||
{@const draftless =
|
{@const draftless =
|
||||||
section === "about" ||
|
section === "about" ||
|
||||||
section === "appearance" ||
|
section === "appearance" ||
|
||||||
section === "accessibility"}
|
section === "accessibility" ||
|
||||||
|
section === "lsp"}
|
||||||
<button
|
<button
|
||||||
class="rounded-md px-3 py-1.5 text-xs text-[var(--color-ink-muted)] hover:bg-[var(--color-surface-sunken)]"
|
class="rounded-md px-3 py-1.5 text-xs text-[var(--color-ink-muted)] hover:bg-[var(--color-surface-sunken)]"
|
||||||
onclick={onclose}
|
onclick={onclose}
|
||||||
|
|||||||
+154
-51
@@ -3,6 +3,7 @@
|
|||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { save } from "@tauri-apps/plugin-dialog";
|
import { save } from "@tauri-apps/plugin-dialog";
|
||||||
import { writeImage, writeText } from "@tauri-apps/plugin-clipboard-manager";
|
import { writeImage, writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||||
|
import { Image } from "@tauri-apps/api/image";
|
||||||
import { revealItemInDir } from "@tauri-apps/plugin-opener";
|
import { revealItemInDir } from "@tauri-apps/plugin-opener";
|
||||||
import { getCurrentWebview } from "@tauri-apps/api/webview";
|
import { getCurrentWebview } from "@tauri-apps/api/webview";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
import PageSettingsModal from "$lib/components/PageSettingsModal.svelte";
|
import PageSettingsModal from "$lib/components/PageSettingsModal.svelte";
|
||||||
|
|
||||||
import type { EditorView } from "@codemirror/view";
|
import type { EditorView } from "@codemirror/view";
|
||||||
import { insertText } from "$lib/ts/editor-actions";
|
import { insertText, redoEdit, undoEdit } from "$lib/ts/editor-actions";
|
||||||
|
|
||||||
import * as api from "$lib/ts/api";
|
import * as api from "$lib/ts/api";
|
||||||
import type { BrowseEntry, CloudFile, CloudFolder } from "$lib/ts/api";
|
import type { BrowseEntry, CloudFile, CloudFolder } from "$lib/ts/api";
|
||||||
@@ -43,6 +44,8 @@
|
|||||||
refreshEntries,
|
refreshEntries,
|
||||||
refreshTarget,
|
refreshTarget,
|
||||||
removeDownloadedDocument,
|
removeDownloadedDocument,
|
||||||
|
resolveCollabConflict,
|
||||||
|
cancelCollabConflict,
|
||||||
runSync,
|
runSync,
|
||||||
saveAndCompile,
|
saveAndCompile,
|
||||||
scheduleAutosave,
|
scheduleAutosave,
|
||||||
@@ -90,6 +93,16 @@
|
|||||||
let selectedIsDir = $state(false);
|
let selectedIsDir = $state(false);
|
||||||
let treeDropTarget = $state<string | null>(null);
|
let treeDropTarget = $state<string | null>(null);
|
||||||
|
|
||||||
|
const SIDEBAR_COLLAPSED_KEY = "editor-sidebar-collapsed";
|
||||||
|
let sidebarCollapsed = $state(
|
||||||
|
localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === "true",
|
||||||
|
);
|
||||||
|
|
||||||
|
function toggleSidebar() {
|
||||||
|
sidebarCollapsed = !sidebarCollapsed;
|
||||||
|
localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(sidebarCollapsed));
|
||||||
|
}
|
||||||
|
|
||||||
const selectedFolder = $derived(
|
const selectedFolder = $derived(
|
||||||
!selectedEntry
|
!selectedEntry
|
||||||
? ""
|
? ""
|
||||||
@@ -371,13 +384,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function pngBytesToRgba(bytes: Uint8Array) {
|
||||||
|
const blob = new Blob([bytes], { type: "image/png" });
|
||||||
|
const bitmap = await createImageBitmap(blob);
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = bitmap.width;
|
||||||
|
canvas.height = bitmap.height;
|
||||||
|
const context = canvas.getContext("2d");
|
||||||
|
if (!context) throw new Error("Could not create canvas context");
|
||||||
|
context.drawImage(bitmap, 0, 0);
|
||||||
|
const { data } = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
return { rgba: new Uint8Array(data.buffer), width: canvas.width, height: canvas.height };
|
||||||
|
}
|
||||||
|
|
||||||
async function copyAs(format: string) {
|
async function copyAs(format: string) {
|
||||||
if (!app.target) return;
|
if (!app.target) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (format === "png") {
|
if (format === "png") {
|
||||||
const bytes = await api.renderTargetPng(app.target.path);
|
const bytes = await api.renderTargetPng(app.target.path);
|
||||||
await writeImage(new Uint8Array(bytes));
|
const { rgba, width, height } = await pngBytesToRgba(new Uint8Array(bytes));
|
||||||
|
const image = await Image.new(rgba, width, height);
|
||||||
|
await writeImage(image);
|
||||||
} else if (format === "svg") {
|
} else if (format === "svg") {
|
||||||
const svg = app.compiled?.pages[0];
|
const svg = app.compiled?.pages[0];
|
||||||
if (!svg) {
|
if (!svg) {
|
||||||
@@ -506,22 +534,41 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const lspLabel: Record<string, string> = {
|
|
||||||
off: "LSP off",
|
|
||||||
starting: "LSP starting",
|
|
||||||
on: "LSP ready",
|
|
||||||
unavailable: "LSP unavailable",
|
|
||||||
};
|
|
||||||
|
|
||||||
const wsLabel: Record<string, string> = {
|
const wsLabel: Record<string, string> = {
|
||||||
connected: "Live sync",
|
connected: "Live sync",
|
||||||
connecting: "Connecting…",
|
connecting: "Connecting…",
|
||||||
offline: "Offline (polling)",
|
offline: "Offline (polling)",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const collabLabel: Record<string, string> = {
|
||||||
|
connected: "Live",
|
||||||
|
connecting: "Connecting…",
|
||||||
|
offline: "Offline — editing locally",
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={handleKeydown} />
|
<svelte:window on:keydown={handleKeydown} />
|
||||||
|
|
||||||
|
{#snippet statusBadge(
|
||||||
|
icon: string,
|
||||||
|
label: string,
|
||||||
|
tone: "success" | "accent" | "muted",
|
||||||
|
spin = false,
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
class="flex items-center gap-1 rounded-full px-2 py-1 text-[11px] font-medium
|
||||||
|
{tone === 'success'
|
||||||
|
? 'bg-[var(--color-success)]/10 text-[var(--color-success)]'
|
||||||
|
: tone === 'accent'
|
||||||
|
? 'bg-[var(--color-accent)]/10 text-[var(--color-accent)]'
|
||||||
|
: 'bg-[var(--color-surface-sunken)] text-[var(--color-ink-muted)]'}"
|
||||||
|
>
|
||||||
|
<Icon {icon} class="text-xs {spin ? 'animate-spin' : ''}" />
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
<div class="flex h-screen flex-col">
|
<div class="flex h-screen flex-col">
|
||||||
<header
|
<header
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
@@ -535,15 +582,31 @@
|
|||||||
<Icon icon="ph:arrow-left" />
|
<Icon icon="ph:arrow-left" />
|
||||||
Files
|
Files
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{#if editorView}
|
||||||
|
<button
|
||||||
|
class="rounded-md p-1.5 text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-ink)]"
|
||||||
|
title="Undo"
|
||||||
|
aria-label="Undo"
|
||||||
|
onclick={() => undoEdit(editorView)}
|
||||||
|
>
|
||||||
|
<Icon icon="ph:arrow-counter-clockwise" class="text-sm" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="rounded-md p-1.5 text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-ink)]"
|
||||||
|
title="Redo"
|
||||||
|
aria-label="Redo"
|
||||||
|
onclick={() => redoEdit(editorView)}
|
||||||
|
>
|
||||||
|
<Icon icon="ph:arrow-clockwise" class="text-sm" />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<span data-tauri-drag-region class="text-sm font-medium">
|
<span data-tauri-drag-region class="text-sm font-medium">
|
||||||
{app.target?.path.split("/").pop()}
|
{app.target?.path.split("/").pop()}
|
||||||
</span>
|
</span>
|
||||||
{#if app.target?.standalone}
|
{#if app.target?.standalone}
|
||||||
<span
|
{@render statusBadge("ph:file", "Single file", "muted")}
|
||||||
class="rounded bg-[var(--color-surface-sunken)] px-1.5 py-0.5 text-[10px] text-[var(--color-ink-muted)]"
|
|
||||||
>
|
|
||||||
single file
|
|
||||||
</span>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if app.dirty}
|
{#if app.dirty}
|
||||||
<span class="h-1.5 w-1.5 rounded-full bg-[var(--color-accent)]"></span>
|
<span class="h-1.5 w-1.5 rounded-full bg-[var(--color-accent)]"></span>
|
||||||
@@ -579,45 +642,53 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if app.view === "editor"}
|
{#if app.view === "editor"}
|
||||||
<span
|
{#if app.account && (app.target?.cloud_project_id || app.documentLink) && !app.collabIntent}
|
||||||
class="flex items-center gap-1 text-[10px] text-[var(--color-ink-muted)]"
|
<span title="Cloud sync connection">
|
||||||
title="Typst language server (tinymist)"
|
{@render statusBadge(
|
||||||
>
|
app.wsStatus === "connected"
|
||||||
<span
|
? "ph:cloud-check"
|
||||||
class="h-1.5 w-1.5 rounded-full
|
: app.wsStatus === "connecting"
|
||||||
{app.lspStatus === 'on'
|
? "ph:circle-notch"
|
||||||
? 'bg-[var(--color-success)]'
|
: "ph:cloud-slash",
|
||||||
: app.lspStatus === 'starting'
|
wsLabel[app.wsStatus] ?? "Offline (polling)",
|
||||||
? 'bg-[var(--color-accent)]'
|
app.wsStatus === "connected"
|
||||||
: 'bg-[var(--color-ink-muted)]'}"
|
? "success"
|
||||||
></span>
|
: app.wsStatus === "connecting"
|
||||||
{lspLabel[app.lspStatus]}
|
? "accent"
|
||||||
</span>
|
: "muted",
|
||||||
|
app.wsStatus === "connecting",
|
||||||
{#if app.account}
|
)}
|
||||||
<span
|
|
||||||
class="flex items-center gap-1 text-[10px] text-[var(--color-ink-muted)]"
|
|
||||||
title="Cloud sync connection"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="h-1.5 w-1.5 rounded-full
|
|
||||||
{app.wsStatus === 'connected'
|
|
||||||
? 'bg-[var(--color-success)]'
|
|
||||||
: app.wsStatus === 'connecting'
|
|
||||||
? 'bg-[var(--color-accent)]'
|
|
||||||
: 'bg-[var(--color-ink-muted)]'}"
|
|
||||||
></span>
|
|
||||||
{wsLabel[app.wsStatus] ?? "Offline (polling)"}
|
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button
|
{#if app.collabIntent}
|
||||||
class="flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-ink)]"
|
<span title="Realtime cloud sync">
|
||||||
onclick={saveAndCompile}
|
{@render statusBadge(
|
||||||
>
|
app.collabStatus === "connected"
|
||||||
<Icon icon="ph:floppy-disk" />
|
? "ph:broadcast"
|
||||||
Save
|
: app.collabStatus === "connecting"
|
||||||
</button>
|
? "ph:circle-notch"
|
||||||
|
: "ph:wifi-slash",
|
||||||
|
collabLabel[app.collabStatus ?? "offline"],
|
||||||
|
app.collabStatus === "connected"
|
||||||
|
? "success"
|
||||||
|
: app.collabStatus === "connecting"
|
||||||
|
? "accent"
|
||||||
|
: "muted",
|
||||||
|
app.collabStatus === "connecting",
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !app.collabIntent}
|
||||||
|
<button
|
||||||
|
class="flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-ink)]"
|
||||||
|
onclick={saveAndCompile}
|
||||||
|
>
|
||||||
|
<Icon icon="ph:floppy-disk" />
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="group relative">
|
<div class="group relative">
|
||||||
<button
|
<button
|
||||||
@@ -661,7 +732,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if app.target?.cloud_project_id || app.documentLink}
|
{#if (app.target?.cloud_project_id || app.documentLink) && !app.collabIntent}
|
||||||
<button
|
<button
|
||||||
class="flex items-center gap-1.5 rounded-md bg-[var(--color-accent)] px-2.5 py-1.5 text-xs font-medium text-white transition hover:opacity-90 disabled:opacity-50"
|
class="flex items-center gap-1.5 rounded-md bg-[var(--color-accent)] px-2.5 py-1.5 text-xs font-medium text-white transition hover:opacity-90 disabled:opacity-50"
|
||||||
disabled={app.syncing}
|
disabled={app.syncing}
|
||||||
@@ -789,7 +860,20 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
{#if !app.target?.standalone}
|
{#if !app.target?.standalone && sidebarCollapsed}
|
||||||
|
<div
|
||||||
|
class="flex w-8 shrink-0 flex-col items-center border-r border-[var(--color-line)] bg-[var(--color-surface)] py-1.5"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="rounded p-1 text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-sunken)] hover:text-[var(--color-ink)]"
|
||||||
|
title="Show files"
|
||||||
|
aria-label="Show files"
|
||||||
|
onclick={toggleSidebar}
|
||||||
|
>
|
||||||
|
<Icon icon="ph:sidebar-simple" class="text-base" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{:else if !app.target?.standalone}
|
||||||
<div
|
<div
|
||||||
class="flex w-56 shrink-0 flex-col border-r border-[var(--color-line)] bg-[var(--color-surface)]"
|
class="flex w-56 shrink-0 flex-col border-r border-[var(--color-line)] bg-[var(--color-surface)]"
|
||||||
>
|
>
|
||||||
@@ -801,6 +885,14 @@
|
|||||||
>
|
>
|
||||||
{selectedFolder ? selectedFolder : "Files"}
|
{selectedFolder ? selectedFolder : "Files"}
|
||||||
</span>
|
</span>
|
||||||
|
<button
|
||||||
|
class="shrink-0 rounded p-1 text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-sunken)] hover:text-[var(--color-ink)]"
|
||||||
|
title="Hide files"
|
||||||
|
aria-label="Hide files"
|
||||||
|
onclick={toggleSidebar}
|
||||||
|
>
|
||||||
|
<Icon icon="ph:sidebar-simple" class="text-sm" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FileTree
|
<FileTree
|
||||||
@@ -855,6 +947,9 @@
|
|||||||
filePath={app.activePath}
|
filePath={app.activePath}
|
||||||
targetPath={app.target?.path ?? ""}
|
targetPath={app.target?.path ?? ""}
|
||||||
diagnostics={app.diagnostics}
|
diagnostics={app.diagnostics}
|
||||||
|
collab={app.collab
|
||||||
|
? { text: app.collab.text, awareness: app.collab.provider.awareness }
|
||||||
|
: null}
|
||||||
onchange={(value) => {
|
onchange={(value) => {
|
||||||
app.editorContent = value;
|
app.editorContent = value;
|
||||||
app.dirty = true;
|
app.dirty = true;
|
||||||
@@ -1148,3 +1243,11 @@
|
|||||||
onclose={close}
|
onclose={close}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if app.collabConflict}
|
||||||
|
<ConflictModal
|
||||||
|
conflicts={[app.collabConflict]}
|
||||||
|
onresolve={(resolutions) => resolveCollabConflict(resolutions[0]?.content ?? "")}
|
||||||
|
onclose={cancelCollabConflict}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user