Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add autosave toggle to fallback Editor component #4830

Merged
merged 17 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web-common/src/components/editor/YAMLEditor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe("YAMLEditor.svelte", () => {
expect(getLines(container)).toHaveLength(1);

const onUpdate = vi.fn();
component?.$on("update", onUpdate);
component?.$on("save", onUpdate);

const content = "foo: 10\nbar: 20\nfoo: 10\nbar: 20";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import type { EditorView } from "@codemirror/view";
import { Meta, Story, Template } from "@storybook/addon-svelte-csf";
import Button from "../../button/Button.svelte";
import YAMLEditor from "../YAMLEditor.svelte";
import { setLineStatuses } from "../line-status";
import type { LineStatus } from "../line-status/state";
import YAMLEditor from "../YAMLEditor.svelte";

let content = `name: this is the name
values:
Expand Down Expand Up @@ -52,7 +52,7 @@ values:
key="key"
{content}
bind:view
on:update={(event) => {
on:save={(event) => {
// Often, you want to debounce the update to parent content.
// Here, we have no such requirement.
content = event.detail;
Expand Down
2 changes: 1 addition & 1 deletion web-common/src/components/editor/dispatch-events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function bindEditorEventsToDispatcher(
* The viewUpdate can be used to look at transactions at the parent component level.
*/
if (whenFocused && !viewUpdate.view.hasFocus) return;
dispatch("update", {
dispatch("save", {
content: viewUpdate.view.state.doc.toString(),
viewUpdate,
} as UpdateDetails);
Expand Down
2 changes: 1 addition & 1 deletion web-common/src/features/charts/editor/ChartsEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@
content={yaml}
extensions={[customYAMLwithJSONandSQL]}
key={filePath}
on:update={(e) => debounceUpdateChartContent(e.detail.content)}
on:save={(e) => debounceUpdateChartContent(e.detail.content)}
/>
</ChartsEditorContainer>
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import type { EditorView } from "@codemirror/view";
import YAMLEditor from "@rilldata/web-common/components/editor/YAMLEditor.svelte";
import { debounce } from "@rilldata/web-common/lib/create-debouncer";
import { V1ParseError } from "@rilldata/web-common/runtime-client";
import { createEventDispatcher } from "svelte";
import ChartsEditorContainer from "../charts/editor/ChartsEditorContainer.svelte";
import { V1ParseError } from "@rilldata/web-common/runtime-client";

const dispatch = createEventDispatcher();

Expand All @@ -30,6 +30,6 @@
content={yaml}
key={filePath}
whenFocused
on:update={(e) => debounceUpdateChartContent(e.detail.content)}
on:save={(e) => debounceUpdateChartContent(e.detail.content)}
/>
</ChartsEditorContainer>
128 changes: 110 additions & 18 deletions web-common/src/features/editor/Editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,32 @@
import type { Extension } from "@codemirror/state";
import { EditorState } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import Button from "@rilldata/web-common/components/button/Button.svelte";
import Label from "@rilldata/web-common/components/forms/Label.svelte";
import Switch from "@rilldata/web-common/components/forms/Switch.svelte";
import Check from "@rilldata/web-common/components/icons/Check.svelte";
import UndoIcon from "@rilldata/web-common/components/icons/UndoIcon.svelte";
import { createEventDispatcher, onMount } from "svelte";
import { bindEditorEventsToDispatcher } from "../../components/editor/dispatch-events";
import { base } from "../../components/editor/presets/base";
import { debounce } from "../../lib/create-debouncer";
import { FILE_SAVE_DEBOUNCE_TIME } from "./config";

const dispatch = createEventDispatcher();

export let blob: string;
export let latest: string;
export let extensions: Extension[] = [];
export let autoSave: boolean;
export let disableAutoSave: boolean;
export let hasUnsavedChanges: boolean;

let editor: EditorView;
let container: HTMLElement;

$: latest = blob;
$: updateEditorContents(latest);
$: if (editor) updateEditorExtensions(extensions);

onMount(() => {
editor = new EditorView({
Expand All @@ -34,6 +46,30 @@
});
});

function updateEditorExtensions(newExtensions: Extension[]) {
editor.setState(
EditorState.create({
doc: blob,
extensions: [
// establish a basic editor
base(),
// any extensions passed as props
...newExtensions,
EditorView.updateListener.of((v) => {
if (v.focusChanged && v.view.hasFocus) {
dispatch("receive-focus");
}
if (v.docChanged) {
latest = v.state.doc.toString();

if (!disableAutoSave && autoSave) debounceSave();
}
}),
],
}),
);
}

function updateEditorContents(newContent: string) {
if (editor && !editor.hasFocus) {
// NOTE: when changing files, we still want to update the editor
Expand All @@ -50,26 +86,82 @@
}
}

function updateEditorExtensions(newExtensions: Extension[]) {
editor.setState(
EditorState.create({
doc: blob,
extensions: [
// any extensions passed as props
...newExtensions,
// establish a basic editor
base(),
// this will catch certain events and dispatch them to the parent
bindEditorEventsToDispatcher(dispatch),
],
}),
);
function handleKeydown(e: KeyboardEvent) {
if (e.key === "s" && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
save();
}
}

// reactive statements to dynamically update the editor when inputs change
$: updateEditorContents(latest);
function save() {
dispatch("save");
}

$: if (editor) updateEditorExtensions(extensions);
const debounceSave = debounce(save, FILE_SAVE_DEBOUNCE_TIME);

function revertContent() {
dispatch("revert");
}
</script>

<div bind:this={container} class="contents" />
<svelte:window on:keydown={handleKeydown} />

<section>
<div class="editor-container">
<div
bind:this={container}
class="size-full"
on:click={() => {
/** give the editor focus no matter where we click */
if (!editor.hasFocus) editor.focus();
}}
on:keydown={() => {
/** no op for now */
}}
role="textbox"
tabindex="0"
/>
</div>

<footer>
<div class="flex gap-x-3">
{#if !autoSave || disableAutoSave}
<Button disabled={!hasUnsavedChanges} on:click={save}>
<Check size="14px" />
Save
</Button>

<Button
type="text"
disabled={!hasUnsavedChanges}
on:click={revertContent}
>
<UndoIcon size="14px" />
Revert changes
</Button>
{/if}
</div>
<div
class="flex gap-x-1 items-center h-full bg-white rounded-full"
class:hidden={disableAutoSave}
>
<Switch bind:checked={autoSave} id="auto-save" small />
<Label class="font-normal text-xs" for="auto-save">Auto-save</Label>
</div>
</footer>
</section>

<style lang="postcss">
.editor-container {
@apply size-full overflow-auto p-2 pb-0 flex flex-col;
}

footer {
@apply justify-between items-center flex flex-none;
@apply h-10 p-2 w-full rounded-b-sm border-t bg-white;
}

section {
@apply size-full flex-col rounded-sm bg-white flex overflow-hidden relative;
}
</style>
2 changes: 2 additions & 0 deletions web-common/src/features/editor/FileWorkspaceHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { PROTECTED_FILES } from "../file-explorer/protected-paths";

export let filePath: string;
export let hasUnsavedChanges: boolean;

let fileName: string;
let folder: string;
Expand Down Expand Up @@ -39,6 +40,7 @@
<WorkspaceHeader
editable={!isProtectedFile}
on:change={onChangeCallback}
{hasUnsavedChanges}
showInspectorToggle={false}
titleInput={fileName}
/>
3 changes: 3 additions & 0 deletions web-common/src/features/editor/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const FILES_WITHOUT_AUTOSAVE = ["/rill.yaml", "/.env"];

export const FILE_SAVE_DEBOUNCE_TIME = 400;
4 changes: 2 additions & 2 deletions web-common/src/features/editor/getExtensionsForFile.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { yaml } from "@rilldata/web-common/components/editor/presets/yaml";
import { markdown } from "@codemirror/lang-markdown";
import { yaml } from "@rilldata/web-common/components/editor/presets/yaml";
import { extractFileExtension } from "@rilldata/web-common/features/sources/extract-file-name";

export const FileExtensionToEditorExtension = {
Expand All @@ -8,7 +8,7 @@ export const FileExtensionToEditorExtension = {
".md": [markdown()],
};

export function getExtensionsForFiles(filePath: string) {
export function getExtensionsForFile(filePath: string) {
const extension = extractFileExtension(filePath);
return FileExtensionToEditorExtension[extension] ?? [];
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import type { EditorView } from "@codemirror/view";
import { debounce } from "@rilldata/web-common/lib/create-debouncer";
import { parse } from "yaml";
import YAMLEditor from "../../components/editor/YAMLEditor.svelte";
import {
Expand All @@ -8,7 +9,6 @@
} from "../../runtime-client";
import { runtime } from "../../runtime-client/runtime-store";
import ErrorPane from "./ErrorPane.svelte";
import { debounce } from "@rilldata/web-common/lib/create-debouncer";

export let filePath: string;

Expand Down Expand Up @@ -70,7 +70,7 @@
bind:view
{content}
key={filePath}
on:update={debouncedUpdate}
on:save={debouncedUpdate}
whenFocused
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@
content={yaml}
extensions={[placeholderElements.extension, yamlSchema(metricsJsonSchema)]}
whenFocused
on:update={updateMetrics}
on:save={updateMetrics}
/>
</MetricsEditorContainer>
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,25 @@
lineNumbers,
rectangularSelection,
} from "@codemirror/view";
import { createEventDispatcher, onMount } from "svelte";
import { DuckDBSQL } from "../../../components/editor/presets/duckDBDialect";
import { editorTheme } from "../../../components/editor/theme";
import { runtime } from "../../../runtime-client/runtime-store";
import { useAllSourceColumns } from "../../sources/selectors";
import { useAllModelColumns } from "../selectors";
import Button from "@rilldata/web-common/components/button/Button.svelte";
import Label from "@rilldata/web-common/components/forms/Label.svelte";
import Switch from "@rilldata/web-common/components/forms/Switch.svelte";
import Check from "@rilldata/web-common/components/icons/Check.svelte";
import UndoIcon from "@rilldata/web-common/components/icons/UndoIcon.svelte";
import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient";
import { createEventDispatcher, onMount } from "svelte";
import { DuckDBSQL } from "../../../components/editor/presets/duckDBDialect";
import { editorTheme } from "../../../components/editor/theme";
import { runtime } from "../../../runtime-client/runtime-store";
import { useAllSourceColumns } from "../../sources/selectors";
import { useAllModelColumns } from "../selectors";

const dispatch = createEventDispatcher();
const schema: { [table: string]: string[] } = {};

export let blob: string;
export let latest: string;
export let selections: SelectionRange[] = [];
export let focusOnMount = false;
export let autoSave = true;
export let hasUnsavedChanges: boolean;

Expand Down Expand Up @@ -230,7 +229,6 @@
}),
parent: editorContainerComponent,
});
if (focusOnMount) editor.focus();
});

// REACTIVE FUNCTIONS
Expand Down
8 changes: 4 additions & 4 deletions web-common/src/features/sources/editor/SourceEditor.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script lang="ts">
import type { EditorView } from "@codemirror/view";
import YAMLEditor from "@rilldata/web-common/components/editor/YAMLEditor.svelte";
import type { V1ParseError } from "@rilldata/web-common/runtime-client";
import { createEventDispatcher } from "svelte";
import { setLineStatuses } from "../../../components/editor/line-status";
import { mapParseErrorsToLines } from "../../metrics-views/errors";
import { createEventDispatcher } from "svelte";
import type { EditorView } from "@codemirror/view";
import type { V1ParseError } from "@rilldata/web-common/runtime-client";

const dispatch = createEventDispatcher();

Expand Down Expand Up @@ -46,7 +46,7 @@
<YAMLEditor
content={latest}
bind:view
on:update={handleUpdate}
on:save={handleUpdate}
key={filePath}
/>
</div>
Expand Down
5 changes: 2 additions & 3 deletions web-common/src/layout/workspace/workspace-stores.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { writable } from "svelte/store";
import { debounce } from "@rilldata/web-common/lib/create-debouncer";
import { derived, writable } from "svelte/store";
import {
DEFAULT_INSPECTOR_WIDTH,
DEFAULT_PREVIEW_TABLE_HEIGHT,
} from "../config";
import { derived } from "svelte/store";
import { debounce } from "@rilldata/web-common/lib/create-debouncer";

type WorkspaceLayout = {
inspector: {
Expand Down
Loading
Loading