Merge pull request #95 from space-nuko/frontend-note

Frontend Markdown note
This commit is contained in:
space-nuko
2023-05-31 10:51:44 -05:00
committed by GitHub
11 changed files with 400 additions and 24 deletions

View File

@@ -50,6 +50,7 @@
"@codemirror/search": "^6.2.2",
"@codemirror/state": "^6.1.2",
"@codemirror/view": "^6.4.1",
"@dogagenc/svelte-markdown": "^0.2.4",
"@gradio/accordion": "workspace:*",
"@gradio/atoms": "workspace:*",
"@gradio/button": "workspace:*",
@@ -85,6 +86,7 @@
"framework7": "^8.0.3",
"framework7-svelte": "^8.0.3",
"img-comparison-slider": "^8.0.0",
"marked": "^5.0.3",
"pollen-css": "^4.6.2",
"radix-icons-svelte": "^1.2.1",
"style-mod": "^4.0.3",

35
pnpm-lock.yaml generated
View File

@@ -25,6 +25,9 @@ importers:
'@codemirror/view':
specifier: ^6.4.1
version: 6.11.0
'@dogagenc/svelte-markdown':
specifier: ^0.2.4
version: 0.2.4(svelte@3.58.0)
'@gradio/accordion':
specifier: workspace:*
version: link:gradio/js/accordion
@@ -130,6 +133,9 @@ importers:
img-comparison-slider:
specifier: ^8.0.0
version: 8.0.0
marked:
specifier: ^5.0.3
version: 5.0.3
pollen-css:
specifier: ^4.6.2
version: 4.6.2
@@ -1357,7 +1363,6 @@ packages:
'@codemirror/language': ^6.0.0
'@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0
'@lezer/common': ^1.0.0
dependencies:
'@codemirror/language': 6.6.0
'@codemirror/state': 6.2.0
@@ -1501,6 +1506,16 @@ packages:
w3c-keyname: 2.2.6
dev: false
/@dogagenc/svelte-markdown@0.2.4(svelte@3.58.0):
resolution: {integrity: sha512-UmmHHZ7rilAbBYiNsxuL5d8Ac79EhFXrhjsUNr30BPzn+T7ohJR8kHMFjDYDQc0tOQOfKbICvkPAQ6cprqS3Eg==}
peerDependencies:
svelte: ^3.0.0
dependencies:
'@types/marked': 4.3.1
marked: 4.3.0
svelte: 3.58.0
dev: false
/@esbuild/android-arm64@0.17.18:
resolution: {integrity: sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==}
engines: {node: '>=12'}
@@ -2423,6 +2438,10 @@ packages:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true
/@types/marked@4.3.1:
resolution: {integrity: sha512-vSSbKZFbNktrQ15v7o1EaH78EbWV+sPQbPjHG+Cp8CaNcPFUEfjZ0Iml/V0bFDwsTlYe8o6XC5Hfdp91cqPV2g==}
dev: false
/@types/node@10.17.60:
resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==}
dev: false
@@ -3098,8 +3117,6 @@ packages:
'@codemirror/search': 6.4.0
'@codemirror/state': 6.2.0
'@codemirror/view': 6.11.0
transitivePeerDependencies:
- '@lezer/common'
dev: false
/codemirror@6.0.1(@lezer/common@1.0.2):
@@ -5481,6 +5498,18 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/marked@4.3.0:
resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==}
engines: {node: '>= 12'}
hasBin: true
dev: false
/marked@5.0.3:
resolution: {integrity: sha512-KUONa43Uk74uUNWMxh6lfaNYmSAsRMiDAaX8QBCCRVXzEufR0zX6T33vrGbvTnQLL02ungDM3KSzZtO+chJaHg==}
engines: {node: '>= 18'}
hasBin: true
dev: false
/md5-hex@3.0.1:
resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==}
engines: {node: '>=8'}

View File

