Context menu thing
This commit is contained in:
@@ -177,7 +177,6 @@
|
|||||||
<select id="enable-ui-editing" name="enable-ui-editing" bind:value={$uiState.uiEditMode}>
|
<select id="enable-ui-editing" name="enable-ui-editing" bind:value={$uiState.uiEditMode}>
|
||||||
<option value="disabled">Disabled</option>
|
<option value="disabled">Disabled</option>
|
||||||
<option value="widgets">Widgets</option>
|
<option value="widgets">Widgets</option>
|
||||||
<option value="containers">Containers</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<LightboxModal />
|
<LightboxModal />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { tick } from 'svelte'
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { LGraphNode, LGraph } from "@litegraph-ts/core";
|
import { LGraphNode, LGraph } from "@litegraph-ts/core";
|
||||||
import type { IWidget } from "@litegraph-ts/core";
|
import type { IWidget } from "@litegraph-ts/core";
|
||||||
@@ -7,6 +8,12 @@
|
|||||||
import WidgetContainer from "./WidgetContainer.svelte";
|
import WidgetContainer from "./WidgetContainer.svelte";
|
||||||
import nodeState from "$lib/stores/nodeState";
|
import nodeState from "$lib/stores/nodeState";
|
||||||
import layoutState, { type ContainerLayout, type DragItem } from "$lib/stores/layoutState";
|
import layoutState, { type ContainerLayout, type DragItem } from "$lib/stores/layoutState";
|
||||||
|
import uiState from "$lib/stores/uiState";
|
||||||
|
|
||||||
|
import Menu from './menu/Menu.svelte';
|
||||||
|
import MenuOption from './menu/MenuOption.svelte';
|
||||||
|
import MenuDivider from './menu/MenuDivider.svelte';
|
||||||
|
import Icon from './menu/Icon.svelte'
|
||||||
|
|
||||||
export let app: ComfyApp;
|
export let app: ComfyApp;
|
||||||
let dragConfigured: boolean = false;
|
let dragConfigured: boolean = false;
|
||||||
@@ -39,18 +46,60 @@
|
|||||||
}
|
}
|
||||||
console.warn($layoutState)
|
console.warn($layoutState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let menuPos = { x: 0, y: 0 };
|
||||||
|
let showMenu = false;
|
||||||
|
|
||||||
|
$: $layoutState.isMenuOpen = showMenu;
|
||||||
|
|
||||||
|
async function onRightClick(e) {
|
||||||
|
if ($uiState.uiEditMode === "disabled")
|
||||||
|
return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
if (showMenu) {
|
||||||
|
showMenu = false;
|
||||||
|
await new Promise(res => setTimeout(res, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
menuPos = { x: e.clientX, y: e.clientY };
|
||||||
|
showMenu = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMenu() {
|
||||||
|
showMenu = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="comfy-ui-panes" >
|
<div id="comfy-ui-panes" on:contextmenu={onRightClick}>
|
||||||
<WidgetContainer bind:dragItem={$layoutState.root} classes={["root-container"]} />
|
<WidgetContainer bind:dragItem={$layoutState.root} classes={["root-container"]} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if showMenu}
|
||||||
|
<Menu {...menuPos} on:click={closeMenu} on:clickoutside={closeMenu}>
|
||||||
|
<MenuOption
|
||||||
|
on:click={console.log}
|
||||||
|
text="Do nothing" />
|
||||||
|
<MenuOption
|
||||||
|
on:click={console.log}
|
||||||
|
text="Do nothing, but twice" />
|
||||||
|
<MenuDivider />
|
||||||
|
<MenuOption
|
||||||
|
isDisabled={true}
|
||||||
|
on:click={console.log}
|
||||||
|
text="Whoops, disabled!" />
|
||||||
|
<MenuOption on:click={console.log}>
|
||||||
|
<Icon />
|
||||||
|
<span>Look! An icon!</span>
|
||||||
|
</MenuOption>
|
||||||
|
</Menu>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#comfy-ui-panes {
|
#comfy-ui-panes {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: scroll;
|
overflow: auto;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
let widget: WidgetLayout | null = null;
|
let widget: WidgetLayout | null = null;
|
||||||
let widgetState: WidgetUIState | null = null;
|
let widgetState: WidgetUIState | null = null;
|
||||||
let children: IDragItem[] | null = null;
|
let children: IDragItem[] | null = null;
|
||||||
|
let showHandles: boolean = false;
|
||||||
const flipDurationMs = 100;
|
const flipDurationMs = 100;
|
||||||
|
|
||||||
$: if (dragItem) {
|
$: if (dragItem) {
|
||||||
@@ -38,6 +39,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: showHandles = $uiState.uiEditMode === "widgets" // TODO
|
||||||
|
&& zIndex > 1
|
||||||
|
&& !$layoutState.isMenuOpen
|
||||||
|
|
||||||
function handleConsider(evt: any) {
|
function handleConsider(evt: any) {
|
||||||
children = layoutState.updateChildren(dragItem, evt.detail.items)
|
children = layoutState.updateChildren(dragItem, evt.detail.items)
|
||||||
// console.log(dragItems);
|
// console.log(dragItems);
|
||||||
@@ -49,6 +54,9 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const startDrag = (evt: MouseEvent) => {
|
const startDrag = (evt: MouseEvent) => {
|
||||||
|
if (evt.button === 2)
|
||||||
|
return;
|
||||||
|
|
||||||
const dragItemId: string = evt.target.dataset["dragItemId"];
|
const dragItemId: string = evt.target.dataset["dragItemId"];
|
||||||
const item = $layoutState.allItems[dragItemId].dragItem
|
const item = $layoutState.allItems[dragItemId].dragItem
|
||||||
if (evt.ctrlKey) {
|
if (evt.ctrlKey) {
|
||||||
@@ -79,12 +87,12 @@
|
|||||||
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')}"
|
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')}"
|
||||||
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(container.id)}
|
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(container.id)}
|
||||||
class:root-container={isRoot}
|
class:root-container={isRoot}
|
||||||
class:container-edit-outline={$uiState.uiEditMode === "containers" && zIndex > 1}>
|
class:container-edit-outline={$uiState.uiEditMode === "widgets" && zIndex > 1}>
|
||||||
<Block>
|
<Block>
|
||||||
{#if container.attrs.showTitle}
|
{#if container.attrs.showTitle}
|
||||||
<label for={String(id)} class={$uiState.uiEditMode === "containers" ? "edit-title-label" : ""}>
|
<label for={String(id)} class={$uiState.uiEditMode === "widgets" ? "edit-title-label" : ""}>
|
||||||
<BlockTitle>
|
<BlockTitle>
|
||||||
{#if $uiState.uiEditMode === "containers"}
|
{#if $uiState.uiEditMode === "widgets"}
|
||||||
<input class="edit-title" bind:value={container.attrs.title} type="text" minlength="1" />
|
<input class="edit-title" bind:value={container.attrs.title} type="text" minlength="1" />
|
||||||
{:else}
|
{:else}
|
||||||
{container.attrs.title}
|
{container.attrs.title}
|
||||||
@@ -94,7 +102,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="v-pane"
|
<div class="v-pane"
|
||||||
class:empty={children.length === 0}
|
class:empty={children.length === 0}
|
||||||
class:edit={$uiState.uiEditMode === "containers" && zIndex > 1}
|
class:edit={$uiState.uiEditMode === "widgets" && zIndex > 1}
|
||||||
class:is-executing={$queueState.runningNodeId && $queueState.runningNodeId === container.attrs.associatedNode}
|
class:is-executing={$queueState.runningNodeId && $queueState.runningNodeId === container.attrs.associatedNode}
|
||||||
use:dndzone="{{
|
use:dndzone="{{
|
||||||
items: children,
|
items: children,
|
||||||
@@ -119,7 +127,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if $uiState.uiEditMode === "containers" && zIndex > 1}
|
{#if showHandles}
|
||||||
<div class="handle handle-container" style="z-index: {zIndex+100}" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
<div class="handle handle-container" style="z-index: {zIndex+100}" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||||
{/if}
|
{/if}
|
||||||
</Block>
|
</Block>
|
||||||
@@ -131,7 +139,7 @@
|
|||||||
>
|
>
|
||||||
<svelte:component this={getComponentForWidgetState(widgetState)} item={widgetState} />
|
<svelte:component this={getComponentForWidgetState(widgetState)} item={widgetState} />
|
||||||
</div>
|
</div>
|
||||||
{#if $uiState.uiEditMode ==="widgets" && zIndex > 1}
|
{#if showHandles}
|
||||||
<div class="handle handle-widget" style="z-index: {zIndex+100}" data-drag-item-id={widget.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
<div class="handle handle-widget" style="z-index: {zIndex+100}" data-drag-item-id={widget.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
@@ -197,10 +205,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.container, .widget {
|
.widget.selected {
|
||||||
&.selected {
|
|
||||||
background: var(--color-yellow-200);
|
background: var(--color-yellow-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container.selected > :global(.block) {
|
||||||
|
background: var(--color-yellow-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-executing {
|
.is-executing {
|
||||||
@@ -286,7 +296,7 @@
|
|||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
border-style: dashed !important;
|
border-style: dashed !important;
|
||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
padding: 1.2em;
|
padding: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget-edit-outline {
|
.widget-edit-outline {
|
||||||
|
|||||||
47
src/lib/components/menu/ContextMenu.svelte
Normal file
47
src/lib/components/menu/ContextMenu.svelte
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<script>
|
||||||
|
import Menu from './Menu.svelte';
|
||||||
|
import MenuOption from './MenuOption.svelte';
|
||||||
|
import MenuDivider from './MenuDivider.svelte';
|
||||||
|
import { tick } from 'svelte'
|
||||||
|
|
||||||
|
import Icon from './Icon.svelte'
|
||||||
|
|
||||||
|
let pos = { x: 0, y: 0 };
|
||||||
|
let showMenu = false;
|
||||||
|
|
||||||
|
async function onRightClick(e) {
|
||||||
|
if (showMenu) {
|
||||||
|
showMenu = false;
|
||||||
|
await new Promise(res => setTimeout(res, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = { x: e.clientX, y: e.clientY };
|
||||||
|
showMenu = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMenu() {
|
||||||
|
showMenu = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if showMenu}
|
||||||
|
<Menu {...pos} on:click={closeMenu} on:clickoutside={closeMenu}>
|
||||||
|
<MenuOption
|
||||||
|
on:click={console.log}
|
||||||
|
text="Do nothing" />
|
||||||
|
<MenuOption
|
||||||
|
on:click={console.log}
|
||||||
|
text="Do nothing, but twice" />
|
||||||
|
<MenuDivider />
|
||||||
|
<MenuOption
|
||||||
|
isDisabled={true}
|
||||||
|
on:click={console.log}
|
||||||
|
text="Whoops, disabled!" />
|
||||||
|
<MenuOption on:click={console.log}>
|
||||||
|
<Icon />
|
||||||
|
<span>Look! An icon!</span>
|
||||||
|
</MenuOption>
|
||||||
|
</Menu>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<svelte:body on:contextmenu|preventDefault={onRightClick} />
|
||||||
3
src/lib/components/menu/Icon.svelte
Normal file
3
src/lib/components/menu/Icon.svelte
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M11.2584 13.039C10.3201 13.647 9.2013 14 8 14C4.68629 14 2 11.3137 2 8C2 6.66837 2.4338 5.43806 3.1678 4.44268L11.2584 13.039ZM12.7332 11.6878L4.60537 3.05194C5.57075 2.38838 6.74002 2 8 2C11.3137 2 14 4.68629 14 8C14 9.39043 13.527 10.6704 12.7332 11.6878ZM16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8Z" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 548 B |
45
src/lib/components/menu/Menu.svelte
Normal file
45
src/lib/components/menu/Menu.svelte
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<script>
|
||||||
|
import { setContext, createEventDispatcher } from 'svelte';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import { key } from './menu.ts';
|
||||||
|
|
||||||
|
export let x;
|
||||||
|
export let y;
|
||||||
|
|
||||||
|
// whenever x and y is changed, restrict box to be within bounds
|
||||||
|
$: (() => {
|
||||||
|
if (!menuEl) return;
|
||||||
|
|
||||||
|
const rect = menuEl.getBoundingClientRect();
|
||||||
|
x = Math.min(window.innerWidth - rect.width, x);
|
||||||
|
if (y > window.innerHeight - rect.height) y -= rect.height;
|
||||||
|
})(x, y);
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
setContext(key, {
|
||||||
|
dispatchClick: () => dispatch('click')
|
||||||
|
});
|
||||||
|
|
||||||
|
let menuEl;
|
||||||
|
function onPageClick(e) {
|
||||||
|
if (e.target === menuEl || menuEl.contains(e.target)) return;
|
||||||
|
dispatch('clickoutside');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:body on:click={onPageClick} />
|
||||||
|
<div class="menu" bind:this={menuEl} style="top: {y}px; left: {x}px;">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.menu {
|
||||||
|
z-index: var(--layer-top);
|
||||||
|
position: absolute;
|
||||||
|
display: grid;
|
||||||
|
border: 1px solid #0003;
|
||||||
|
box-shadow: 2px 2px 5px 0px #0002;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
9
src/lib/components/menu/MenuDivider.svelte
Normal file
9
src/lib/components/menu/MenuDivider.svelte
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<hr />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
hr {
|
||||||
|
border-top: 1px solid #0003;
|
||||||
|
width: 100%;
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
50
src/lib/components/menu/MenuOption.svelte
Normal file
50
src/lib/components/menu/MenuOption.svelte
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount, getContext } from 'svelte';
|
||||||
|
import { key } from './menu.ts';
|
||||||
|
|
||||||
|
export let isDisabled = false;
|
||||||
|
export let text = '';
|
||||||
|
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
const { dispatchClick } = getContext(key);
|
||||||
|
|
||||||
|
const handleClick = e => {
|
||||||
|
if (isDisabled) return;
|
||||||
|
|
||||||
|
dispatch('click');
|
||||||
|
dispatchClick();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class:disabled={isDisabled}
|
||||||
|
on:click={handleClick}
|
||||||
|
>
|
||||||
|
{#if text}
|
||||||
|
{text}
|
||||||
|
{:else}
|
||||||
|
<slot />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
padding: 4px 15px;
|
||||||
|
cursor: default;
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
grid-gap: 5px;
|
||||||
|
}
|
||||||
|
div:hover {
|
||||||
|
background: #0002;
|
||||||
|
}
|
||||||
|
div.disabled {
|
||||||
|
color: #0006;
|
||||||
|
}
|
||||||
|
div.disabled:hover {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
3
src/lib/components/menu/menu.ts
Normal file
3
src/lib/components/menu/menu.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
const key = {};
|
||||||
|
|
||||||
|
export { key };
|
||||||
@@ -16,7 +16,8 @@ export type LayoutState = {
|
|||||||
root: IDragItem | null,
|
root: IDragItem | null,
|
||||||
allItems: Record<DragItemID, DragItemEntry>,
|
allItems: Record<DragItemID, DragItemEntry>,
|
||||||
currentId: number,
|
currentId: number,
|
||||||
currentSelection: IDragItem[]
|
currentSelection: DragItemID[],
|
||||||
|
isMenuOpen: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Attributes = {
|
export type Attributes = {
|
||||||
@@ -62,7 +63,8 @@ const store: Writable<LayoutState> = writable({
|
|||||||
root: null,
|
root: null,
|
||||||
allItems: [],
|
allItems: [],
|
||||||
currentId: 0,
|
currentId: 0,
|
||||||
currentSelection: []
|
currentSelection: [],
|
||||||
|
isMenuOpen: false
|
||||||
})
|
})
|
||||||
|
|
||||||
function findDefaultContainerForInsertion(): ContainerLayout | null {
|
function findDefaultContainerForInsertion(): ContainerLayout | null {
|
||||||
|
|||||||
Reference in New Issue
Block a user