feat: add theming basics
This commit is contained in:
parent
f9d211eb72
commit
9b76299272
@ -1,12 +1,15 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
<head>
|
||||||
<link rel="icon" href="%sveltekit.assets%/svelte.svg" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<link rel="icon" href="%sveltekit.assets%/svelte.svg" />
|
||||||
%sveltekit.head%
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
</head>
|
%sveltekit.head%
|
||||||
<body data-sveltekit-preload-data="hover">
|
</head>
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
|
||||||
</body>
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -3,12 +3,13 @@
|
|||||||
import { OrbitControls } from "@threlte/extras";
|
import { OrbitControls } from "@threlte/extras";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { MOUSE, type OrthographicCamera } from "three";
|
import { MOUSE, type OrthographicCamera } from "three";
|
||||||
|
import type { OrbitControls as OrbitControlsType } from "three/examples/jsm/Addons.js";
|
||||||
|
|
||||||
export let camera: OrthographicCamera | undefined = undefined;
|
export let camera: OrthographicCamera | undefined = undefined;
|
||||||
export let maxZoom = 150;
|
export let maxZoom = 150;
|
||||||
export let minZoom = 4;
|
export let minZoom = 4;
|
||||||
|
|
||||||
let controls: OrbitControls | undefined = undefined;
|
export let controls: OrbitControlsType | undefined = undefined;
|
||||||
|
|
||||||
export const position: [number, number, number] = [0, 1, 0];
|
export const position: [number, number, number] = [0, 1, 0];
|
||||||
|
|
||||||
@ -56,8 +57,8 @@
|
|||||||
|
|
||||||
<T.OrthographicCamera bind:ref={camera} position.y={10} makeDefault>
|
<T.OrthographicCamera bind:ref={camera} position.y={10} makeDefault>
|
||||||
<OrbitControls
|
<OrbitControls
|
||||||
args={[camera, window]}
|
args={[camera, document.body]}
|
||||||
mouseButtons={{ LEFT: MOUSE.PAN, MIDDLE: 0, RIGHT: 0 }}
|
mouseButtons={{ LEFT: 0, MIDDLE: 0, RIGHT: MOUSE.PAN }}
|
||||||
bind:ref={controls}
|
bind:ref={controls}
|
||||||
enableZoom={true}
|
enableZoom={true}
|
||||||
zoomSpeed={2}
|
zoomSpeed={2}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Node } from "$lib/types";
|
import type { Node } from "$lib/types";
|
||||||
import { getContext, onMount } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import NodeHeader from "./NodeHeader.svelte";
|
import NodeHeader from "./NodeHeader.svelte";
|
||||||
import NodeParameter from "./NodeParameter.svelte";
|
import NodeParameter from "./NodeParameter.svelte";
|
||||||
import { activeNodeId, selectedNodes } from "./graph/stores";
|
import { activeNodeId, selectedNodes } from "./graph/stores";
|
||||||
@ -38,7 +38,6 @@
|
|||||||
<NodeParameter
|
<NodeParameter
|
||||||
{node}
|
{node}
|
||||||
id={key}
|
id={key}
|
||||||
index={i}
|
|
||||||
input={value}
|
input={value}
|
||||||
isLast={i == parameters.length - 1}
|
isLast={i == parameters.length - 1}
|
||||||
/>
|
/>
|
||||||
@ -51,25 +50,24 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
user-select: none !important;
|
user-select: none !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 100px;
|
width: 200px;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
transform: translate3d(var(--nx), var(--ny), 0);
|
transform: translate3d(var(--nx), var(--ny), 0);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 0.5em;
|
|
||||||
display: none;
|
display: none;
|
||||||
--stroke: #777;
|
--stroke: var(--background-color-lighter);
|
||||||
--stroke-width: 0.1px;
|
--stroke-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node.active {
|
.node.active {
|
||||||
--stroke: white;
|
--stroke: white;
|
||||||
--stroke-width: 0.3px;
|
--stroke-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node.selected {
|
.node.selected {
|
||||||
--stroke: #f2be90;
|
--stroke: #f2be90;
|
||||||
--stroke-width: 0.2px;
|
--stroke-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node.in-view {
|
.node.in-view {
|
||||||
|
@ -81,7 +81,7 @@
|
|||||||
.wrapper {
|
.wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 25px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.click-target {
|
.click-target {
|
||||||
@ -89,8 +89,8 @@
|
|||||||
right: 0px;
|
right: 0px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateX(50%) translateY(-50%);
|
transform: translateX(50%) translateY(-50%);
|
||||||
height: 15px;
|
height: 30px;
|
||||||
width: 15px;
|
width: 30px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
/* background: red; */
|
/* background: red; */
|
||||||
@ -114,7 +114,9 @@
|
|||||||
|
|
||||||
svg path {
|
svg path {
|
||||||
stroke-width: 0.2px;
|
stroke-width: 0.2px;
|
||||||
transition: 0.2s;
|
transition:
|
||||||
|
d 0.3s ease,
|
||||||
|
fill 0.3s ease;
|
||||||
fill: var(--background-color-lighter);
|
fill: var(--background-color-lighter);
|
||||||
stroke: var(--stroke);
|
stroke: var(--stroke);
|
||||||
stroke-width: var(--stroke-width);
|
stroke-width: var(--stroke-width);
|
||||||
@ -125,7 +127,7 @@
|
|||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: 5px;
|
padding-left: 20px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,9 +47,9 @@
|
|||||||
aspectRatio,
|
aspectRatio,
|
||||||
});
|
});
|
||||||
const pathHover = createNodePath({
|
const pathHover = createNodePath({
|
||||||
depth: 8,
|
depth: 6,
|
||||||
height: 24,
|
height: 18,
|
||||||
y: 50,
|
y: 50.5,
|
||||||
cornerBottom,
|
cornerBottom,
|
||||||
leftBump,
|
leftBump,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
@ -102,7 +102,7 @@
|
|||||||
.wrapper {
|
.wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50px;
|
height: 100px;
|
||||||
transform: translateY(-0.5px);
|
transform: translateY(-0.5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,13 +116,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.small.target {
|
.small.target {
|
||||||
width: 15px;
|
width: 30px;
|
||||||
height: 15px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.large.target {
|
.large.target {
|
||||||
width: 30px;
|
width: 60px;
|
||||||
height: 30px;
|
height: 60px;
|
||||||
cursor: unset;
|
cursor: unset;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@ -133,14 +133,13 @@
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 2px 5px;
|
padding: 10px 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
opacity: calc((2 * var(--cz)) / 150 - 0.5);
|
opacity: var(--input-opacity);
|
||||||
opacity: calc((var(--cz) - 10) / 20);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.zoom-small) .content {
|
:global(.zoom-small) .content {
|
||||||
@ -150,14 +149,11 @@
|
|||||||
.input {
|
.input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 2px;
|
border-radius: 3px;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
padding: 2px 2px;
|
padding: 10px;
|
||||||
background: #111;
|
background: #111;
|
||||||
}
|
background: var(--background-color-lighter);
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -172,7 +168,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
svg path {
|
svg path {
|
||||||
transition: 0.2s;
|
transition:
|
||||||
|
d 0.3s ease,
|
||||||
|
fill 0.3s ease;
|
||||||
fill: var(--background-color);
|
fill: var(--background-color);
|
||||||
stroke: var(--stroke);
|
stroke: var(--stroke);
|
||||||
stroke-width: var(--stroke-width);
|
stroke-width: var(--stroke-width);
|
||||||
|
@ -4,13 +4,10 @@ varying vec2 vUv;
|
|||||||
|
|
||||||
const float PI = 3.14159265359;
|
const float PI = 3.14159265359;
|
||||||
|
|
||||||
uniform float width;
|
uniform vec2 dimensions;
|
||||||
uniform float height;
|
uniform vec3 camPos;
|
||||||
uniform float cx;
|
uniform vec2 zoomLimits;
|
||||||
uniform float cy;
|
uniform vec3 backgroundColor;
|
||||||
uniform float cz;
|
|
||||||
uniform float minZ;
|
|
||||||
uniform float maxZ;
|
|
||||||
|
|
||||||
float grid(float x, float y, float divisions, float thickness) {
|
float grid(float x, float y, float divisions, float thickness) {
|
||||||
x = fract(x * divisions);
|
x = fract(x * divisions);
|
||||||
@ -47,6 +44,17 @@ float lerp(float a, float b,float t) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
|
|
||||||
|
float cx = camPos.x;
|
||||||
|
float cy = camPos.y;
|
||||||
|
float cz = camPos.z;
|
||||||
|
|
||||||
|
float width = dimensions.x;
|
||||||
|
float height = dimensions.y;
|
||||||
|
|
||||||
|
float minZ = zoomLimits.x;
|
||||||
|
float maxZ = zoomLimits.y;
|
||||||
|
|
||||||
float divisions = 0.1/cz;
|
float divisions = 0.1/cz;
|
||||||
float thickness = 0.05/cz;
|
float thickness = 0.05/cz;
|
||||||
float delta = 0.1 / 2.0;
|
float delta = 0.1 / 2.0;
|
||||||
@ -84,7 +92,7 @@ void main(void) {
|
|||||||
float c = mix(large, small, min(nz*2.0+0.05, 1.0));
|
float c = mix(large, small, min(nz*2.0+0.05, 1.0));
|
||||||
c = mix(c, xsmall, max(min((nz-0.3)/0.7, 1.0), 0.0));
|
c = mix(c, xsmall, max(min((nz-0.3)/0.7, 1.0), 0.0));
|
||||||
|
|
||||||
//c = xsmall;
|
vec3 color = mix(backgroundColor, vec3(1.0), c*0.5);
|
||||||
|
|
||||||
gl_FragColor = vec4(c, c, c, 1.0);
|
gl_FragColor = vec4(color, 1.0);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { T } from "@threlte/core";
|
import { T } from "@threlte/core";
|
||||||
import { onMount } from "svelte";
|
|
||||||
|
|
||||||
import BackgroundVert from "./Background.vert";
|
import BackgroundVert from "./Background.vert";
|
||||||
import BackgroundFrag from "./Background.frag";
|
import BackgroundFrag from "./Background.frag";
|
||||||
|
import { Color } from "three";
|
||||||
|
import { colors } from "../graph/stores";
|
||||||
|
|
||||||
export let minZoom = 4;
|
export let minZoom = 4;
|
||||||
export let maxZoom = 150;
|
export let maxZoom = 150;
|
||||||
@ -34,33 +35,23 @@
|
|||||||
vertexShader={BackgroundVert}
|
vertexShader={BackgroundVert}
|
||||||
fragmentShader={BackgroundFrag}
|
fragmentShader={BackgroundFrag}
|
||||||
uniforms={{
|
uniforms={{
|
||||||
cx: {
|
camPos: {
|
||||||
value: 0,
|
value: [0, 1, 0],
|
||||||
},
|
},
|
||||||
cy: {
|
backgroundColor: {
|
||||||
value: 0,
|
value: [0, 0, 0],
|
||||||
},
|
},
|
||||||
cz: {
|
zoomLimits: {
|
||||||
value: 30,
|
value: [2, 50],
|
||||||
},
|
},
|
||||||
minZ: {
|
dimensions: {
|
||||||
value: minZoom,
|
value: [100, 100],
|
||||||
},
|
|
||||||
maxZ: {
|
|
||||||
value: maxZoom,
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
value: 100,
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
value: 100,
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
uniforms.cx.value={cameraPosition[0]}
|
uniforms.camPos.value={cameraPosition}
|
||||||
uniforms.cy.value={cameraPosition[1]}
|
uniforms.backgroundColor.value={$colors.backgroundColorDarker}
|
||||||
uniforms.cz.value={cameraPosition[2]}
|
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
||||||
uniforms.width.value={width}
|
uniforms.dimensions.value={[width, height]}
|
||||||
uniforms.height.value={height}
|
|
||||||
/>
|
/>
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
</T.Group>
|
</T.Group>
|
||||||
|
@ -22,6 +22,10 @@
|
|||||||
|
|
||||||
let mesh: Mesh;
|
let mesh: Mesh;
|
||||||
|
|
||||||
|
import { colors } from "../graph/stores";
|
||||||
|
|
||||||
|
$: color = $colors.backgroundColorLighter;
|
||||||
|
|
||||||
export const update = function (force = false) {
|
export const update = function (force = false) {
|
||||||
if (!force) {
|
if (!force) {
|
||||||
const new_x = from.x + to.x;
|
const new_x = from.x + to.x;
|
||||||
@ -64,8 +68,8 @@
|
|||||||
position.y={0.8}
|
position.y={0.8}
|
||||||
rotation.x={-Math.PI / 2}
|
rotation.x={-Math.PI / 2}
|
||||||
>
|
>
|
||||||
<T.CircleGeometry args={[0.2, 16]} />
|
<T.CircleGeometry args={[0.3, 16]} />
|
||||||
<T.MeshBasicMaterial color={0x555555} />
|
<T.MeshBasicMaterial {color} />
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
|
|
||||||
<T.Mesh
|
<T.Mesh
|
||||||
@ -74,11 +78,11 @@
|
|||||||
position.y={0.8}
|
position.y={0.8}
|
||||||
rotation.x={-Math.PI / 2}
|
rotation.x={-Math.PI / 2}
|
||||||
>
|
>
|
||||||
<T.CircleGeometry args={[0.2, 16]} />
|
<T.CircleGeometry args={[0.3, 16]} />
|
||||||
<T.MeshBasicMaterial color={0x555555} />
|
<T.MeshBasicMaterial {color} />
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
|
|
||||||
<T.Mesh position.y={0.5} bind:ref={mesh}>
|
<T.Mesh position.y={0.5} bind:ref={mesh}>
|
||||||
<MeshLineGeometry {points} />
|
<MeshLineGeometry {points} />
|
||||||
<MeshLineMaterial width={2} attenuate={false} color={0x555555} />
|
<MeshLineMaterial width={2} attenuate={false} {color} />
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
selectedNodes,
|
selectedNodes,
|
||||||
} from "./stores";
|
} from "./stores";
|
||||||
import BoxSelection from "../BoxSelection.svelte";
|
import BoxSelection from "../BoxSelection.svelte";
|
||||||
|
import type { OrbitControls } from "three/examples/jsm/Addons.js";
|
||||||
|
|
||||||
export let graph: GraphManager;
|
export let graph: GraphManager;
|
||||||
setContext("graphManager", graph);
|
setContext("graphManager", graph);
|
||||||
@ -27,8 +28,9 @@
|
|||||||
const edges = graph.edges;
|
const edges = graph.edges;
|
||||||
|
|
||||||
let camera: OrthographicCamera;
|
let camera: OrthographicCamera;
|
||||||
const minZoom = 4;
|
let controls: OrbitControls;
|
||||||
const maxZoom = 100;
|
const minZoom = 2;
|
||||||
|
const maxZoom = 40;
|
||||||
let mousePosition = [0, 0];
|
let mousePosition = [0, 0];
|
||||||
let mouseDown: null | [number, number] = null;
|
let mouseDown: null | [number, number] = null;
|
||||||
let boxSelection = false;
|
let boxSelection = false;
|
||||||
@ -80,16 +82,16 @@
|
|||||||
}
|
}
|
||||||
const node = graph.getNodeType(nodeTypeId);
|
const node = graph.getNodeType(nodeTypeId);
|
||||||
if (!node?.inputs) {
|
if (!node?.inputs) {
|
||||||
return 2.5;
|
return 5;
|
||||||
}
|
}
|
||||||
const height = 2.5 + 5 * Object.keys(node.inputs).length;
|
const height = 5 + 10 * Object.keys(node.inputs).length;
|
||||||
nodeHeightCache[nodeTypeId] = height;
|
nodeHeightCache[nodeTypeId] = height;
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
setContext("isNodeInView", (node: NodeType) => {
|
setContext("isNodeInView", (node: NodeType) => {
|
||||||
const height = getNodeHeight(node.type);
|
const height = getNodeHeight(node.type);
|
||||||
const width = 10;
|
const width = 20;
|
||||||
return (
|
return (
|
||||||
// check x-axis
|
// check x-axis
|
||||||
node.position.x > cameraBounds[0] - width &&
|
node.position.x > cameraBounds[0] - width &&
|
||||||
@ -159,14 +161,14 @@
|
|||||||
): [number, number] {
|
): [number, number] {
|
||||||
if (typeof index === "number") {
|
if (typeof index === "number") {
|
||||||
return [
|
return [
|
||||||
(node?.tmp?.x ?? node.position.x) + 10,
|
(node?.tmp?.x ?? node.position.x) + 20,
|
||||||
(node?.tmp?.y ?? node.position.y) + 1.25 + 5 * index,
|
(node?.tmp?.y ?? node.position.y) + 2.5 + 10 * index,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index);
|
const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index);
|
||||||
return [
|
return [
|
||||||
node?.tmp?.x ?? node.position.x,
|
node?.tmp?.x ?? node.position.x,
|
||||||
(node?.tmp?.y ?? node.position.y) + 5 + 5 * _index,
|
(node?.tmp?.y ?? node.position.y) + 10 + 10 * _index,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,71 +211,74 @@
|
|||||||
|
|
||||||
// handle box selection
|
// handle box selection
|
||||||
if (boxSelection) {
|
if (boxSelection) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
const mouseD = projectScreenToWorld(mouseDown[0], mouseDown[1]);
|
const mouseD = projectScreenToWorld(mouseDown[0], mouseDown[1]);
|
||||||
const x1 = Math.min(mouseD[0], mousePosition[0]);
|
const x1 = Math.min(mouseD[0], mousePosition[0]);
|
||||||
const x2 = Math.max(mouseD[0], mousePosition[0]);
|
const x2 = Math.max(mouseD[0], mousePosition[0]);
|
||||||
const y1 = Math.min(mouseD[1], mousePosition[1]);
|
const y1 = Math.min(mouseD[1], mousePosition[1]);
|
||||||
const y2 = Math.max(mouseD[1], mousePosition[1]);
|
const y2 = Math.max(mouseD[1], mousePosition[1]);
|
||||||
const _selectedNodes = $selectedNodes || new Set();
|
|
||||||
for (const node of $nodes.values()) {
|
for (const node of $nodes.values()) {
|
||||||
if (!node?.tmp) continue;
|
if (!node?.tmp) continue;
|
||||||
const x = node.position.x;
|
const x = node.position.x;
|
||||||
const y = node.position.y;
|
const y = node.position.y;
|
||||||
if (x > x1 && x < x2 && y > y1 && y < y2) {
|
const height = getNodeHeight(node.type);
|
||||||
_selectedNodes.add(node.id);
|
if (x > x1 - 20 && x < x2 && y > y1 - height && y < y2) {
|
||||||
|
$selectedNodes?.add(node.id);
|
||||||
} else {
|
} else {
|
||||||
_selectedNodes?.delete(node.id);
|
$selectedNodes?.delete(node.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$selectedNodes = _selectedNodes;
|
$selectedNodes = $selectedNodes;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// here we are handling dragging of nodes
|
// here we are handling dragging of nodes
|
||||||
if ($activeNodeId === -1) return;
|
if ($activeNodeId !== -1) {
|
||||||
|
const node = graph.getNode($activeNodeId);
|
||||||
|
if (!node || event.buttons !== 1) return;
|
||||||
|
|
||||||
const node = graph.getNode($activeNodeId);
|
node.tmp = node.tmp || {};
|
||||||
if (!node || event.buttons !== 1) return;
|
|
||||||
|
|
||||||
node.tmp = node.tmp || {};
|
const oldX = node.tmp.downX || 0;
|
||||||
|
const oldY = node.tmp.downY || 0;
|
||||||
|
|
||||||
const oldX = node.tmp.downX || 0;
|
let newX = oldX + (event.clientX - mouseDown[0]) / cameraPosition[2];
|
||||||
const oldY = node.tmp.downY || 0;
|
let newY = oldY + (event.clientY - mouseDown[1]) / cameraPosition[2];
|
||||||
|
|
||||||
let newX = oldX + (event.clientX - mouseDown[0]) / cameraPosition[2];
|
if (event.ctrlKey) {
|
||||||
let newY = oldY + (event.clientY - mouseDown[1]) / cameraPosition[2];
|
const snapLevel = getSnapLevel();
|
||||||
|
newX = snapToGrid(newX, 5 / snapLevel);
|
||||||
if (event.ctrlKey) {
|
newY = snapToGrid(newY, 5 / snapLevel);
|
||||||
const snapLevel = getSnapLevel();
|
|
||||||
newX = snapToGrid(newX, 5 / snapLevel);
|
|
||||||
newY = snapToGrid(newY, 5 / snapLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!node.tmp.isMoving) {
|
|
||||||
const dist = Math.sqrt((oldX - newX) ** 2 + (oldY - newY) ** 2);
|
|
||||||
if (dist > 0.2) {
|
|
||||||
node.tmp.isMoving = true;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const vecX = oldX - newX;
|
if (!node.tmp.isMoving) {
|
||||||
const vecY = oldY - newY;
|
const dist = Math.sqrt((oldX - newX) ** 2 + (oldY - newY) ** 2);
|
||||||
|
if (dist > 0.2) {
|
||||||
if ($selectedNodes?.size) {
|
node.tmp.isMoving = true;
|
||||||
for (const nodeId of $selectedNodes) {
|
}
|
||||||
const n = graph.getNode(nodeId);
|
|
||||||
if (!n?.tmp) continue;
|
|
||||||
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
|
|
||||||
n.tmp.y = (n?.tmp?.downY || 0) - vecY;
|
|
||||||
updateNodePosition(n);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const vecX = oldX - newX;
|
||||||
|
const vecY = oldY - newY;
|
||||||
|
|
||||||
|
if ($selectedNodes?.size) {
|
||||||
|
for (const nodeId of $selectedNodes) {
|
||||||
|
const n = graph.getNode(nodeId);
|
||||||
|
if (!n?.tmp) continue;
|
||||||
|
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
|
||||||
|
n.tmp.y = (n?.tmp?.downY || 0) - vecY;
|
||||||
|
updateNodePosition(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.tmp.x = newX;
|
||||||
|
node.tmp.y = newY;
|
||||||
|
|
||||||
|
updateNodePosition(node);
|
||||||
|
|
||||||
|
$edges = $edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.tmp.x = newX;
|
|
||||||
node.tmp.y = newY;
|
|
||||||
|
|
||||||
updateNodePosition(node);
|
|
||||||
|
|
||||||
$edges = $edges;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseDown(event: MouseEvent) {
|
function handleMouseDown(event: MouseEvent) {
|
||||||
@ -316,6 +321,7 @@
|
|||||||
}
|
}
|
||||||
} else if (event.ctrlKey) {
|
} else if (event.ctrlKey) {
|
||||||
boxSelection = true;
|
boxSelection = true;
|
||||||
|
controls.enabled = false;
|
||||||
} else {
|
} else {
|
||||||
$activeNodeId = -1;
|
$activeNodeId = -1;
|
||||||
$selectedNodes?.clear();
|
$selectedNodes?.clear();
|
||||||
@ -489,6 +495,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
mouseDown = null;
|
mouseDown = null;
|
||||||
|
controls.enabled = true;
|
||||||
boxSelection = false;
|
boxSelection = false;
|
||||||
$activeSocket = null;
|
$activeSocket = null;
|
||||||
$possibleSockets = [];
|
$possibleSockets = [];
|
||||||
@ -497,18 +504,24 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window
|
<svelte:document
|
||||||
on:mousemove={handleMouseMove}
|
on:mousemove={handleMouseMove}
|
||||||
on:mouseup={handleMouseUp}
|
on:mouseup={handleMouseUp}
|
||||||
on:mousedown={handleMouseDown}
|
on:mousedown={handleMouseDown}
|
||||||
on:keydown={handleKeyDown}
|
on:keydown={handleKeyDown}
|
||||||
bind:innerWidth={width}
|
|
||||||
bind:innerHeight={height}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<svelte:window bind:innerWidth={width} bind:innerHeight={height} />
|
||||||
|
|
||||||
<Debug />
|
<Debug />
|
||||||
|
|
||||||
<Camera bind:camera {maxZoom} {minZoom} bind:position={cameraPosition} />
|
<Camera
|
||||||
|
bind:controls
|
||||||
|
bind:camera
|
||||||
|
{maxZoom}
|
||||||
|
{minZoom}
|
||||||
|
bind:position={cameraPosition}
|
||||||
|
/>
|
||||||
|
|
||||||
<Background {cameraPosition} {maxZoom} {minZoom} {width} {height} />
|
<Background {cameraPosition} {maxZoom} {minZoom} {width} {height} />
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Edge as EdgeType, Node as NodeType, Socket } from "$lib/types";
|
import type { Edge as EdgeType, Node as NodeType } from "$lib/types";
|
||||||
import { HTML } from "@threlte/extras";
|
import { HTML } from "@threlte/extras";
|
||||||
import Edge from "../edges/Edge.svelte";
|
import Edge from "../edges/Edge.svelte";
|
||||||
import Node from "../Node.svelte";
|
import Node from "../Node.svelte";
|
||||||
import { getContext, onMount } from "svelte";
|
import { getContext, onMount } from "svelte";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import { activeSocket } from "./stores";
|
import { activeSocket, colors } from "./stores";
|
||||||
|
|
||||||
export let nodes: Writable<Map<number, NodeType>>;
|
export let nodes: Writable<Map<number, NodeType>>;
|
||||||
export let edges: Writable<EdgeType[]>;
|
export let edges: Writable<EdgeType[]>;
|
||||||
@ -40,6 +40,7 @@
|
|||||||
{@const pos = getEdgePosition(edge)}
|
{@const pos = getEdgePosition(edge)}
|
||||||
{@const [x1, y1, x2, y2] = pos}
|
{@const [x1, y1, x2, y2] = pos}
|
||||||
<Edge
|
<Edge
|
||||||
|
color={$colors.backgroundColorLighter}
|
||||||
from={{
|
from={{
|
||||||
x: x1,
|
x: x1,
|
||||||
y: y1,
|
y: y1,
|
||||||
@ -56,9 +57,9 @@
|
|||||||
role="tree"
|
role="tree"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
class:zoom-small={cameraPosition[2] < 10}
|
class:zoom-small={cameraPosition[2] < 2}
|
||||||
class:hovering-sockets={activeSocket}
|
class:hovering-sockets={activeSocket}
|
||||||
style={`--cz: ${cameraPosition[2]}`}
|
style={`--cz: ${cameraPosition[2]};`}
|
||||||
>
|
>
|
||||||
{#each $nodes.values() as node (node.id)}
|
{#each $nodes.values() as node (node.id)}
|
||||||
<Node {node} inView={cameraPosition && isNodeInView(node)} />
|
<Node {node} inView={cameraPosition && isNodeInView(node)} />
|
||||||
@ -73,5 +74,6 @@
|
|||||||
width: 0px;
|
width: 0px;
|
||||||
height: 0px;
|
height: 0px;
|
||||||
transform: scale(calc(var(--cz) * 0.1));
|
transform: scale(calc(var(--cz) * 0.1));
|
||||||
|
--input-opacity: calc((var(--cz) - 2) / 5);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import type { Node, Socket } from "$lib/types";
|
import { browser } from "$app/environment";
|
||||||
|
import type { Socket } from "$lib/types";
|
||||||
import { writable, type Writable } from "svelte/store";
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
import { Color } from "three";
|
||||||
|
|
||||||
export const activeNodeId: Writable<number> = writable(-1);
|
export const activeNodeId: Writable<number> = writable(-1);
|
||||||
export const selectedNodes: Writable<Set<number> | null> = writable(null);
|
export const selectedNodes: Writable<Set<number> | null> = writable(null);
|
||||||
@ -8,3 +10,41 @@ export const activeSocket: Writable<Socket | null> = writable(null);
|
|||||||
export const hoveredSocket: Writable<Socket | null> = writable(null);
|
export const hoveredSocket: Writable<Socket | null> = writable(null);
|
||||||
export const possibleSockets: Writable<Socket[]> = writable([]);
|
export const possibleSockets: Writable<Socket[]> = writable([]);
|
||||||
export const possibleSocketIds: Writable<Set<string> | null> = writable(null);
|
export const possibleSocketIds: Writable<Set<string> | null> = writable(null);
|
||||||
|
|
||||||
|
|
||||||
|
export const colors = writable({
|
||||||
|
backgroundColorDarker: new Color().setStyle("#101010"),
|
||||||
|
backgroundColor: new Color().setStyle("#151515"),
|
||||||
|
backgroundColorLighter: new Color().setStyle("#202020")
|
||||||
|
});
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
|
||||||
|
const body = document.body;
|
||||||
|
|
||||||
|
function updateColors() {
|
||||||
|
|
||||||
|
const style = getComputedStyle(body);
|
||||||
|
const backgroundColorDarker = style.getPropertyValue("--background-color-darker");
|
||||||
|
const backgroundColor = style.getPropertyValue("--background-color");
|
||||||
|
const backgroundColorLighter = style.getPropertyValue("--background-color-lighter");
|
||||||
|
|
||||||
|
colors.update(col => {
|
||||||
|
col.backgroundColorDarker.setStyle(backgroundColorDarker);
|
||||||
|
col.backgroundColor.setStyle(backgroundColor);
|
||||||
|
col.backgroundColorLighter.setStyle(backgroundColorLighter);
|
||||||
|
return col;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
globalThis["updateColors"] = updateColors;
|
||||||
|
|
||||||
|
body.addEventListener("transitionstart", () => {
|
||||||
|
updateColors();
|
||||||
|
})
|
||||||
|
window.onload = () => {
|
||||||
|
updateColors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
17
frontend/src/lib/elements/Checkbox.story.svelte
Normal file
17
frontend/src/lib/elements/Checkbox.story.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Hst } from "@histoire/plugin-svelte";
|
||||||
|
export let Hst: Hst;
|
||||||
|
import Checkbox from "./Checkbox.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Hst.Story>
|
||||||
|
<div>
|
||||||
|
<Checkbox checked={false} />
|
||||||
|
</div>
|
||||||
|
</Hst.Story>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
58
frontend/src/lib/elements/Checkbox.svelte
Normal file
58
frontend/src/lib/elements/Checkbox.svelte
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let checked: boolean;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input type="checkbox" bind:checked />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input[type="checkbox"] {
|
||||||
|
/* Add if not using autoprefixer */
|
||||||
|
-webkit-appearance: none;
|
||||||
|
/* Remove most all native input styles */
|
||||||
|
appearance: none;
|
||||||
|
/* For iOS < 15 */
|
||||||
|
background-color: var(--form-background);
|
||||||
|
/* Not removed via appearance */
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
font: inherit;
|
||||||
|
color: currentColor;
|
||||||
|
width: 1.15em;
|
||||||
|
height: 1.15em;
|
||||||
|
border: 0.15em solid currentColor;
|
||||||
|
border-radius: 0.15em;
|
||||||
|
transform: translateY(-0.075em);
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]::before {
|
||||||
|
content: "";
|
||||||
|
width: 0.65em;
|
||||||
|
height: 0.65em;
|
||||||
|
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
||||||
|
transform: scale(0);
|
||||||
|
transform-origin: bottom left;
|
||||||
|
transition: 120ms transform ease-in-out;
|
||||||
|
box-shadow: inset 1em 1em var(--form-control-color);
|
||||||
|
/* Windows High Contrast Mode */
|
||||||
|
background-color: CanvasText;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked::before {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:focus {
|
||||||
|
outline: max(2px, 0.15em) solid currentColor;
|
||||||
|
outline-offset: max(2px, 0.15em);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:disabled {
|
||||||
|
--form-control-color: var(--form-control-disabled);
|
||||||
|
|
||||||
|
color: var(--form-control-disabled);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
0
frontend/src/lib/elements/Float.svelte
Normal file
0
frontend/src/lib/elements/Float.svelte
Normal file
0
frontend/src/lib/elements/Integer.svelte
Normal file
0
frontend/src/lib/elements/Integer.svelte
Normal file
0
frontend/src/lib/elements/Select.svelte
Normal file
0
frontend/src/lib/elements/Select.svelte
Normal file
@ -319,8 +319,8 @@ export class GraphManager {
|
|||||||
visible: false,
|
visible: false,
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
x: x * 15,
|
x: x * 30,
|
||||||
y: y * 20,
|
y: y * 40,
|
||||||
},
|
},
|
||||||
props: i == 0 ? { value: 0 } : {},
|
props: i == 0 ? { value: 0 } : {},
|
||||||
type: i == 0 ? "input/float" : "math",
|
type: i == 0 ? "input/float" : "math",
|
||||||
@ -335,8 +335,8 @@ export class GraphManager {
|
|||||||
visible: false,
|
visible: false,
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
x: width * 15,
|
x: width * 30,
|
||||||
y: (height - 1) * 20,
|
y: (height - 1) * 40,
|
||||||
},
|
},
|
||||||
type: "output",
|
type: "output",
|
||||||
props: {},
|
props: {},
|
||||||
|
@ -61,4 +61,12 @@ export function createNodePath({
|
|||||||
: ` H0`
|
: ` H0`
|
||||||
}
|
}
|
||||||
Z`.replace(/\s+/g, " ");
|
Z`.replace(/\s+/g, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const debounce = (fn: Function, ms = 300) => {
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout>;
|
||||||
|
return function (this: any, ...args: any[]) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(() => fn.apply(this, args), ms);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="canvas-wrapper">
|
<div class="canvas-wrapper">
|
||||||
<Canvas shadows={false} renderMode="on-demand" autoRender={true}>
|
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
|
||||||
<!-- <PerfMonitor /> -->
|
<!-- <PerfMonitor /> -->
|
||||||
<Graph {graph} bind:debug />
|
<Graph {graph} bind:debug />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
@ -23,28 +23,6 @@
|
|||||||
:root {
|
:root {
|
||||||
font-family: 'Fira Code', monospace;
|
font-family: 'Fira Code', monospace;
|
||||||
|
|
||||||
/* Colors */
|
|
||||||
--primary-color: #007bff;
|
|
||||||
/* Primary brand color */
|
|
||||||
--secondary-color: #6c757d;
|
|
||||||
/* Secondary color */
|
|
||||||
--background-color: #151515;
|
|
||||||
--background-color-lightest: #ffffff;
|
|
||||||
--background-color-lighter: #202020;
|
|
||||||
--background-color-dark: #dae0e5;
|
|
||||||
--background-color-darkest: #c8d1d7;
|
|
||||||
/* Background color */
|
|
||||||
--text-color: #aeaeae;
|
|
||||||
/* Text color */
|
|
||||||
--accent-color: #ffc107;
|
|
||||||
/* Accent color */
|
|
||||||
|
|
||||||
/* Typography */
|
|
||||||
--font-family: Arial, sans-serif;
|
|
||||||
--font-size-base: 16px;
|
|
||||||
/* Base font size */
|
|
||||||
--line-height-base: 1.5;
|
|
||||||
/* Base line height */
|
|
||||||
|
|
||||||
/* Spacing */
|
/* Spacing */
|
||||||
--spacing-xs: 4px;
|
--spacing-xs: 4px;
|
||||||
@ -62,4 +40,23 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
/* Secondary color */
|
||||||
|
--secondary-color: #6c757d;
|
||||||
|
/* Background color */
|
||||||
|
--background-color-lighter: #202020;
|
||||||
|
--background-color: #151515;
|
||||||
|
--background-color-darker: #101010;
|
||||||
|
--text-color: #aeaeae;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.theme-catppuccin {
|
||||||
|
--text-color: #CDD6F4;
|
||||||
|
--background-color-lighter: #313244;
|
||||||
|
--background-color: #1E1E2E;
|
||||||
|
--background-color-darker: #11111b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* canvas { */
|
||||||
|
/* display: none !important; */
|
||||||
|
/* } */
|
||||||
|
Loading…
Reference in New Issue
Block a user