Skip to content

Commit

Permalink
[shared-ui/visual-editor] Teach runtime and UI to duplicate boards & …
Browse files Browse the repository at this point in the history
…mods (#3844)

Working on #3836
  • Loading branch information
paullewis authored and timswanson-google committed Dec 3, 2024
1 parent 415af4f commit ad3a296
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 26 deletions.
6 changes: 6 additions & 0 deletions .changeset/fast-mayflies-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@breadboard-ai/visual-editor": patch
"@breadboard-ai/shared-ui": patch
---

Teach runtime and UI to duplicate boards & mods
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { customElement, property, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { map } from "lit/directives/map.js";
import {
BoardItemCopyEvent,
HideTooltipEvent,
ModuleChosenEvent,
ModuleCreateEvent,
Expand Down Expand Up @@ -568,30 +569,43 @@ export class WorkspaceOutline extends LitElement {
background-color: var(--bb-neutral-50);
}
.duplicate,
.delete {
height: var(--bb-grid-size-7);
width: var(--bb-grid-size-7);
border: none;
background: transparent var(--bb-icon-delete) center center / 20px 20px
no-repeat;
background: transparent none center center / 20px 20px no-repeat;
font-size: 0;
margin: 0 var(--bb-grid-size-2);
margin: 0 0 0 var(--bb-grid-size-2);
opacity: 0;
cursor: pointer;
border-radius: 50%;
transition: background 0.1s cubic-bezier(0, 0, 0.3, 1);
}
.title:hover .duplicate,
.duplicate:hover,
.duplicate:focus,
.title:hover .delete,
.delete:hover,
.delete:focus {
opacity: 1;
}
.duplicate:hover,
.duplicate:focus,
.delete:hover,
.delete:focus {
background-color: var(--bb-neutral-50);
}
.delete {
background-image: var(--bb-icon-delete);
}
.duplicate {
background-image: var(--bb-icon-duplicate);
}
`;

#containerRef: Ref<HTMLDivElement> = createRef();
Expand Down Expand Up @@ -1083,35 +1097,57 @@ export class WorkspaceOutline extends LitElement {
</button>
${main !== id
? html`<button
class="delete"
@click=${() => {
if (subItem.type === "declarative") {
if (
!confirm(
"Are you sure you wish to delete this board?"
)
) {
class="duplicate"
@click=${() => {
const name = prompt(
"What would you like to call this?",
`${subItem.title} Copy`
);
if (!name) {
return;
}
this.dispatchEvent(new SubGraphDeleteEvent(id));
return;
} else {
if (
!confirm(
"Are you sure you wish to delete this module?"
this.dispatchEvent(
new BoardItemCopyEvent(
id,
subItem.type === "declarative" ? "graph" : "module",
name
)
) {
);
}}
>
Duplicate
</button>
<button
class="delete"
@click=${() => {
if (subItem.type === "declarative") {
if (
!confirm(
"Are you sure you wish to delete this board?"
)
) {
return;
}
this.dispatchEvent(new SubGraphDeleteEvent(id));
return;
}
} else {
if (
!confirm(
"Are you sure you wish to delete this module?"
)
) {
return;
}
this.dispatchEvent(new ModuleDeleteEvent(id));
return;
}
}}
>
Delete
</button>`
this.dispatchEvent(new ModuleDeleteEvent(id));
return;
}
}}
>
Delete
</button>`
: nothing}
</div>
</summary>
Expand Down
12 changes: 12 additions & 0 deletions packages/shared-ui/src/events/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,18 @@ export class GraphBoardServerSelectionChangeEvent extends Event {
* Graph Management - UI
*/

export class BoardItemCopyEvent extends Event {
static eventName = "bbboarditemcopy";

constructor(
public readonly id: GraphIdentifier | ModuleIdentifier,
public readonly itemType: "graph" | "module",
public readonly title: string
) {
super(BoardItemCopyEvent.eventName, { ...eventInit });
}
}

export class ZoomToGraphEvent extends Event {
static eventName = "bbzoomtograph";

Expand Down
10 changes: 10 additions & 0 deletions packages/visual-editor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2610,6 +2610,16 @@ export class Main extends LitElement {
@bbinteraction=${() => {
this.#clearBoardSave();
}}
@bbboarditemcopy=${(
evt: BreadboardUI.Events.BoardItemCopyEvent
) => {
this.#runtime.edit.copyBoardItem(
this.tab,
evt.itemType,
evt.id,
evt.title
);
}}
@bbsave=${() => {
this.#attemptBoardSave();
}}
Expand Down
65 changes: 65 additions & 0 deletions packages/visual-editor/src/runtime/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
RuntimeErrorEvent,
} from "./events";
import {
GraphIdentifier,
GraphTag,
Module,
ModuleCode,
Expand Down Expand Up @@ -329,6 +330,70 @@ export class Edit extends EventTarget {
this.dispatchEvent(new RuntimeBoardEditEvent(tab.id, [], false));
}

async copyBoardItem(
tab: Tab | null,
type: "graph" | "module",
id: GraphIdentifier | ModuleIdentifier,
title: string
) {
if (!tab) {
return null;
}

const editableGraph = this.getEditor(tab);
if (!editableGraph) {
this.dispatchEvent(new RuntimeErrorEvent("Unable to edit graph"));
return null;
}

const edits: EditSpec[] = [];
switch (type) {
case "graph": {
const graph = editableGraph.raw().graphs?.[id];
if (!graph) {
console.warn(
`Issued copy request for "${id}", but no such graph was found`
);
break;
}

const newGraph = structuredClone(graph);
newGraph.title = title;

const newId = crypto.randomUUID();
edits.push({ type: "addgraph", graph: newGraph, id: newId });
break;
}

case "module": {
const module = editableGraph.raw().modules?.[id];
if (!module) {
console.warn(
`Issued copy request for "${id}", but no such module was found`
);
break;
}

const newModule = structuredClone(module);
newModule.metadata ??= {};
newModule.metadata.title = title;

const newId = crypto.randomUUID();
edits.push({ type: "addmodule", module: newModule, id: newId });
break;
}

default:
throw new Error(`Unexpected copy type ${type}`);
}

if (!edits.length) {
return;
}

return editableGraph.edit(edits, `Copying to ${title} (${type})`);
}

createModule(
tab: Tab | null,
moduleId: ModuleIdentifier,
Expand Down

0 comments on commit ad3a296

Please sign in to comment.