Group/ungroup feature
This commit is contained in:
@@ -47,6 +47,34 @@
|
|||||||
console.warn($layoutState)
|
console.warn($layoutState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function groupWidgets() {
|
||||||
|
const items = layoutState.getCurrentSelection()
|
||||||
|
$layoutState.currentSelection = []
|
||||||
|
layoutState.groupItems(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
let canUngroup = false;
|
||||||
|
let isDeleteGroup = false;
|
||||||
|
$: canUngroup = $layoutState.currentSelection.length === 1
|
||||||
|
&& layoutState.getCurrentSelection()[0].type === "container"
|
||||||
|
$: if (canUngroup) {
|
||||||
|
const dragItem = layoutState.getCurrentSelection()[0];
|
||||||
|
const entry = $layoutState.allItems[dragItem.id];
|
||||||
|
isDeleteGroup = entry.children.length === 0
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isDeleteGroup = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function ungroup() {
|
||||||
|
const item = layoutState.getCurrentSelection()[0]
|
||||||
|
if (item.type !== "container")
|
||||||
|
return;
|
||||||
|
|
||||||
|
$layoutState.currentSelection = []
|
||||||
|
layoutState.ungroup(item as ContainerLayout)
|
||||||
|
}
|
||||||
|
|
||||||
let menuPos = { x: 0, y: 0 };
|
let menuPos = { x: 0, y: 0 };
|
||||||
let showMenu = false;
|
let showMenu = false;
|
||||||
|
|
||||||
@@ -78,20 +106,13 @@
|
|||||||
{#if showMenu}
|
{#if showMenu}
|
||||||
<Menu {...menuPos} on:click={closeMenu} on:clickoutside={closeMenu}>
|
<Menu {...menuPos} on:click={closeMenu} on:clickoutside={closeMenu}>
|
||||||
<MenuOption
|
<MenuOption
|
||||||
on:click={console.log}
|
isDisabled={$layoutState.currentSelection.length === 0}
|
||||||
text="Do nothing" />
|
on:click={groupWidgets}
|
||||||
|
text="Group" />
|
||||||
<MenuOption
|
<MenuOption
|
||||||
on:click={console.log}
|
isDisabled={!canUngroup}
|
||||||
text="Do nothing, but twice" />
|
on:click={ungroup}
|
||||||
<MenuDivider />
|
text={isDeleteGroup ? "Delete Group" : "Ungroup"} />
|
||||||
<MenuOption
|
|
||||||
isDisabled={true}
|
|
||||||
on:click={console.log}
|
|
||||||
text="Whoops, disabled!" />
|
|
||||||
<MenuOption on:click={console.log}>
|
|
||||||
<Icon />
|
|
||||||
<span>Look! An icon!</span>
|
|
||||||
</MenuOption>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,14 @@
|
|||||||
const flipDurationMs = 100;
|
const flipDurationMs = 100;
|
||||||
|
|
||||||
$: if (dragItem) {
|
$: if (dragItem) {
|
||||||
if (dragItem.type === "container") {
|
if (!$layoutState.allItems[dragItem.id]) {
|
||||||
|
dragItem = null;
|
||||||
|
widget = null;
|
||||||
|
widgetState = null;
|
||||||
|
children = null;
|
||||||
|
container = null;
|
||||||
|
}
|
||||||
|
else if (dragItem.type === "container") {
|
||||||
container = dragItem as ContainerLayout;
|
container = dragItem as ContainerLayout;
|
||||||
children = $layoutState.allItems[dragItem.id].children;
|
children = $layoutState.allItems[dragItem.id].children;
|
||||||
widget = null;
|
widget = null;
|
||||||
@@ -54,11 +61,16 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
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"];
|
||||||
|
|
||||||
|
if (evt.button !== 0) {
|
||||||
|
if ($layoutState.currentSelection.length <= 1 && !$layoutState.isMenuOpen)
|
||||||
|
$layoutState.currentSelection = [dragItemId]
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const item = $layoutState.allItems[dragItemId].dragItem
|
const item = $layoutState.allItems[dragItemId].dragItem
|
||||||
|
|
||||||
if (evt.ctrlKey) {
|
if (evt.ctrlKey) {
|
||||||
const index = $layoutState.currentSelection.indexOf(item.id)
|
const index = $layoutState.currentSelection.indexOf(item.id)
|
||||||
if (index === -1)
|
if (index === -1)
|
||||||
@@ -103,7 +115,6 @@
|
|||||||
<div class="v-pane"
|
<div class="v-pane"
|
||||||
class:empty={children.length === 0}
|
class:empty={children.length === 0}
|
||||||
class:edit={$uiState.uiEditMode === "widgets" && zIndex > 1}
|
class:edit={$uiState.uiEditMode === "widgets" && zIndex > 1}
|
||||||
class:is-executing={$queueState.runningNodeId && $queueState.runningNodeId === container.attrs.associatedNode}
|
|
||||||
use:dndzone="{{
|
use:dndzone="{{
|
||||||
items: children,
|
items: children,
|
||||||
flipDurationMs,
|
flipDurationMs,
|
||||||
@@ -137,6 +148,10 @@
|
|||||||
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(widget.id)}
|
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(widget.id)}
|
||||||
class:is-executing={$queueState.runningNodeId && $queueState.runningNodeId == widget.attrs.associatedNode}
|
class:is-executing={$queueState.runningNodeId && $queueState.runningNodeId == widget.attrs.associatedNode}
|
||||||
>
|
>
|
||||||
|
{#if widget.attrs.associatedNode}
|
||||||
|
{@const node = $nodeState[widget.attrs.associatedNode].node}
|
||||||
|
<span class="node-type">({node.type})</span>
|
||||||
|
{/if}
|
||||||
<svelte:component this={getComponentForWidgetState(widgetState)} item={widgetState} />
|
<svelte:component this={getComponentForWidgetState(widgetState)} item={widgetState} />
|
||||||
</div>
|
</div>
|
||||||
{#if showHandles}
|
{#if showHandles}
|
||||||
@@ -261,12 +276,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.edit-title-label {
|
.edit-title-label {
|
||||||
|
z-index: 10000;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: var(--layer-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-title {
|
.edit-title {
|
||||||
z-index: var(--layer-1);
|
z-index: 10000;
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
|
|||||||
@@ -48,12 +48,15 @@ export interface WidgetLayout extends IDragItem {
|
|||||||
type DragItemID = string;
|
type DragItemID = string;
|
||||||
|
|
||||||
type LayoutStateOps = {
|
type LayoutStateOps = {
|
||||||
addContainer: (parentId: DragItemID, attrs: Partial<Attributes>) => ContainerLayout,
|
addContainer: (parentId: DragItemID, attrs: Partial<Attributes>, index: number) => ContainerLayout,
|
||||||
|
addWidget: (parentId: DragItemID, node: LGraphNode, widget: IWidget<any, any>, attrs: Partial<Attributes>, index: number) => WidgetLayout,
|
||||||
findDefaultContainerForInsertion: () => ContainerLayout | null,
|
findDefaultContainerForInsertion: () => ContainerLayout | null,
|
||||||
addWidget: (parentId: DragItemID, node: LGraphNode, widget: IWidget<any, any>, attrs: Partial<Attributes>) => WidgetLayout,
|
|
||||||
updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[],
|
updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[],
|
||||||
nodeAdded: (node: LGraphNode) => void,
|
nodeAdded: (node: LGraphNode) => void,
|
||||||
nodeRemoved: (node: LGraphNode) => void,
|
nodeRemoved: (node: LGraphNode) => void,
|
||||||
|
groupItems: (dragItems: IDragItem[]) => ContainerLayout,
|
||||||
|
ungroup: (container: ContainerLayout) => void,
|
||||||
|
getCurrentSelection: () => IDragItem[],
|
||||||
clear: () => void,
|
clear: () => void,
|
||||||
resetLayout: () => void,
|
resetLayout: () => void,
|
||||||
}
|
}
|
||||||
@@ -87,7 +90,7 @@ function findDefaultContainerForInsertion(): ContainerLayout | null {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function addContainer(parentId: DragItemID | null, attrs: Partial<Attributes> = {}): ContainerLayout {
|
function addContainer(parentId: DragItemID | null, attrs: Partial<Attributes> = {}, index: number = -1): ContainerLayout {
|
||||||
const state = get(store);
|
const state = get(store);
|
||||||
const dragItem: ContainerLayout = {
|
const dragItem: ContainerLayout = {
|
||||||
type: "container",
|
type: "container",
|
||||||
@@ -106,13 +109,16 @@ function addContainer(parentId: DragItemID | null, attrs: Partial<Attributes> =
|
|||||||
state.allItems[dragItem.id] = entry;
|
state.allItems[dragItem.id] = entry;
|
||||||
if (parent) {
|
if (parent) {
|
||||||
parent.children ||= []
|
parent.children ||= []
|
||||||
parent.children.push(dragItem)
|
if (index)
|
||||||
|
parent.children.splice(index, 0, dragItem)
|
||||||
|
else
|
||||||
|
parent.children.push(dragItem)
|
||||||
}
|
}
|
||||||
store.set(state)
|
store.set(state)
|
||||||
return dragItem;
|
return dragItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addWidget(parentId: DragItemID, node: LGraphNode, widget: IWidget<any, any>, attrs: Partial<Attributes> = {}): WidgetLayout {
|
function addWidget(parentId: DragItemID, node: LGraphNode, widget: IWidget<any, any>, attrs: Partial<Attributes> = {}, index: number = -1): WidgetLayout {
|
||||||
const state = get(store);
|
const state = get(store);
|
||||||
const dragItem: WidgetLayout = {
|
const dragItem: WidgetLayout = {
|
||||||
type: "widget",
|
type: "widget",
|
||||||
@@ -132,21 +138,25 @@ function addWidget(parentId: DragItemID, node: LGraphNode, widget: IWidget<any,
|
|||||||
const entry: DragItemEntry = { dragItem, children: [], parent: parent.dragItem };
|
const entry: DragItemEntry = { dragItem, children: [], parent: parent.dragItem };
|
||||||
state.allItems[dragItem.id] = entry;
|
state.allItems[dragItem.id] = entry;
|
||||||
parent.children ||= []
|
parent.children ||= []
|
||||||
parent.children.push(dragItem)
|
if (index !== -1)
|
||||||
|
parent.children.splice(index, 0, dragItem)
|
||||||
|
else
|
||||||
|
parent.children.push(dragItem)
|
||||||
store.set(state)
|
store.set(state)
|
||||||
return dragItem;
|
return dragItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateChildren(parent: IDragItem, children: IDragItem[]): IDragItem[] {
|
function updateChildren(parent: IDragItem, newChildren?: IDragItem[]): IDragItem[] {
|
||||||
const state = get(store);
|
const state = get(store);
|
||||||
state.allItems[parent.id].children = children;
|
if (newChildren)
|
||||||
for (const child of children) {
|
state.allItems[parent.id].children = newChildren;
|
||||||
|
for (const child of state.allItems[parent.id].children) {
|
||||||
if (child.id === SHADOW_PLACEHOLDER_ITEM_ID)
|
if (child.id === SHADOW_PLACEHOLDER_ITEM_ID)
|
||||||
continue;
|
continue;
|
||||||
state.allItems[child.id].parent = parent;
|
state.allItems[child.id].parent = parent;
|
||||||
}
|
}
|
||||||
store.set(state)
|
store.set(state)
|
||||||
return children
|
return state.allItems[parent.id].children
|
||||||
}
|
}
|
||||||
|
|
||||||
function nodeAdded(node: LGraphNode) {
|
function nodeAdded(node: LGraphNode) {
|
||||||
@@ -207,6 +217,99 @@ function nodeRemoved(node: LGraphNode) {
|
|||||||
store.set(state)
|
store.set(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveItem(target: IDragItem, to: ContainerLayout, index: number = -1) {
|
||||||
|
const state = get(store)
|
||||||
|
const entry = state.allItems[target.id]
|
||||||
|
if (entry.parent && entry.parent.id === to.id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (entry.parent) {
|
||||||
|
const parentEntry = state.allItems[entry.parent.id];
|
||||||
|
const index = parentEntry.children.indexOf(target)
|
||||||
|
if (index !== -1) {
|
||||||
|
parentEntry.children.splice(index, 1)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(parentEntry)
|
||||||
|
console.error(target)
|
||||||
|
throw "Child not found in parent!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toEntry = state.allItems[to.id];
|
||||||
|
if (index !== -1)
|
||||||
|
toEntry.children.splice(index, 0, target)
|
||||||
|
else
|
||||||
|
toEntry.children.push(target)
|
||||||
|
state.allItems[target.id].parent = toEntry.dragItem;
|
||||||
|
|
||||||
|
console.debug("[layoutState] Move child", target, toEntry, index)
|
||||||
|
|
||||||
|
store.set(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentSelection(): IDragItem[] {
|
||||||
|
const state = get(store)
|
||||||
|
return state.currentSelection.map(id => state.allItems[id].dragItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupItems(dragItems: IDragItem[]): ContainerLayout {
|
||||||
|
if (dragItems.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const state = get(store)
|
||||||
|
const parent = state.allItems[dragItems[0].id].parent || findDefaultContainerForInsertion();
|
||||||
|
|
||||||
|
if (parent === null || parent.type !== "container")
|
||||||
|
return;
|
||||||
|
|
||||||
|
let index = undefined;
|
||||||
|
if (parent) {
|
||||||
|
const indexFound = state.allItems[parent.id].children.indexOf(dragItems[0])
|
||||||
|
if (indexFound !== -1)
|
||||||
|
index = indexFound
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = addContainer(parent.id, { title: "Group" }, index)
|
||||||
|
|
||||||
|
for (const item of dragItems) {
|
||||||
|
moveItem(item, container)
|
||||||
|
}
|
||||||
|
|
||||||
|
store.set(state)
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
|
||||||
|
function ungroup(container: ContainerLayout) {
|
||||||
|
const state = get(store)
|
||||||
|
|
||||||
|
const parent = state.allItems[container.id].parent;
|
||||||
|
if (!parent || parent.type !== "container") {
|
||||||
|
console.warn("No parent to ungroup into!", container)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = undefined;
|
||||||
|
const parentChildren = state.allItems[parent.id].children;
|
||||||
|
const indexFound = parentChildren.indexOf(container)
|
||||||
|
if (indexFound !== -1)
|
||||||
|
index = indexFound
|
||||||
|
|
||||||
|
const containerEntry = state.allItems[container.id]
|
||||||
|
console.debug("[layoutState] About to ungroup", containerEntry, parent, parentChildren, index)
|
||||||
|
|
||||||
|
const children = [...containerEntry.children]
|
||||||
|
for (const item of children) {
|
||||||
|
moveItem(item, parent as ContainerLayout, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEntry(state, container.id)
|
||||||
|
|
||||||
|
console.debug("[layoutState] Ungrouped", containerEntry, parent, parentChildren, index)
|
||||||
|
|
||||||
|
store.set(state)
|
||||||
|
}
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +317,7 @@ function resetLayout() {
|
|||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
const uiStateStore: WritableLayoutStateStore =
|
const layoutStateStore: WritableLayoutStateStore =
|
||||||
{
|
{
|
||||||
...store,
|
...store,
|
||||||
addContainer,
|
addContainer,
|
||||||
@@ -223,7 +326,10 @@ const uiStateStore: WritableLayoutStateStore =
|
|||||||
updateChildren,
|
updateChildren,
|
||||||
nodeAdded,
|
nodeAdded,
|
||||||
nodeRemoved,
|
nodeRemoved,
|
||||||
|
getCurrentSelection,
|
||||||
|
groupItems,
|
||||||
|
ungroup,
|
||||||
clear,
|
clear,
|
||||||
resetLayout
|
resetLayout
|
||||||
}
|
}
|
||||||
export default uiStateStore;
|
export default layoutStateStore;
|
||||||
|
|||||||
Reference in New Issue
Block a user