feat: init frontend

This commit is contained in:
2024-03-06 14:01:07 +01:00
parent b54370bec0
commit f920a34a0d
112 changed files with 56743 additions and 1 deletions

13
frontend/src/app.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

12
frontend/src/app.html Normal file
View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/svelte.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -0,0 +1,36 @@
<script lang="ts">
import { Canvas } from "@threlte/core";
import Scene from "./Scene.svelte";
import type { Graph } from "$lib/types";
const graph: Graph = {
edges: [],
nodes: [],
};
for (let i = 0; i < 40; i++) {
const x = i % 20;
const y = Math.floor(i / 20);
graph.nodes.push({
id: `${i.toString()}`,
tmp: {
visible: false,
},
position: {
x: x * 7.5,
y: y * 5,
},
type: "test",
});
graph.edges.push({
from: i.toString(),
to: (i + 1).toString(),
});
}
</script>
<Canvas shadows={false}>
<Scene {graph} />
</Canvas>

View File

@ -0,0 +1,61 @@
precision highp float;
varying vec2 vUv;
const float PI = 3.14159265359;
uniform float width;
uniform float height;
uniform float cx;
uniform float cy;
uniform float cz;
uniform float minZ;
uniform float maxZ;
float grid(float x, float y, float divisions, float thickness) {
x = fract(x * divisions);
x = min(x, 1.0 - x);
float xdelta = fwidth(x);
x = smoothstep(x - xdelta, x + xdelta, thickness);
y = fract(y * divisions);
y = min(y, 1.0 - y);
float ydelta = fwidth(y);
y = smoothstep(y - ydelta, y + ydelta, thickness);
return clamp(x + y, 0.0, 1.0);
}
float circle_grid(float x, float y, float divisions, float circleRadius) {
float gridX = mod(x + divisions/2.0, divisions) - divisions / 2.0;
float gridY = mod(y + divisions/2.0, divisions) - divisions / 2.0;
// Calculate the distance from the center of the grid
float gridDistance = length(vec2(gridX, gridY));
// Use smoothstep to create a smooth transition at the edges of the circle
float circle = 1.0 - smoothstep(circleRadius - 0.5, circleRadius + 0.5, gridDistance);
return circle;
}
void main(void) {
float divisions = 0.1/cz;
float thickness = 0.05/cz;
float delta = 0.1 / 2.0;
float ux = (vUv.x-0.5) * width + cx*cz;
float uy = (vUv.y-0.5) * height - cy*cz;
float c1 = grid(ux, uy, divisions, thickness) * 0.1;
float c2 = grid(ux, uy, divisions*2.0, thickness) * 0.1;
float c = max(c1, c2);
float s1 = circle_grid(ux, uy, cz*10.0, 2.0) * 0.2;
c = max(c, s1);
gl_FragColor = vec4(c, c, c, 1.0);
}

View File

@ -0,0 +1,15 @@
varying vec2 vUv;
varying vec3 vPosition;
void main() {
vUv = uv;
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;
}

View File

@ -0,0 +1,52 @@
<script lang="ts">
import type { Node } from "$lib/types";
import { T } from "@threlte/core";
import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
import { CubicBezierCurve, Vector2, Vector3 } from "three";
export let from: Node;
export let to: Node;
let samples = 20;
console.log("edge");
const curve = new CubicBezierCurve(
new Vector2(from.position.x + 20, from.position.y),
new Vector2(from.position.x + 2, from.position.y),
new Vector2(to.position.x - 2, to.position.y),
new Vector2(to.position.x, to.position.y),
);
let points: Vector3[] = [];
let last_from_x = 0;
let last_from_y = 0;
function update(force = false) {
if (!force) {
const new_x = from.position.x + to.position.x;
const new_y = from.position.y + to.position.y;
if (last_from_x === new_x && last_from_y === new_y) {
return;
}
last_from_x = new_x;
last_from_y = new_y;
}
curve.v0.set(from.position.x + 5, from.position.y + 1.25);
curve.v1.set(from.position.x + 6, from.position.y + 1.25);
curve.v2.set(to.position.x - 1, to.position.y + 1.25);
curve.v3.set(to.position.x, to.position.y + 1.25);
points = curve.getPoints(samples).map((p) => new Vector3(p.x, 0, p.y));
}
update();
$: if (from.position || to.position) {
update();
}
</script>
<T.Mesh>
<MeshLineGeometry {points} />
<MeshLineMaterial width={1} attenuate={false} color={0xffffff} />
</T.Mesh>

View File

