Skip to content

Commit a07df82

Browse files
committed
editor report: migrate to Svelte 5 runes
1 parent ea970fa commit a07df82

File tree

6 files changed

+147
-86
lines changed

6 files changed

+147
-86
lines changed

frontend/src/reports/editor/AppMenu.svelte

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,18 @@
22
@component
33
An application menu.
44
-->
5+
<script lang="ts">
6+
import type { Snippet } from "svelte";
7+
8+
interface Props {
9+
children: Snippet;
10+
}
11+
12+
let { children }: Props = $props();
13+
</script>
514

615
<div role="menubar">
7-
<slot />
16+
{@render children()}
817
</div>
918

1019
<style>

frontend/src/reports/editor/AppMenuItem.svelte

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,37 @@
55
The default slot should filled with its vertically arranged sub-items.
66
-->
77
<script lang="ts">
8-
/** The name of the menu item. */
9-
export let name: string;
8+
import type { Snippet } from "svelte";
109
11-
let open = false;
10+
interface Props {
11+
/** The name of the menu item. */
12+
name: string;
13+
children: Snippet;
14+
}
15+
16+
let { name, children }: Props = $props();
17+
18+
let open = $state(false);
1219
</script>
1320

1421
<span
1522
class:open
1623
tabindex="0"
1724
role="menuitem"
18-
on:keydown={(ev) => {
19-
if (ev.key === "ArrowDown") {
25+
onblur={() => {
26+
open = false;
27+
}}
28+
onkeydown={(event) => {
29+
if (event.key === "Escape") {
30+
open = false;
31+
} else if (event.key === "ArrowDown") {
2032
open = true;
2133
}
2234
}}
2335
>
2436
{name}
25-
<ul>
26-
<slot />
37+
<ul role="menu">
38+
{@render children()}
2739
</ul>
2840
</span>
2941

frontend/src/reports/editor/AppMenuSubItem.svelte

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,39 @@
33
A sub-item in an app menu.
44
-->
55
<script lang="ts">
6-
/** A title (optional) for the element. */
7-
export let title: string | undefined = undefined;
8-
/** Whether this menu item should be marked as selected. */
9-
export let selected = false;
10-
/** The action to execute on click. */
11-
export let action: () => void;
6+
import type { Snippet } from "svelte";
7+
8+
interface Props {
9+
/** A title (optional) for the element. */
10+
title?: string;
11+
/** Whether this menu item should be marked as selected. */
12+
selected?: boolean;
13+
/** The action to execute on click. */
14+
action: () => void;
15+
children: Snippet;
16+
right?: Snippet;
17+
}
18+
19+
let { title, selected = false, action, children, right }: Props = $props();
1220
</script>
1321

14-
<li class:selected {title}>
15-
<button type="button" on:click={action}>
16-
<slot />
17-
{#if $$slots.right}
18-
<span>
19-
<slot name="right" />
20-
</span>
21-
{/if}
22-
</button>
22+
<li
23+
class:selected
24+
{title}
25+
role="menuitem"
26+
onclick={action}
27+
onkeydown={(event) => {
28+
if (event.key === "Enter") {
29+
action();
30+
}
31+
}}
32+
>
33+
{@render children()}
34+
{#if right}
35+
<span>
36+
{@render right()}
37+
</span>
38+
{/if}
2339
</li>
2440

2541
<style>
@@ -29,18 +45,12 @@
2945
3046
li {
3147
padding: 0.25em 0.5em;
32-
cursor: pointer;
3348
}
3449
3550
span {
3651
float: right;
3752
}
3853
39-
button {
40-
display: contents;
41-
color: inherit;
42-
}
43-
4454
li:hover,
4555
li:focus-visible {
4656
background-color: var(--background-darker);

frontend/src/reports/editor/Editor.svelte

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
22
import type { LanguageSupport } from "@codemirror/language";
33
import type { EditorView } from "@codemirror/view";
4-
import { onMount } from "svelte";
4+
import { onMount, untrack } from "svelte";
55
66
import { get, put } from "../../api";
77
import type { SourceFile } from "../../api/validators";
@@ -19,18 +19,22 @@
1919
import { searchParams } from "../../stores/url";
2020
import EditorMenu from "./EditorMenu.svelte";
2121
22-
export let source: SourceFile;
23-
export let beancount_language_support: LanguageSupport;
22+
interface Props {
23+
source: SourceFile;
24+
beancount_language_support: LanguageSupport;
25+
}
26+
27+
let { source, beancount_language_support }: Props = $props();
2428
25-
$: file_path = source.file_path;
29+
let file_path = $derived(source.file_path);
2630
27-
let changed = false;
31+
let changed = $state(false);
2832
const onDocChanges = () => {
2933
changed = true;
3034
};
3135
32-
let sha256sum = "";
33-
let saving = false;
36+
let sha256sum = $state("");
37+
let saving = $state(false);
3438
3539
/**
3640
* Save the contents of the editor.
@@ -73,48 +77,48 @@
7377
beancount_language_support,
7478
);
7579
76-
// update editor contents if source changes
77-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/no-unnecessary-condition
78-
$: if (source) {
79-
editor.dispatch(replaceContents(editor.state, source.source));
80-
sha256sum = source.sha256sum;
81-
editor.focus();
82-
changed = false;
83-
}
84-
85-
// wrap this in a function to not trigger the reactive block below
86-
// on store updates.
87-
function jumpToInsertOption() {
88-
const opts = $fava_options.insert_entry.filter(
89-
(f) => f.filename === file_path,
90-
);
91-
const line = parseInt($searchParams.get("line") ?? "0", 10);
92-
const last_insert_opt = opts[opts.length - 1];
93-
const lineToScrollTo = (() => {
80+
$effect(() => {
81+
// update editor contents if source changes
82+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
83+
source;
84+
untrack(() => {
85+
editor.dispatch(replaceContents(editor.state, source.source));
86+
sha256sum = source.sha256sum;
87+
editor.focus();
88+
changed = false;
89+
});
90+
});
91+
92+
$effect(() => {
93+
// Go to line if the edited file changes.
94+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
95+
file_path;
96+
untrack(() => {
97+
const opts = $fava_options.insert_entry.filter(
98+
(f) => f.filename === file_path,
99+
);
100+
const last_insert_opt = opts[opts.length - 1];
101+
const line = parseInt($searchParams.get("line") ?? "0", 10);
102+
let line_to_scroll_to = null;
94103
if (line > 0) {
95-
return line;
104+
line_to_scroll_to = line;
105+
} else if (last_insert_opt) {
106+
line_to_scroll_to = last_insert_opt.lineno - 1;
96107
}
97-
if (last_insert_opt) {
98-
return last_insert_opt.lineno - 1;
99-
}
100-
return editor.state.doc.lines;
101-
})();
102-
editor.dispatch(scrollToLine(editor.state, lineToScrollTo));
103-
}
104-
105-
// Go to line if the edited file changes.
106-
$: if (file_path) {
107-
jumpToInsertOption();
108-
}
109-
110-
// Update diagnostics, showing errors in the editor
111-
$: {
108+
editor.dispatch(
109+
scrollToLine(editor.state, line_to_scroll_to ?? editor.state.doc.lines),
110+
);
111+
});
112+
});
113+
114+
$effect(() => {
115+
// Update diagnostics, showing errors in the editor
112116
// Only show errors for this file, or general errors (AKA no source)
113117
const errorsForFile = $errors.filter(
114118
(err) => err.source === null || err.source.filename === file_path,
115119
);
116120
editor.dispatch(setErrors(editor.state, errorsForFile));
117-
}
121+
});
118122
119123
const checkEditorChanges = () =>
120124
changed
@@ -126,7 +130,10 @@
126130

127131
<form
128132
class="fixed-fullsize-container"
129-
on:submit|preventDefault={async () => save(editor)}
133+
onsubmit={async (event) => {
134+
event.preventDefault();
135+
return save(editor);
136+
}}
130137
>
131138
<EditorMenu {file_path} {editor}>
132139
<SaveButton {changed} {saving} />

frontend/src/reports/editor/EditorMenu.svelte

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { toggleComment } from "@codemirror/commands";
33
import { foldAll, unfoldAll } from "@codemirror/language";
44
import type { EditorView } from "@codemirror/view";
5+
import type { Snippet } from "svelte";
56
67
import { beancountFormat } from "../../codemirror/beancount-format";
78
import { scrollToLine } from "../../codemirror/editor-transactions";
@@ -15,19 +16,27 @@
1516
import AppMenuSubItem from "./AppMenuSubItem.svelte";
1617
import Key from "./Key.svelte";
1718
18-
export let file_path: string;
19-
export let editor: EditorView;
19+
interface Props {
20+
file_path: string;
21+
editor: EditorView;
22+
children: Snippet;
23+
}
24+
25+
let { file_path, editor, children }: Props = $props();
2026
21-
$: sources = [
27+
let sources = $derived([
2228
$options.filename,
2329
...$options.include.filter((f) => f !== $options.filename),
24-
];
25-
$: insertEntryOptions = $fava_options.insert_entry;
30+
]);
31+
let insertEntryOptions = $derived($fava_options.insert_entry);
2632
2733
function goToFileAndLine(filename: string, line?: number) {
2834
const url = urlFor("editor/", { file_path: filename, line });
29-
router.navigate(url);
30-
if (filename === file_path && line != null) {
35+
// only load if the file changed.
36+
const load = filename !== file_path;
37+
router.navigate(url, load);
38+
if (!load && line != null) {
39+
// Scroll to line if we didn't change to a different file.
3140
editor.dispatch(scrollToLine(editor.state, line));
3241
editor.focus();
3342
}
@@ -51,19 +60,27 @@
5160
<AppMenuItem name={_("Edit")}>
5261
<AppMenuSubItem action={() => beancountFormat(editor)}>
5362
{_("Align Amounts")}
54-
<Key slot="right" key={[modKey, "d"]} />
63+
{#snippet right()}
64+
<Key key={[modKey, "d"]} />
65+
{/snippet}
5566
</AppMenuSubItem>
5667
<AppMenuSubItem action={() => toggleComment(editor)}>
5768
{_("Toggle Comment (selection)")}
58-
<Key slot="right" key={[modKey, "/"]} />
69+
{#snippet right()}
70+
<Key key={[modKey, "/"]} />
71+
{/snippet}
5972
</AppMenuSubItem>
6073
<AppMenuSubItem action={() => unfoldAll(editor)}>
6174
{_("Open all folds")}
62-
<Key slot="right" key={["Ctrl", "Alt", "]"]} />
75+
{#snippet right()}
76+
<Key key={["Ctrl", "Alt", "]"]} />
77+
{/snippet}
6378
</AppMenuSubItem>
6479
<AppMenuSubItem action={() => foldAll(editor)}>
6580
{_("Close all folds")}
66-
<Key slot="right" key={["Ctrl", "Alt", "["]} />
81+
{#snippet right()}
82+
<Key key={["Ctrl", "Alt", "["]} />
83+
{/snippet}
6784
</AppMenuSubItem>
6885
</AppMenuItem>
6986
{#if insertEntryOptions.length}
@@ -76,13 +93,15 @@
7693
}}
7794
>
7895
{opt.re}
79-
<span slot="right">{opt.date}</span>
96+
{#snippet right()}
97+
<span>{opt.date}</span>
98+
{/snippet}
8099
</AppMenuSubItem>
81100
{/each}
82101
</AppMenuItem>
83102
{/if}
84103
</AppMenu>
85-
<slot />
104+
{@render children()}
86105
</div>
87106

88107
<style>

frontend/src/reports/editor/Key.svelte

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<script lang="ts">
2-
export let key: string[];
2+
interface Props {
3+
key: string[];
4+
}
5+
6+
let { key }: Props = $props();
37
</script>
48

59
{#each key as part, index}

0 commit comments

Comments
 (0)