Skip to content

Commit

Permalink
editor report: migrate to Svelte 5 runes
Browse files Browse the repository at this point in the history
  • Loading branch information
yagebu committed Nov 24, 2024
1 parent ea970fa commit a07df82
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 86 deletions.
11 changes: 10 additions & 1 deletion frontend/src/reports/editor/AppMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@
@component
An application menu.
-->
<script lang="ts">
import type { Snippet } from "svelte";
interface Props {
children: Snippet;
}
let { children }: Props = $props();
</script>

<div role="menubar">
<slot />
{@render children()}
</div>

<style>
Expand Down
26 changes: 19 additions & 7 deletions frontend/src/reports/editor/AppMenuItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,37 @@
The default slot should filled with its vertically arranged sub-items.
-->
<script lang="ts">
/** The name of the menu item. */
export let name: string;
import type { Snippet } from "svelte";
let open = false;
interface Props {
/** The name of the menu item. */
name: string;
children: Snippet;
}
let { name, children }: Props = $props();
let open = $state(false);
</script>

<span
class:open
tabindex="0"
role="menuitem"
on:keydown={(ev) => {
if (ev.key === "ArrowDown") {
onblur={() => {
open = false;
}}
onkeydown={(event) => {
if (event.key === "Escape") {
open = false;
} else if (event.key === "ArrowDown") {
open = true;
}
}}
>
{name}
<ul>
<slot />
<ul role="menu">
{@render children()}
</ul>
</span>

Expand Down
52 changes: 31 additions & 21 deletions frontend/src/reports/editor/AppMenuSubItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,39 @@
A sub-item in an app menu.
-->
<script lang="ts">
/** A title (optional) for the element. */
export let title: string | undefined = undefined;
/** Whether this menu item should be marked as selected. */
export let selected = false;
/** The action to execute on click. */
export let action: () => void;
import type { Snippet } from "svelte";
interface Props {
/** A title (optional) for the element. */
title?: string;
/** Whether this menu item should be marked as selected. */
selected?: boolean;
/** The action to execute on click. */
action: () => void;
children: Snippet;
right?: Snippet;
}
let { title, selected = false, action, children, right }: Props = $props();
</script>

<li class:selected {title}>
<button type="button" on:click={action}>
<slot />
{#if $$slots.right}
<span>
<slot name="right" />
</span>
{/if}
</button>
<li
class:selected
{title}
role="menuitem"
onclick={action}
onkeydown={(event) => {
if (event.key === "Enter") {
action();
}
}}
>
{@render children()}
{#if right}
<span>
{@render right()}
</span>
{/if}
</li>

<style>
Expand All @@ -29,18 +45,12 @@
li {
padding: 0.25em 0.5em;
cursor: pointer;
}
span {
float: right;
}
button {
display: contents;
color: inherit;
}
li:hover,
li:focus-visible {
background-color: var(--background-darker);
Expand Down
93 changes: 50 additions & 43 deletions frontend/src/reports/editor/Editor.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import type { LanguageSupport } from "@codemirror/language";
import type { EditorView } from "@codemirror/view";
import { onMount } from "svelte";
import { onMount, untrack } from "svelte";
import { get, put } from "../../api";
import type { SourceFile } from "../../api/validators";
Expand All @@ -19,18 +19,22 @@
import { searchParams } from "../../stores/url";
import EditorMenu from "./EditorMenu.svelte";
export let source: SourceFile;
export let beancount_language_support: LanguageSupport;
interface Props {
source: SourceFile;
beancount_language_support: LanguageSupport;
}
let { source, beancount_language_support }: Props = $props();
$: file_path = source.file_path;
let file_path = $derived(source.file_path);
let changed = false;
let changed = $state(false);
const onDocChanges = () => {
changed = true;
};
let sha256sum = "";
let saving = false;
let sha256sum = $state("");
let saving = $state(false);
/**
* Save the contents of the editor.
Expand Down Expand Up @@ -73,48 +77,48 @@
beancount_language_support,
);
// update editor contents if source changes
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/no-unnecessary-condition
$: if (source) {
editor.dispatch(replaceContents(editor.state, source.source));
sha256sum = source.sha256sum;
editor.focus();
changed = false;
}
// wrap this in a function to not trigger the reactive block below
// on store updates.
function jumpToInsertOption() {
const opts = $fava_options.insert_entry.filter(
(f) => f.filename === file_path,
);
const line = parseInt($searchParams.get("line") ?? "0", 10);
const last_insert_opt = opts[opts.length - 1];
const lineToScrollTo = (() => {
$effect(() => {
// update editor contents if source changes
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
source;
untrack(() => {
editor.dispatch(replaceContents(editor.state, source.source));
sha256sum = source.sha256sum;
editor.focus();
changed = false;
});
});
$effect(() => {
// Go to line if the edited file changes.
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
file_path;
untrack(() => {
const opts = $fava_options.insert_entry.filter(
(f) => f.filename === file_path,
);
const last_insert_opt = opts[opts.length - 1];
const line = parseInt($searchParams.get("line") ?? "0", 10);
let line_to_scroll_to = null;
if (line > 0) {
return line;
line_to_scroll_to = line;
} else if (last_insert_opt) {
line_to_scroll_to = last_insert_opt.lineno - 1;
}
if (last_insert_opt) {
return last_insert_opt.lineno - 1;
}
return editor.state.doc.lines;
})();
editor.dispatch(scrollToLine(editor.state, lineToScrollTo));
}
// Go to line if the edited file changes.
$: if (file_path) {
jumpToInsertOption();
}
// Update diagnostics, showing errors in the editor
$: {
editor.dispatch(
scrollToLine(editor.state, line_to_scroll_to ?? editor.state.doc.lines),
);
});
});
$effect(() => {
// Update diagnostics, showing errors in the editor
// Only show errors for this file, or general errors (AKA no source)
const errorsForFile = $errors.filter(
(err) => err.source === null || err.source.filename === file_path,
);
editor.dispatch(setErrors(editor.state, errorsForFile));
}
});
const checkEditorChanges = () =>
changed
Expand All @@ -126,7 +130,10 @@

<form
class="fixed-fullsize-container"
on:submit|preventDefault={async () => save(editor)}
onsubmit={async (event) => {
event.preventDefault();
return save(editor);
}}
>
<EditorMenu {file_path} {editor}>
<SaveButton {changed} {saving} />
Expand Down
45 changes: 32 additions & 13 deletions frontend/src/reports/editor/EditorMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { toggleComment } from "@codemirror/commands";
import { foldAll, unfoldAll } from "@codemirror/language";
import type { EditorView } from "@codemirror/view";
import type { Snippet } from "svelte";
import { beancountFormat } from "../../codemirror/beancount-format";
import { scrollToLine } from "../../codemirror/editor-transactions";
Expand All @@ -15,19 +16,27 @@
import AppMenuSubItem from "./AppMenuSubItem.svelte";
import Key from "./Key.svelte";
export let file_path: string;
export let editor: EditorView;
interface Props {
file_path: string;
editor: EditorView;
children: Snippet;
}
let { file_path, editor, children }: Props = $props();
$: sources = [
let sources = $derived([
$options.filename,
...$options.include.filter((f) => f !== $options.filename),
];
$: insertEntryOptions = $fava_options.insert_entry;
]);
let insertEntryOptions = $derived($fava_options.insert_entry);
function goToFileAndLine(filename: string, line?: number) {
const url = urlFor("editor/", { file_path: filename, line });
router.navigate(url);
if (filename === file_path && line != null) {
// only load if the file changed.
const load = filename !== file_path;
router.navigate(url, load);
if (!load && line != null) {
// Scroll to line if we didn't change to a different file.
editor.dispatch(scrollToLine(editor.state, line));
editor.focus();
}
Expand All @@ -51,19 +60,27 @@
<AppMenuItem name={_("Edit")}>
<AppMenuSubItem action={() => beancountFormat(editor)}>
{_("Align Amounts")}
<Key slot="right" key={[modKey, "d"]} />
{#snippet right()}
<Key key={[modKey, "d"]} />
{/snippet}
</AppMenuSubItem>
<AppMenuSubItem action={() => toggleComment(editor)}>
{_("Toggle Comment (selection)")}
<Key slot="right" key={[modKey, "/"]} />
{#snippet right()}
<Key key={[modKey, "/"]} />
{/snippet}
</AppMenuSubItem>
<AppMenuSubItem action={() => unfoldAll(editor)}>
{_("Open all folds")}
<Key slot="right" key={["Ctrl", "Alt", "]"]} />
{#snippet right()}
<Key key={["Ctrl", "Alt", "]"]} />
{/snippet}
</AppMenuSubItem>
<AppMenuSubItem action={() => foldAll(editor)}>
{_("Close all folds")}
<Key slot="right" key={["Ctrl", "Alt", "["]} />
{#snippet right()}
<Key key={["Ctrl", "Alt", "["]} />
{/snippet}
</AppMenuSubItem>
</AppMenuItem>
{#if insertEntryOptions.length}
Expand All @@ -76,13 +93,15 @@
}}
>
{opt.re}
<span slot="right">{opt.date}</span>
{#snippet right()}
<span>{opt.date}</span>
{/snippet}
</AppMenuSubItem>
{/each}
</AppMenuItem>
{/if}
</AppMenu>
<slot />
{@render children()}
</div>

<style>
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/reports/editor/Key.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<script lang="ts">
export let key: string[];
interface Props {
key: string[];
}
let { key }: Props = $props();
</script>

{#each key as part, index}
Expand Down

0 comments on commit a07df82

Please sign in to comment.