new node / new edge modes, popups
This commit is contained in:
parent
277ba8dbe3
commit
4ff8242d48
|
@ -22,8 +22,12 @@
|
|||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.3",
|
||||
"@shoelace-style/shoelace": "^2.15.0",
|
||||
"@wavebeem/candy-css": "^0.3.0",
|
||||
"@types/cytoscape-edgehandles": "^4.0.3",
|
||||
"@types/cytoscape-popper": "^2.0.4",
|
||||
"cytoscape": "^3.28.1",
|
||||
"ts-loader": "^9.5.1"
|
||||
"cytoscape-edgehandles": "^4.0.1",
|
||||
"cytoscape-popper": "^4.0.0",
|
||||
"ts-loader": "^9.5.1",
|
||||
"tseep": "^1.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,41 @@
|
|||
import * as cytoscape from "cytoscape";
|
||||
import * as edgehandles from "cytoscape-edgehandles";
|
||||
import * as cytoscapePopper from "cytoscape-popper";
|
||||
import { RoomClient } from "../../common";
|
||||
import { ToggleButtons } from "./togglebuttons";
|
||||
//import { computePosition, flip } from "@floating-ui/dom";
|
||||
import { computePosition, flip, shift, limitShift } from "@floating-ui/dom";
|
||||
|
||||
function popperFactory(ref: any, content: any, opts: any) {
|
||||
// see https://floating-ui.com/docs/computePosition#options
|
||||
const popperOptions = {
|
||||
// matching the default behaviour from Popper@2
|
||||
// https://floating-ui.com/docs/migration#configure-middleware
|
||||
middleware: [flip(), shift({ limiter: limitShift() })],
|
||||
...opts,
|
||||
};
|
||||
|
||||
function update() {
|
||||
computePosition(ref, content, popperOptions).then(({ x, y }) => {
|
||||
console.log(`update object: ${x} ${y}`);
|
||||
Object.assign(content.style, {
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
});
|
||||
});
|
||||
}
|
||||
update();
|
||||
return { update };
|
||||
}
|
||||
|
||||
export class ThingGraph {
|
||||
private cy;
|
||||
private eh;
|
||||
private client: RoomClient;
|
||||
private socket: WebSocket;
|
||||
public constructor() {
|
||||
public constructor(private modeToggle: ToggleButtons) {
|
||||
cytoscape.use(edgehandles);
|
||||
cytoscape.use(cytoscapePopper(popperFactory));
|
||||
this.cy = cytoscape({
|
||||
container: document.getElementById("cy"),
|
||||
elements: [
|
||||
|
@ -21,6 +50,31 @@ export class ThingGraph {
|
|||
},
|
||||
],
|
||||
});
|
||||
this.eh = this.cy.edgehandles({
|
||||
canConnect: function (sourceNode: any, targetNode: any) {
|
||||
// whether an edge can be created between source and target
|
||||
return !sourceNode.same(targetNode); // e.g. disallow loops
|
||||
},
|
||||
edgeParams: function (sourceNode: any, targetNode: any) {
|
||||
// for edges between the specified source and target
|
||||
// return element object to be passed to cy.add() for edge
|
||||
return { data: {} };
|
||||
},
|
||||
hoverDelay: 150, // time spent hovering over a target node before it is considered selected
|
||||
snap: true, // when enabled, the edge can be drawn by just moving close to a target node (can be confusing on compound graphs)
|
||||
snapThreshold: 50, // the target node must be less than or equal to this many pixels away from the cursor/finger
|
||||
snapFrequency: 15, // the number of times per second (Hz) that snap checks done (lower is less expensive)
|
||||
noEdgeEventsInDraw: true, // set events:no to edges during draws, prevents mouseouts on compounds
|
||||
disableBrowserGestures: true, // during an edge drawing gesture, disable browser gestures such as two-finger trackpad swipe and pinch-to-zoom
|
||||
});
|
||||
|
||||
this.modeToggle.emitter.on("toggled", (index, id) => {
|
||||
if (id === "newEdges") {
|
||||
this.eh.enableDrawMode();
|
||||
} else {
|
||||
this.eh.disableDrawMode();
|
||||
}
|
||||
});
|
||||
|
||||
this.socket = new WebSocket("ws://localhost:3000");
|
||||
this.client = new RoomClient((tm) => {
|
||||
|
@ -29,11 +83,47 @@ export class ThingGraph {
|
|||
this.socket.addEventListener("message", (event) => {
|
||||
this.client.handleMessage(event.data.toString());
|
||||
});
|
||||
|
||||
this.cy.on("click", (event) => {
|
||||
if (event.target === this.cy) {
|
||||
console.log("graph clicked.");
|
||||
|
||||
if (this.modeToggle.getActiveId() === "newNodes") {
|
||||
const pos = event.position;
|
||||
this.cy.add({
|
||||
group: "nodes",
|
||||
data: {},
|
||||
position: pos,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const group = event.target.group();
|
||||
console.log(`a '${group}' was clicked`);
|
||||
|
||||
let popper = event.target.popper({
|
||||
content: () => {
|
||||
let div = document.createElement("sl-card");
|
||||
|
||||
div.innerHTML = "Popper content";
|
||||
div.classList.add("popper-div");
|
||||
div.classList.add("card-basic");
|
||||
|
||||
document.body.appendChild(div);
|
||||
|
||||
return div;
|
||||
},
|
||||
});
|
||||
let update = () => {
|
||||
popper.update();
|
||||
};
|
||||
event.target.on("position", update);
|
||||
this.cy.on("pan zoom resize", update);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public resize() {
|
||||
this.cy.resize();
|
||||
this.cy.fit();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,57 +1,68 @@
|
|||
<sl-split-panel position="25" style="height:100%;">
|
||||
<sl-split-panel position="15" style="height: 100%">
|
||||
<sl-icon slot="divider" name="grip-vertical"></sl-icon>
|
||||
<div
|
||||
slot="start"
|
||||
style="--min: 100px; --max: 33%; height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
|
||||
slot="start"
|
||||
style="
|
||||
--min: 100px;
|
||||
--max: 33%;
|
||||
height: 100%;
|
||||
background: var(--sl-color-neutral-50);
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
"
|
||||
>
|
||||
stuff
|
||||
<div>
|
||||
<sl-button-group label="Mode">
|
||||
<sl-button size="medium" id="newNodes">New Nodes</sl-button>
|
||||
<sl-button size="medium" id="newEdges">New Edges</sl-button>
|
||||
<sl-button size="medium">IDK</sl-button>
|
||||
</sl-button-group>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<sl-button> Button time </sl-button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
slot="end"
|
||||
id="cy"
|
||||
style="height: 100%; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
|
||||
>
|
||||
</div>
|
||||
</sl-split-panel>
|
||||
slot="end"
|
||||
id="cy"
|
||||
style="
|
||||
height: 100%;
|
||||
background: var(--sl-color-neutral-50);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
"
|
||||
></div>
|
||||
</sl-split-panel>
|
||||
|
||||
<style>
|
||||
.split-panel-divider sl-split-panel {
|
||||
--divider-width: 2px;
|
||||
}
|
||||
.split-panel-divider sl-split-panel {
|
||||
--divider-width: 2px;
|
||||
}
|
||||
|
||||
.split-panel-divider sl-split-panel::part(divider) {
|
||||
background-color: var(--sl-color-pink-600);
|
||||
}
|
||||
.split-panel-divider sl-split-panel::part(divider) {
|
||||
background-color: var(--sl-color-pink-600);
|
||||
}
|
||||
|
||||
.split-panel-divider sl-icon {
|
||||
position: absolute;
|
||||
border-radius: var(--sl-border-radius-small);
|
||||
background: var(--sl-color-pink-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
padding: 0.5rem 0.125rem;
|
||||
}
|
||||
.split-panel-divider sl-icon {
|
||||
position: absolute;
|
||||
border-radius: var(--sl-border-radius-small);
|
||||
background: var(--sl-color-pink-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
padding: 0.5rem 0.125rem;
|
||||
}
|
||||
|
||||
.split-panel-divider sl-split-panel::part(divider):focus-visible {
|
||||
background-color: var(--sl-color-primary-600);
|
||||
}
|
||||
.split-panel-divider sl-split-panel::part(divider):focus-visible {
|
||||
background-color: var(--sl-color-primary-600);
|
||||
}
|
||||
|
||||
.split-panel-divider sl-split-panel:focus-within sl-icon {
|
||||
background-color: var(--sl-color-primary-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
.split-panel-divider sl-split-panel:focus-within sl-icon {
|
||||
background-color: var(--sl-color-primary-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<button type="button" class="candy-button candy-texture-glossy">
|
||||
Glossy
|
||||
</button>
|
||||
<button type="button" class="candy-button candy-texture-glossy">
|
||||
Glossy
|
||||
</button>
|
||||
<button type="button" class="candy-button candy-texture-glossy">
|
||||
Glossy
|
||||
</button>
|
||||
<button type="button" class="candy-button candy-texture-glossy">
|
||||
Glossy
|
||||
</button>
|
||||
</div>
|
|
@ -1,11 +1,16 @@
|
|||
import { ThingGraph } from "./graph";
|
||||
import "@wavebeem/candy-css";
|
||||
import "@shoelace-style/shoelace/dist/themes/light.css";
|
||||
import "@shoelace-style/shoelace/dist/themes/dark.css";
|
||||
import "@shoelace-style/shoelace/dist/components/button/button.js";
|
||||
import "@shoelace-style/shoelace/dist/components/button-group/button-group.js";
|
||||
import "@shoelace-style/shoelace/dist/components/split-panel/split-panel.js";
|
||||
import "@shoelace-style/shoelace/dist/components/popup/popup.js";
|
||||
import "@shoelace-style/shoelace/dist/components/icon/icon.js";
|
||||
import "@shoelace-style/shoelace/dist/components/card/card.js";
|
||||
import { setBasePath } from "@shoelace-style/shoelace/dist/utilities/base-path.js";
|
||||
import idk from "./idk.html";
|
||||
import { ToggleButtons } from "./togglebuttons";
|
||||
|
||||
setBasePath("/dist/shoelace/assets");
|
||||
setBasePath("/dist/shoelace");
|
||||
|
||||
function component() {
|
||||
const element = document.createElement("div");
|
||||
|
@ -19,12 +24,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
document.getElementById("app").innerHTML = idk;
|
||||
const splitPanel = document.querySelector("sl-split-panel");
|
||||
|
||||
const mode = new ToggleButtons(["newNodes", "newEdges"]);
|
||||
mode.emitter.on("toggled", (index, id) => {
|
||||
console.log(`toggled: ${index} ${id}`);
|
||||
});
|
||||
|
||||
var once = {
|
||||
created: false,
|
||||
};
|
||||
splitPanel.updateComplete.then(() => {
|
||||
if (!once.created) {
|
||||
const g = new ThingGraph();
|
||||
const g = new ThingGraph(mode);
|
||||
splitPanel.addEventListener("sl-reposition", () => {
|
||||
g.resize();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { SlButton } from "@shoelace-style/shoelace";
|
||||
import { EventEmitter } from "tseep";
|
||||
|
||||
export class ToggleButtons {
|
||||
private activeIndex: number = 0;
|
||||
private activeId: string;
|
||||
private buttonElements: SlButton[] = [];
|
||||
public readonly emitter = new EventEmitter<{
|
||||
toggled: (index: number, id: string) => void;
|
||||
}>();
|
||||
constructor(private buttonIds: string[]) {
|
||||
for (var i = 0; i < buttonIds.length; i++) {
|
||||
const thisButtonIndex = i;
|
||||
const id = buttonIds[i];
|
||||
const buttonElement = document.querySelector(`sl-button#${id}`);
|
||||
if (buttonElement === undefined) {
|
||||
throw new Error(`Button doesn't exist: ${id}`);
|
||||
}
|
||||
buttonElement.addEventListener("click", () => {
|
||||
this.activeIndex = thisButtonIndex;
|
||||
this.emitter.emit("toggled", thisButtonIndex, id);
|
||||
this.updateHighlight();
|
||||
});
|
||||
this.buttonElements.push(buttonElement as SlButton);
|
||||
}
|
||||
|
||||
this.activeId = buttonIds[0];
|
||||
this.updateHighlight();
|
||||
}
|
||||
|
||||
public getActiveId() {
|
||||
return this.activeId;
|
||||
}
|
||||
|
||||
private updateHighlight() {
|
||||
for (let i = 0; i < this.buttonElements.length; i++) {
|
||||
const element = this.buttonElements[i];
|
||||
if (this.activeIndex === i) {
|
||||
element.setAttribute("variant", "primary");
|
||||
} else {
|
||||
element.setAttribute("variant", "default");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,15 +20,27 @@ importers:
|
|||
'@shoelace-style/shoelace':
|
||||
specifier: ^2.15.0
|
||||
version: 2.15.0(@types/react@18.2.74)
|
||||
'@wavebeem/candy-css':
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0
|
||||
'@types/cytoscape-edgehandles':
|
||||
specifier: ^4.0.3
|
||||
version: 4.0.3
|
||||
'@types/cytoscape-popper':
|
||||
specifier: ^2.0.4
|
||||
version: 2.0.4
|
||||
cytoscape:
|
||||
specifier: ^3.28.1
|
||||
version: 3.28.1
|
||||
cytoscape-edgehandles:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1(cytoscape@3.28.1)
|
||||
cytoscape-popper:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0(cytoscape@3.28.1)
|
||||
ts-loader:
|
||||
specifier: ^9.5.1
|
||||
version: 9.5.1(typescript@5.4.3)(webpack@5.91.0)
|
||||
tseep:
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
devDependencies:
|
||||
'@types/cytoscape':
|
||||
specifier: ^3.21.0
|
||||
|
@ -448,6 +460,10 @@ packages:
|
|||
fastq: 1.17.1
|
||||
dev: false
|
||||
|
||||
/@popperjs/core@2.11.8:
|
||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
||||
dev: false
|
||||
|
||||
/@shoelace-style/animations@1.1.0:
|
||||
resolution: {integrity: sha512-Be+cahtZyI2dPKRm8EZSx3YJQ+jLvEcn3xzRP7tM4tqBnvd/eW/64Xh0iOf0t2w5P8iJKfdBbpVNE9naCaOf2g==}
|
||||
dev: false
|
||||
|
@ -506,9 +522,21 @@ packages:
|
|||
'@types/express': 4.17.21
|
||||
dev: true
|
||||
|
||||
/@types/cytoscape-edgehandles@4.0.3:
|
||||
resolution: {integrity: sha512-n/nUzfSudfbrtvcJIsruCvfduoW2zg/r+EjjFmceDDP+Pbdfx5A/fA/bAfAc4QlOwnkZ3HzF2oESAzes5mQHcg==}
|
||||
dependencies:
|
||||
'@types/cytoscape': 3.21.0
|
||||
dev: false
|
||||
|
||||
/@types/cytoscape-popper@2.0.4:
|
||||
resolution: {integrity: sha512-vGRiAMXeEIoY5ziPO0NrS8xmJyVkT8j8ARzvyD/x6CXciAO1+80Q2/Triyd9/+5I4PeatGo1Pch5YBwDMB1D6A==}
|
||||
dependencies:
|
||||
'@popperjs/core': 2.11.8
|
||||
'@types/cytoscape': 3.21.0
|
||||
dev: false
|
||||
|
||||
/@types/cytoscape@3.21.0:
|
||||
resolution: {integrity: sha512-RN5SPiyVDpUP+LoOlxxlOYAMzkE7iuv3gA1jt3Hx2qTwArpZVPPdO+SI0hUj49OAn4QABR7JK9Gi0hibzGE0Aw==}
|
||||
dev: true
|
||||
|
||||
/@types/eslint-scope@3.7.7:
|
||||
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
|
||||
|
@ -633,10 +661,6 @@ packages:
|
|||
'@types/node': 20.12.4
|
||||
dev: true
|
||||
|
||||
/@wavebeem/candy-css@0.3.0:
|
||||
resolution: {integrity: sha512-f8vbMQAYryU6a6o36tPhAycQI5WAf6TfrSTxV9hdYPbe8iU/uVbf5/BxDAEmFPuVPf9tW3mMsi/FX0FFiwt4ng==}
|
||||
dev: false
|
||||
|
||||
/@webassemblyjs/ast@1.12.1:
|
||||
resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==}
|
||||
dependencies:
|
||||
|
@ -1170,6 +1194,24 @@ packages:
|
|||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
dev: false
|
||||
|
||||
/cytoscape-edgehandles@4.0.1(cytoscape@3.28.1):
|
||||
resolution: {integrity: sha512-uSYshkqRZ4luCxK295bEVTg46q4ZW+fwJhcIzMrtfNR7zeAnJ38Z48kUGeu5ibtXkgLbcZAg0YE4ED2dRuaePg==}
|
||||
peerDependencies:
|
||||
cytoscape: ^3.2.0
|
||||
dependencies:
|
||||
cytoscape: 3.28.1
|
||||
lodash.memoize: 4.1.2
|
||||
lodash.throttle: 4.1.1
|
||||
dev: false
|
||||
|
||||
/cytoscape-popper@4.0.0(cytoscape@3.28.1):
|
||||
resolution: {integrity: sha512-M4q2YeIhZvRDslMLzVuGZKb6HAU3O6M51NAaRc0hr3KubQabiK2c9dEGwfVIBPcDnxr9u/oFAMhAU7DEf2EHaA==}
|
||||
peerDependencies:
|
||||
cytoscape: ^3.2.0
|
||||
dependencies:
|
||||
cytoscape: 3.28.1
|
||||
dev: false
|
||||
|
||||
/cytoscape@3.28.1:
|
||||
resolution: {integrity: sha512-xyItz4O/4zp9/239wCcH8ZcFuuZooEeF8KHRmzjDfGdXsj3OG9MFSMA0pJE0uX3uCN/ygof6hHf4L7lst+JaDg==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
@ -1800,6 +1842,14 @@ packages:
|
|||
dependencies:
|
||||
p-locate: 4.1.0
|
||||
|
||||
/lodash.memoize@4.1.2:
|
||||
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
|
||||
dev: false
|
||||
|
||||
/lodash.throttle@4.1.1:
|
||||
resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
|
||||
dev: false
|
||||
|
||||
/lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: false
|
||||
|
@ -2584,6 +2634,10 @@ packages:
|
|||
webpack: 5.91.0(webpack-cli@5.1.4)
|
||||
dev: false
|
||||
|
||||
/tseep@1.2.1:
|
||||
resolution: {integrity: sha512-VFnsNcPGC4qFJ1nxbIPSjTmtRZOhlqLmtwRqtLVos8mbRHki8HO9cy9Z1e89EiWyxFmq6LBviI9TQjijxw/mEw==}
|
||||
dev: false
|
||||
|
||||
/tslib@2.6.2:
|
||||
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||
dev: true
|
||||
|
|
|
@ -13,4 +13,15 @@ a {
|
|||
body {
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.popper-div {
|
||||
z-index: 9999;
|
||||
padding: 0.25em;
|
||||
/*pointer-events: none;*/
|
||||
width: max-content;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
|
@ -1,5 +1 @@
|
|||
extends layout
|
||||
|
||||
block content
|
||||
h1= title
|
||||
p Welcome to #{title}
|
||||
extends layout
|
|
@ -1,8 +1,9 @@
|
|||
doctype html
|
||||
html
|
||||
html(class='sl-theme-dark')
|
||||
head
|
||||
title= title
|
||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||
link(rel='stylesheet', href='/dist/main.css')
|
||||
script(src='/dist/main.js')
|
||||
body
|
||||
block content
|
||||
|
|
Loading…
Reference in New Issue