@ -0,0 +1,255 @@
<script lang="ts">
import type { Graph, Node } from "$lib/types";
import Edge from "./Edge.svelte";
import { T, useTask } from "@threlte/core";
import { settings } from "$lib/stores/settings";
import type { OrthographicCamera } from "three";
import { HTML, OrbitControls } from "@threlte/extras";
import { onMount } from "svelte";
import BackgroundVert from "./Background.vert";
import BackgroundFrag from "./Background.frag";
import { max } from "three/examples/jsm/nodes/Nodes.js";
export let camera: OrthographicCamera;
export let graph: Graph;
let cx = 0;
let cy = 0;
let cz = 30;
let bw = 2;
let bh = 2;
const minZoom = 4;
const maxZoom = 150;
let width = globalThis?.innerWidth || 100;
let height = globalThis?.innerHeight || 100;
let mouseX = 0;
let mouseY = 0;
let mouseDown = false;
let mouseDownX = 0;
let mouseDownY = 0;
let activeNodeId: string;
function handleKeyDown(event: KeyboardEvent) {
if (event.key === "a") {
$settings.useHtml = !$settings.useHtml;
}
}
function snapToGrid(value: number) {
return Math.round(value / 2.5) * 2.5;
}
function handleMouseMove(event: MouseEvent) {
cx = camera.position.x || 0;
cy = camera.position.z || 0;
cz = camera.zoom || 30;
mouseX = cx + (event.clientX - width / 2) / cz;
mouseY = cy + (event.clientY - height / 2) / cz;
if (activeNodeId && mouseDown) {
graph.nodes = graph.nodes.map((node) => {
if (node.id === activeNodeId) {
node.position.x =
(node?.tmp?.downX || 0) + (event.clientX - mouseDownX) / cz;
node.position.y =
(node?.tmp?.downY || 0) + (event.clientY - mouseDownY) / cz;
if (event.ctrlKey) {
node.position.x = snapToGrid(node.position.x);
node.position.y = snapToGrid(node.position.y);
}
edges = [...edges];
}
return node;
});
}
}
let edges: [Node, Node][] = [];
function calculateEdges() {
edges = graph.edges
.map((edge) => {
const from = graph.nodes.find((node) => node.id === edge.from);
const to = graph.nodes.find((node) => node.id === edge.to);
if (!from || !to) return;
return [from, to] as const;
})
.filter(Boolean) as unknown as [Node, Node][];
}
calculateEdges();
function handleMouseDown(ev: MouseEvent) {
activeNodeId = ev?.target?.dataset?.nodeId;
mouseDown = true;
mouseDownX = ev.clientX;
mouseDownY = ev.clientY;
const node = graph.nodes.find((node) => node.id === activeNodeId);
if (!node) return;
node.tmp = node.tmp || {};
node.tmp.downX = node.position.x;
node.tmp.downY = node.position.y;
}
function handleMouseUp() {
mouseDown = false;
}
function updateCameraProps() {
cx = camera.position.x || 0;
cy = camera.position.z || 0;
cz = camera.zoom || 30;
width = window.innerWidth;
height = window.innerHeight;
bw = width / cz;
bh = height / cz;
}
onMount(() => {
updateCameraProps();
});
</script>
<svelte:window
on:keydown={handleKeyDown}
on:mousemove={handleMouseMove}
on:mouseup={handleMouseUp}
on:wheel={updateCameraProps}
on:resize={updateCameraProps}
/>
{#if true}
<T.Group position.x={mouseX} position.z={mouseY}>
<T.Mesh rotation.x={-Math.PI / 2} position.y={0.2}>
<T.CircleGeometry args={[0.5, 16]} />
<T.MeshBasicMaterial color="green" />
</T.Mesh>
</T.Group>
{/if}
<T.Group position.x={cx} position.z={cy} position.y={-1.0}>
<T.Mesh rotation.x={-Math.PI / 2} position.y={0.2} scale.x={bw} scale.y={bh}>
<T.PlaneGeometry args={[1, 1]} />
<T.ShaderMaterial
transparent
vertexShader={BackgroundVert}
fragmentShader={BackgroundFrag}
uniforms={{
cx: {
value: 0,
},
cy: {
value: 0,
},
cz: {
value: 30,
},
minz: {
value: minZoom,
},
maxz: {
value: maxZoom,
},
height: {
value: 100,
},
width: {
value: 100,
},
}}
uniforms.cx.value={cx}
uniforms.cy.value={cy}
uniforms.cz.value={cz}
uniforms.width.value={width}
uniforms.height.value={height}
/>
</T.Mesh>
</T.Group>
<T.OrthographicCamera
bind:ref={camera}
makeDefault
position={[0, 1, 0]}
zoom={30}
>
<OrbitControls
enableZoom={true}
target.y={0}
rotateSpeed={0}
minPolarAngle={0}
maxPolarAngle={0}
enablePan={true}
zoomToCursor
{maxZoom}
{minZoom}
/>
</T.OrthographicCamera>
{#each edges as edge}
<Edge from={edge[0]} to={edge[1]} />
{/each}
<HTML transform={false}>
<div class="wrapper" style={`--cz: ${cz}`} on:mousedown={handleMouseDown}>
{#each graph.nodes as node}
<div
class="node"
data-node-id={node.id}
style={`--nx:${node.position.x * 10}px;
--ny: ${node.position.y * 10}px`}
>
{node.id}
</div>
{/each}
</div>
</HTML>
<style>
:global(body) {
overflow: hidden;
}
.wrapper {
position: absolute;
z-index: 100;
width: 0px;
height: 0px;
transform: scale(calc(var(--cz) * 0.1));
}
.node {
position: absolute;
border-radius: 1px;
user-select: none !important;
cursor: pointer;
width: 50px;
height: 25px;
background: linear-gradient(-11.1grad, #000 0%, #0b0b0b 100%);
color: white;
transform: translate(var(--nx), var(--ny));
z-index: 1;
box-shadow: 0px 0px 0px calc(15px / var(--cz)) rgba(255, 255, 255, 0.3);
font-weight: 300;
font-size: 0.5em;
}
.node::after {
/* content: ""; */
position: absolute;
width: 100%;
height: 100%;
background: white;
border-radius: 2px;
transform: scale(1.01);
top: 0;
left: 0;
z-index: -1;
}
</style>

View File

@ -0,0 +1,11 @@
<script context="module" lang="ts">
export type Props = {
value: number;
};
</script>
<script lang="ts">
export let props = { value: 0 };
</script>
<p>output</p>

View File

@ -0,0 +1,11 @@
<script context="module" lang="ts">
export type Props = {
value: number;
};
</script>
<script lang="ts">
export let props = { value: 0 };
</script>
<input type="number" bind:value={props.value} />

View File

@ -0,0 +1,6 @@
import Test from './Test.svelte';
import Output from './Output.svelte';
export const nodes = {
test: Test,
output: Output,
} as const;

View File

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

View File

@ -0,0 +1,5 @@
import { writable } from "svelte/store";
export const settings = writable({
useHtml: false
});

34
frontend/src/lib/types.ts Normal file
View File

@ -0,0 +1,34 @@
import { nodes } from "$lib/components/nodes"
export type Node = {
id: string;
type: keyof typeof nodes;
props?: Record<string, any>,
tmp?: {
downX?: number;
downY?: number;
visible?: boolean;
},
meta?: {
title?: string;
lastModified?: string;
},
position: {
x: number;
y: number;
}
}
export type Edge = {
from: string;
to: string;
}
export type Graph = {
meta?: {
title?: string;
lastModified?: string;
},
nodes: Node[];
edges: Edge[];
}

View File

@ -0,0 +1,5 @@
<script lang="ts">
import "./app.css";
</script>
<slot />

View File

@ -0,0 +1,2 @@
export const prerender = true
export const ssr = false

View File

@ -0,0 +1,47 @@
<script lang="ts">
import App from "$lib/components/App.svelte";
import { invoke } from "@tauri-apps/api/core";
import { onMount } from "svelte";
onMount(async () => {
try {
const res = await invoke("greet", { name: "Dude" });
console.log({ res });
} catch (error) {
console.log(error);
}
try {
const res2 = await invoke("run_nodes", {});
console.log({ res2 });
} catch (error) {
console.log(error);
}
});
</script>
<div>
<App />
</div>
<style>
div {
height: 100vh;
}
:global(html) {
background: rgb(13, 19, 32);
background: linear-gradient(
180deg,
rgba(13, 19, 32, 1) 0%,
rgba(8, 12, 21, 1) 100%
);
}
:global(body) {
margin: 0;
position: relative;
width: 100vw;
height: 100vh;
}
</style>

View File

@ -0,0 +1,25 @@
/* fira-code-300 - latin */
@font-face {
font-display: swap;
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Fira Code';
font-style: normal;
font-weight: 300;
src: url('/fonts/fira-code-v22-latin-300.woff2') format('woff2');
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* fira-code-600 - latin */
@font-face {
font-display: swap;
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Fira Code';
font-style: normal;
font-weight: 600;
src: url('/fonts/fira-code-v22-latin-600.woff2') format('woff2');
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
:root {
font-family: 'Fira Code', monospace;
}