Files
ComfyBox/src/lib/widgets/TextWidgetCodeVariant.svelte
2023-05-25 14:09:36 -05:00

245 lines
6.3 KiB
Svelte

<script lang="ts">
import type { ComfyTextNode } from "$lib/nodes/widgets";
import { type WidgetLayout } from "$lib/stores/layoutStates";
import { writable, type Writable } from "svelte/store";
import type { ViewUpdate } from "@codemirror/view";
import { EditorView, keymap, placeholder as placeholderExt } from "@codemirror/view";
import { StateEffect, EditorState, type Extension } from "@codemirror/state";
import { basicDark } from "cm6-theme-basic-dark";
import { basicLight } from "cm6-theme-basic-light";
import { basicSetup } from "./TextWidgetCodeVariant";
import { createEventDispatcher, onMount } from "svelte";
import { TAG_CATEGORY_COLORS } from "$lib/DanbooruTags";
import { Block, BlockTitle } from "@gradio/atoms";
export let widget: WidgetLayout;
export let node: ComfyTextNode;
export let nodeValue: Writable<string> = writable("");
export let extraExtensions: Extension[] = [];
let lines = 5;
let classNames = ""
let element: HTMLDivElement;
let view: EditorView;
const dispatch = createEventDispatcher<{ change: string }>();
$: lines = node?.properties?.lines || 5;
let BaseTheme: Extension = EditorView.theme({
"&": {
width: "100%",
maxWidth: "100%",
height: "12rem",
fontSize: "var(--text-sm)",
backgroundColor: "var(--input-background-fill)"
},
".cm-content": {
paddingTop: "5px",
paddingBottom: "5px",
color: "var(--body-text-color)",
fontFamily: "var(--font-mono)",
minHeight: "100%"
},
".cm-gutters": {
marginRight: "1px",
borderRight: "1px solid var(--border-color-primary)",
backgroundColor: "transparent",
color: "var(--body-text-color-subdued)"
},
".cm-focused": {
outline: "none"
},
".cm-scroller": {
height: "auto"
},
".cm-cursor": {
borderLeftColor: "var(--body-text-color)"
},
".cm-selectionBackground": {
backgroundColor: "var(--secondary-600) !important",
},
".cm-tooltip": {
backgroundColor: "var(--panel-background-fill) !important",
border: "1px solid var(--panel-border-color) !important",
},
".cm-tooltip-autocomplete": {
color: "var(--body-text-color) !important",
},
".cm-tooltip-autocomplete > ul > li[aria-selected]": {
color: "unset"
},
...TAG_CATEGORY_COLORS
})
onMount(() => {
view = createEditorView();
return () => view?.destroy();
});
$: reconfigure()
$: setDoc($nodeValue);
$: updateLines(lines);
function reconfigure(): void {
view?.dispatch({
effects: StateEffect.reconfigure.of(getExtensions())
});
}
function setDoc(newDoc: string) {
if (view && newDoc !== view.state.doc.toString()) {
view.dispatch({
changes: {
from: 0,
to: view.state.doc.length,
insert: newDoc
}
});
}
}
function updateLines(newLines: number) {
if (view) {
view.requestMeasure({ read: updateGutters });
}
}
function getGutterLineHeight(view: EditorView): string | null {
let elements = view.dom.querySelectorAll<HTMLElement>(".cm-gutterElement");
if (elements.length === 0) {
return null;
}
for (var i = 0; i < elements.length; i++) {
let node = elements[i];
let height = getComputedStyle(node)?.height ?? "0px";
if (height != "0px") {
return height;
}
}
return null;
}
function updateGutters(view: EditorView): any {
let gutters = view.dom.querySelectorAll<HTMLElement>(".cm-gutter");
let _lines = lines + 1;
let lineHeight = getGutterLineHeight(view);
if (!lineHeight) {
return null;
}
// for (var i = 0; i < gutters.length; i++) {
// let node = gutters[i];
// node.style.minHeight = `calc(${lineHeight} * ${_lines})`;
// }
return null;
}
function handleChange(vu: ViewUpdate): void {
if (vu.docChanged) {
const doc = vu.state.doc;
const text = doc.toString();
$nodeValue = text;
dispatch("change", text);
}
view.requestMeasure({ read: updateGutters });
}
function getBaseExtensions(readonly: boolean, placeholder: string | null): Extension[] {
const extensions: Extension[] = [
EditorView.editable.of(!readonly),
EditorState.readOnly.of(readonly)
];
extensions.push(basicSetup);
if (placeholder) {
extensions.push(placeholderExt(placeholder));
}
extensions.push(EditorView.updateListener.of(handleChange));
return extensions
}
function getTheme(dark_mode: boolean): Extension[] {
const extensions: Extension[] = [];
if (dark_mode) {
extensions.push(basicDark);
} else {
extensions.push(basicLight);
}
return extensions;
}
function getExtensions(): Extension[] {
// TODO
const readonly = false;
const placeholder = ""
const dark_mode = true;
const stateExtensions: Extension[] = [
...getBaseExtensions(
readonly,
placeholder,
),
BaseTheme,
...getTheme(dark_mode),
...extraExtensions
];
return stateExtensions;
}
function createEditorState(value: string | null | undefined): EditorState {
return EditorState.create({
doc: value ?? undefined,
extensions: getExtensions()
});
}
function createEditorView(): EditorView {
return new EditorView({
parent: element,
state: createEditorState($nodeValue)
});
}
</script>
<div class="code-editor-wrapper">
<Block>
<BlockTitle>{widget.attrs.title}</BlockTitle>
<div class="wrap">
<div class="codemirror-wrapper {classNames}" bind:this={element} />
</div>
</Block>
</div>
<style lang="scss">
.code-editor-wrapper {
:global(> .block) {
background: var(--panel-background-fill) !important;
}
}
.wrap {
display: flex;
flex-direction: column;
flex-flow: column;
margin: 0;
padding: 0;
height: 100%;
border: 1px solid var(--input-border-color);
.codemirror-wrapper {
height: 100%;
overflow: auto;
}
}
:global(.cm-scroller) {
height: 100% !important;
}
</style>