@@ -18,6 +18,7 @@
export let elem_classes: string[] = []
export let style: string = ""
export let label: string = ""
export let mask: ComfyImageLocation | null;
// let propsChanged: Writable<number> | null = null;
let dragging = false;
let pending_upload = false;
@@ -172,6 +173,15 @@
bind:naturalWidth={imgWidth}
bind:naturalHeight={imgHeight}
/>
{#key mask}
{#if mask}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<img src={convertComfyOutputToComfyURL(mask)}
alt={firstImage.filename}
on:click={onImgClicked}
/>
{/if}
{/key}
{:else}
<Upload
file_count={fileCount}
@@ -201,6 +211,9 @@
}
img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
max-width: 100%;

View File

@@ -68,7 +68,7 @@ function getConnectionPos(node: SerializedLGraphNode, is_input: boolean, slotNum
return out;
}
function createSerializedWidgetNode(vanillaWorkflow: ComfyVanillaWorkflow, node: SerializedLGraphNode, slotIndex: number, isInput: boolean, widgetNodeType: string, value: any): [ComfyWidgetNode, SerializedComfyWidgetNode] {
function createSerializedWidgetNode(vanillaWorkflow: ComfyVanillaWorkflow, widgetNodeType: string, value: any, node?: SerializedLGraphNode, slotIndex?: number, isInput?: boolean): [ComfyWidgetNode, SerializedComfyWidgetNode] {
const comfyWidgetNode = LiteGraph.createNode<ComfyWidgetNode>(widgetNodeType);
comfyWidgetNode.flags.collapsed = true;
const size: Vector2 = [0, 0];
@@ -85,12 +85,15 @@ function createSerializedWidgetNode(vanillaWorkflow: ComfyVanillaWorkflow, node:
const serWidgetNode = comfyWidgetNode.serialize() as SerializedComfyWidgetNode;
serWidgetNode.comfyValue = value;
serWidgetNode.shownOutputProperties = {};
if (node != null) {
getConnectionPos(node, isInput, slotIndex, serWidgetNode.pos);
if (isInput)
serWidgetNode.pos[0] -= size[0] - 20;
else
serWidgetNode.pos[0] += 20;
serWidgetNode.pos[1] += LiteGraph.NODE_TITLE_HEIGHT / 2;
}
if (widgetNodeType === "ui/text" && typeof value === "string" && value.indexOf("\n") != -1) {
const lineCount = countNewLines(value);
@@ -260,11 +263,12 @@ function convertPrimitiveNode(vanillaWorkflow: ComfyVanillaWorkflow, node: Seria
const [comfyWidgetNode, serWidgetNode] = createSerializedWidgetNode(
vanillaWorkflow,
widgetNodeType,
value,
node,
0, // first output on the PrimitiveNode
false, // this is an output slot index
widgetNodeType,
value);
false // this is an output slot index
);
// Set the UI node's min/max/step from the node def
configureWidgetNodeProperties(serWidgetNode, widgetOpts)
@@ -381,6 +385,20 @@ export default function convertVanillaWorkflow(vanillaWorkflow: ComfyVanillaWork
removeSerializedNode(vanillaWorkflow, node);
continue
}
else if (node.type === "Note") {
const [comfyWidgetNode, serWidgetNode] = createSerializedWidgetNode(
vanillaWorkflow,
"ui/markdown",
node.widgets_values[0]
);
serWidgetNode.pos = [node.pos[0], node.pos[1]]
const group = layoutState.addContainer(left, { title: "" })
layoutState.addWidget(group, comfyWidgetNode)
removeSerializedNode(vanillaWorkflow, node);
continue
}
const def = ComfyApp.knownBackendNodes[node.type];
if (def == null) {
@@ -449,11 +467,12 @@ export default function convertVanillaWorkflow(vanillaWorkflow: ComfyVanillaWork
const [comfyWidgetNode, serWidgetNode] = createSerializedWidgetNode(
vanillaWorkflow,
widgetNodeType,
value,
node,
connInputIndex,
true,
widgetNodeType,
value);
true
);
configureWidgetNodeProperties(serWidgetNode, inputOpts)
@@ -492,11 +511,12 @@ export default function convertVanillaWorkflow(vanillaWorkflow: ComfyVanillaWork
// Let's create a gallery for this output node and hook it up
const [comfyGalleryNode, serGalleryNode] = createSerializedWidgetNode(
vanillaWorkflow,
"ui/gallery",
[],
node,
connOutputIndex,
false,
"ui/gallery",
[]);
);
if (group == null)
group = layoutState.addContainer(isOutputNode ? right : left, { title: node.title || node.type })

View File

@@ -0,0 +1,57 @@
import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout } from "@litegraph-ts/core";
import MarkdownWidget from "$lib/widgets/MarkdownWidget.svelte";
import ComfyWidgetNode, { type ComfyWidgetProperties } from "./ComfyWidgetNode";
export interface ComfyMarkdownProperties extends ComfyWidgetProperties {
}
export default class ComfyMarkdownNode extends ComfyWidgetNode<string> {
override properties: ComfyMarkdownProperties = {
tags: [],
defaultValue: false,
}
static slotLayout: SlotLayout = {
inputs: [
{ name: "store", type: BuiltInSlotType.ACTION }
],
outputs: [
{ name: "value", type: "string" },
{ name: "changed", type: BuiltInSlotType.EVENT },
]
}
override svelteComponentType = MarkdownWidget;
override defaultValue = "";
constructor(name?: string) {
super(name, "")
}
override createDisplayWidget(): ITextWidget {
const widget = this.addWidget<ITextWidget>(
"text",
"Value",
"",
(v: string) => {
if (v == null || v === this.getValue()) {
return;
}
this.setValue(v);
},
{
multiline: true,
inputStyle: { fontFamily: "monospace" }
}
)
return widget;
}
}
LiteGraph.registerNodeType({
class: ComfyMarkdownNode,
title: "UI.Markdown",
desc: "Displays Markdown in the UI",
type: "ui/markdown"
})

View File

@@ -106,13 +106,18 @@ export default abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
this.value = writable(value)
this.color ||= color.color
this.bgColor ||= color.bgColor
this.displayWidget = this.addWidget<ITextWidget>(
this.displayWidget = this.createDisplayWidget();
this.unsubscribe = this.value.subscribe(this.onValueUpdated.bind(this))
}
protected createDisplayWidget(): ITextWidget {
const widget = this.addWidget<ITextWidget>(
"text",
"Value",
""
);
this.displayWidget.disabled = true; // prevent editing
this.unsubscribe = this.value.subscribe(this.onValueUpdated.bind(this))
)
widget.disabled = true; // prevent editing
return widget;
}
addPropertyAsOutput(propertyName: string, type: string) {

View File

@@ -9,3 +9,4 @@ export { default as ComfyRadioNode } from "./ComfyRadioNode"
export { default as ComfyNumberNode } from "./ComfyNumberNode"
export { default as ComfyTextNode } from "./ComfyTextNode"
export { default as ComfyMultiRegionNode } from "./ComfyMultiRegionNode"
export { default as ComfyMarkdownNode } from "./ComfyMarkdownNode"

View File

@@ -52,12 +52,19 @@
};
let hasImage = false;
$: hasImage = $nodeValue && $nodeValue.length > 0;
$: if (!hasImage) {
editMask = false;
}
let mask: ComfyImageLocation | null;
$: if (hasImage && canMask) {
mask = $nodeValue[0].children?.find(i => i.tags.includes("mask"))?.comfyUIFile;
}
else {
mask = null;
}
const MASK_FILENAME: string = "ComfyBoxMask.png"
async function onMaskReleased(e: CustomEvent<MaskCanvasData>) {
@@ -122,6 +129,7 @@
// TODO other child image types preserved here?
image.children = [];
}
mask = null;
if (maskCanvasComp) {
maskCanvasComp.clearStrokes();
}
@@ -232,6 +240,7 @@
<ImageUpload value={_value}
bind:imgWidth={$imgWidth}
bind:imgHeight={$imgHeight}
{mask}
fileCount={"single"}
elem_classes={[]}
style={""}

View File

@@ -0,0 +1,233 @@
<script lang="ts">
import { type WidgetLayout } from "$lib/stores/layoutStates";
import { get, type Writable, writable } from "svelte/store";
import { Block } from "@gradio/atoms";
import type { ComfyMarkdownNode } from "$lib/nodes/widgets";
import SvelteMarkdown from "@dogagenc/svelte-markdown"
import NullMarkdownRenderer from "./markdown/NullMarkdownRenderer.svelte"
import { SvelteComponentDev } from "svelte/internal";
export let widget: WidgetLayout | null = null;
export let isMobile: boolean = false;
let node: ComfyMarkdownNode | null = null;
let nodeValue: Writable<string> = writable("");
let attrsChanged: Writable<number> = writable(0);
let renderers: Record<string, typeof SvelteComponentDev> = {
"html": NullMarkdownRenderer
}
$: widget && setNodeValue(widget);
function setNodeValue(widget: WidgetLayout) {
if (widget) {
node = widget.node as ComfyMarkdownNode
nodeValue = node.value;
attrsChanged = widget.attrsChanged;
}
};
</script>
<div class="wrapper prose">
{#key $attrsChanged}
{#if widget !== null && node !== null}
<Block>
<SvelteMarkdown source={$nodeValue} {renderers} />
</Block>
{/if}
{/key}
</div>
<style lang="scss">
.wrapper {
padding: 2px;
width: 100%;
height: 100%;
:global(> button) {
height: 100%;
}
:global(> .block) {
border-radius: 0 !important;
}
}
.prose {
font-weight: var(--prose-text-weight);
font-size: var(--text-md);
}
.prose * {
color: var(--body-text-color);
}
.prose p {
margin-bottom: var(--spacing-sm);
line-height: var(--line-lg);
}
/* headings
*/
.prose h1,
.prose h2,
.prose h3,
.prose h4,
.prose h5 {
margin: var(--spacing-xxl) 0 var(--spacing-lg);
font-weight: var(--prose-header-text-weight);
line-height: 1.3;
}
.prose > *:first-child {
margin-top: 0;
}
.prose h1 {
margin-top: 0;
font-size: var(--text-xxl);
}
.prose h2 {
font-size: var(--text-xl);
}
.prose h3 {
font-size: var(--text-lg);
}
.prose h4 {
font-size: 1.1em;
}
.prose h5 {
font-size: 1.05em;
}
/* lists
*/
.prose ul {
list-style: circle inside;
}
.prose ol {
list-style: decimal inside;
}
.prose ul > p,
.prose li > p {
display: inline-block;
}
.prose ol,
.prose ul {
margin-top: 0;
padding-left: 0;
}
.prose ul ul,
.prose ul ol,
.prose ol ol,
.prose ol ul {
margin: 0.5em 0 0.5em 3em;
font-size: 90%;
}
.prose li {
margin-bottom: 0.5em;
}
/* code
*/
.prose code {
border: 1px solid var(--border-color-primary);
border-radius: var(--radius-sm);
background: var(--background-fill-secondary);
padding: 1px 3px;
font-size: 85%;
white-space: nowrap;
}
.prose pre > code {
display: block;
padding: 0.5em 0.7em;
/* font-size: 100%; */
white-space: pre;
}
/* tables
*/
.prose th,
.prose td {
border-bottom: 1px solid #e1e1e1;
padding: 12px 15px;
text-align: left;
}
.prose th:first-child,
.prose td:first-child {
padding-left: 0;
}
.prose th:last-child,
.prose td:last-child {
padding-right: 0;
}
/* spacing
*/
.prose button,
.prose .button {
margin-bottom: var(--spacing-sm);
}
.prose input,
.prose textarea,
.prose select,
.prose fieldset {
margin-bottom: var(--spacing-sm);
}
.prose pre,
.prose blockquote,
.prose dl,
.prose figure,
.prose table,
.prose p,
.prose ul,
.prose ol,
.prose form {
margin-bottom: var(--spacing-md);
}
/* links
*/
.prose a {
color: var(--link-text-color);
text-decoration: underline;
}
.prose a:visited {
color: var(--link-text-color-visited);
}
.prose a:hover {
color: var(--link-text-color-hover);
}
.prose a:active {
color: var(--link-text-color-active);
}
/* misc
*/
.prose hr {
margin-top: 3em;
margin-bottom: 3.5em;
border-width: 0;
border-top: 1px solid #e1e1e1;
}
.prose blockquote {
margin: var(--size-6) 0 !important;
border-left: 5px solid var(--border-color-primary);
padding-left: var(--size-2);
}
.prose :last-child {
margin-bottom: 0 !important;
}
</style>

View File

@@ -0,0 +1,7 @@
<script>
export let href = "";
export let title = undefined;
export let text = "";
</script>
<div/>