feat: project manager #21
@@ -1,4 +1,5 @@
|
|||||||
import type { Box } from '@nodarium/types';
|
import type { Box } from '@nodarium/types';
|
||||||
|
import type { Color } from 'three';
|
||||||
import { Vector3 } from 'three/src/math/Vector3.js';
|
import { Vector3 } from 'three/src/math/Vector3.js';
|
||||||
import Component from './Debug.svelte';
|
import Component from './Debug.svelte';
|
||||||
import { lines, points, rects } from './store';
|
import { lines, points, rects } from './store';
|
||||||
@@ -11,7 +12,6 @@ export function debugPosition(x: number, y: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function debugRect(rect: Box) {
|
export function debugRect(rect: Box) {
|
||||||
console.log(rect);
|
|
||||||
rects.update((r) => {
|
rects.update((r) => {
|
||||||
r.push(rect);
|
r.push(rect);
|
||||||
return r;
|
return r;
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
const serialized = {
|
const serialized = {
|
||||||
id: this.graph.id,
|
id: this.graph.id,
|
||||||
settings: $state.snapshot(this.settings),
|
settings: $state.snapshot(this.settings),
|
||||||
meta: this.graph.meta,
|
meta: $state.snapshot(this.graph.meta),
|
||||||
nodes,
|
nodes,
|
||||||
edges
|
edges
|
||||||
};
|
};
|
||||||
@@ -274,7 +274,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const edges = graph.edges.map((edge) => {
|
this.edges = graph.edges.map((edge) => {
|
||||||
const from = nodes.get(edge[0]);
|
const from = nodes.get(edge[0]);
|
||||||
const to = nodes.get(edge[2]);
|
const to = nodes.get(edge[2]);
|
||||||
if (!from || !to) {
|
if (!from || !to) {
|
||||||
@@ -287,8 +287,6 @@ export class GraphManager extends EventEmitter<{
|
|||||||
return [from, edge[1], to, edge[3]] as Edge;
|
return [from, edge[1], to, edge[3]] as Edge;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.edges = [...edges];
|
|
||||||
|
|
||||||
this.nodes.clear();
|
this.nodes.clear();
|
||||||
for (const [id, node] of nodes) {
|
for (const [id, node] of nodes) {
|
||||||
this.nodes.set(id, node);
|
this.nodes.set(id, node);
|
||||||
@@ -305,7 +303,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
this.status = 'loading';
|
this.status = 'loading';
|
||||||
this.id = graph.id;
|
this.id = graph.id;
|
||||||
|
|
||||||
logger.info('loading graph', graph);
|
logger.info('loading graph', { nodes: graph.nodes, edges: graph.edges, id: graph.id });
|
||||||
|
|
||||||
const nodeIds = Array.from(new Set([...graph.nodes.map((n) => n.type)]));
|
const nodeIds = Array.from(new Set([...graph.nodes.map((n) => n.type)]));
|
||||||
await this.registry.load(nodeIds);
|
await this.registry.load(nodeIds);
|
||||||
@@ -645,6 +643,13 @@ export class GraphManager extends EventEmitter<{
|
|||||||
if (this.currentUndoGroup) return;
|
if (this.currentUndoGroup) return;
|
||||||
const state = this.serialize();
|
const state = this.serialize();
|
||||||
this.history.save(state);
|
this.history.save(state);
|
||||||
|
|
||||||
|
// This is some stupid race condition where the graph-manager emits a save event
|
||||||
|
// when the graph is not fully loaded
|
||||||
|
if (this.nodes.size === 0 && this.edges.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.emit('save', state);
|
this.emit('save', state);
|
||||||
logger.log('saving graphs', state);
|
logger.log('saving graphs', state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,12 +70,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (settingTypes && settings) {
|
|
||||||
manager.setSettings(settings);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
manager.on("settings", (_settings) => {
|
manager.on("settings", (_settings) => {
|
||||||
settingTypes = { ...settingTypes, ..._settings.types };
|
settingTypes = { ...settingTypes, ..._settings.types };
|
||||||
settings = _settings.values;
|
settings = _settings.values;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import path from 'path';
|
|||||||
|
|
||||||
export async function getWasm(id: `${string}/${string}/${string}`) {
|
export async function getWasm(id: `${string}/${string}/${string}`) {
|
||||||
const filePath = path.resolve(`./static/nodes/${id}`);
|
const filePath = path.resolve(`./static/nodes/${id}`);
|
||||||
console.log({ filePath });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fs.access(filePath);
|
await fs.access(filePath);
|
||||||
|
|||||||
@@ -1,18 +1,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Graph } from "$lib/types";
|
import type { Graph } from "$lib/types";
|
||||||
import { defaultPlant } from "$lib/graph-templates";
|
import { defaultPlant, plant, lottaFaces } from "$lib/graph-templates";
|
||||||
import type { ProjectManager } from "./project-manager.svelte";
|
import type { ProjectManager } from "./project-manager.svelte";
|
||||||
|
|
||||||
const { projectManager } = $props<{ projectManager: ProjectManager }>();
|
const { projectManager } = $props<{ projectManager: ProjectManager }>();
|
||||||
|
|
||||||
let showNewProject = $state(false);
|
let showNewProject = $state(false);
|
||||||
let newProjectName = $state("");
|
let newProjectName = $state("");
|
||||||
|
let selectedTemplate = $state("defaultPlant");
|
||||||
|
|
||||||
|
const templates = [
|
||||||
|
{
|
||||||
|
name: "Default Plant",
|
||||||
|
value: "defaultPlant",
|
||||||
|
graph: defaultPlant as unknown as Graph,
|
||||||
|
},
|
||||||
|
{ name: "Plant", value: "plant", graph: plant as unknown as Graph },
|
||||||
|
{
|
||||||
|
name: "Lotta Faces",
|
||||||
|
value: "lottaFaces",
|
||||||
|
graph: lottaFaces as unknown as Graph,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
function handleCreate() {
|
function handleCreate() {
|
||||||
projectManager.handleCreateProject(
|
const template =
|
||||||
defaultPlant as unknown as Graph,
|
templates.find((t) => t.value === selectedTemplate) || templates[0];
|
||||||
newProjectName,
|
projectManager.handleCreateProject(template.graph, newProjectName);
|
||||||
);
|
|
||||||
newProjectName = "";
|
newProjectName = "";
|
||||||
showNewProject = false;
|
showNewProject = false;
|
||||||
}
|
}
|
||||||
@@ -31,17 +45,26 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#if showNewProject}
|
{#if showNewProject}
|
||||||
<div
|
<div class="flex flex-col px-4 py-3 border-b-1 border-[var(--outline)] gap-2">
|
||||||
class="flex justify-between px-4 h-[70px] border-b-1 border-[var(--outline)] items-center gap-2"
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={newProjectName}
|
bind:value={newProjectName}
|
||||||
placeholder="Project name"
|
placeholder="Project name"
|
||||||
class="flex-1 min-w-0 px-2 py-2 bg-gray-800 border border-gray-700 rounded"
|
class="w-full px-2 py-2 bg-gray-800 border border-gray-700 rounded"
|
||||||
onkeydown={(e) => e.key === "Enter" && handleCreate()}
|
onkeydown={(e) => e.key === "Enter" && handleCreate()}
|
||||||
/>
|
/>
|
||||||
<button class="cursor-pointer" onclick={() => handleCreate()}>
|
<select
|
||||||
|
bind:value={selectedTemplate}
|
||||||
|
class="w-full px-2 py-2 bg-gray-800 border border-gray-700 rounded"
|
||||||
|
>
|
||||||
|
{#each templates as template}
|
||||||
|
<option value={template.value}>{template.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
class="cursor-pointer self-end px-3 py-1 bg-blue-600 rounded"
|
||||||
|
onclick={() => handleCreate()}
|
||||||
|
>
|
||||||
Create
|
Create
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export async function getGraph(id: number): Promise<Graph | undefined> {
|
|||||||
export async function saveGraph(graph: Graph): Promise<Graph> {
|
export async function saveGraph(graph: Graph): Promise<Graph> {
|
||||||
const db = await getDB();
|
const db = await getDB();
|
||||||
graph.meta = { ...graph.meta, lastModified: new Date().toISOString() };
|
graph.meta = { ...graph.meta, lastModified: new Date().toISOString() };
|
||||||
console.log('SAVING GRAPH', { graph });
|
|
||||||
await db.put(STORE_NAME, graph);
|
await db.put(STORE_NAME, graph);
|
||||||
return graph;
|
return graph;
|
||||||
}
|
}
|
||||||
@@ -40,7 +39,6 @@ export async function saveGraph(graph: Graph): Promise<Graph> {
|
|||||||
export async function deleteGraph(id: number): Promise<void> {
|
export async function deleteGraph(id: number): Promise<void> {
|
||||||
const db = await getDB();
|
const db = await getDB();
|
||||||
await db.delete(STORE_NAME, id);
|
await db.delete(STORE_NAME, id);
|
||||||
console.log('DELETE GRAPH', { id });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getGraphs(): Promise<Graph[]> {
|
export async function getGraphs(): Promise<Graph[]> {
|
||||||
|
|||||||
@@ -24,22 +24,14 @@ export class ProjectManager {
|
|||||||
await db.getDB();
|
await db.getDB();
|
||||||
this.projects = await db.getGraphs();
|
this.projects = await db.getGraphs();
|
||||||
|
|
||||||
console.log('PM: INIT', {
|
|
||||||
projects: this.projects,
|
|
||||||
activeProjectId: this.activeProjectId.value
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.activeProjectId.value !== undefined) {
|
if (this.activeProjectId.value !== undefined) {
|
||||||
let loadedGraph = await db.getGraph(this.activeProjectId.value);
|
let loadedGraph = await db.getGraph(this.activeProjectId.value);
|
||||||
console.log('PM: LOAD ACTIVE PROJECT', { loadedGraph });
|
|
||||||
if (loadedGraph) {
|
if (loadedGraph) {
|
||||||
console.log('Load active project');
|
|
||||||
this.graph = loadedGraph;
|
this.graph = loadedGraph;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.graph) {
|
if (!this.graph) {
|
||||||
console.log('Load first active project', { projectsAmount: this.projects.length });
|
|
||||||
if (this.projects?.length && this.projects[0]?.id !== undefined) {
|
if (this.projects?.length && this.projects[0]?.id !== undefined) {
|
||||||
this.graph = this.projects[0];
|
this.graph = this.projects[0];
|
||||||
this.activeProjectId.value = this.graph.id;
|
this.activeProjectId.value = this.graph.id;
|
||||||
@@ -47,7 +39,6 @@ export class ProjectManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.graph) {
|
if (!this.graph) {
|
||||||
console.log('Create default project');
|
|
||||||
this.handleCreateProject();
|
this.handleCreateProject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,8 +52,6 @@ export class ProjectManager {
|
|||||||
id++;
|
id++;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('CREATE PROJECT', { id, title });
|
|
||||||
|
|
||||||
g.id = id;
|
g.id = id;
|
||||||
if (!g.meta) g.meta = {};
|
if (!g.meta) g.meta = {};
|
||||||
if (!g.meta.title) g.meta.title = title;
|
if (!g.meta.title) g.meta.title = title;
|
||||||
@@ -79,7 +68,10 @@ export class ProjectManager {
|
|||||||
this.projects = [];
|
this.projects = [];
|
||||||
} else {
|
} else {
|
||||||
this.projects = this.projects.filter((p) => p.id !== projectId);
|
this.projects = this.projects.filter((p) => p.id !== projectId);
|
||||||
this.handleSelectProject(this.projects[0].id);
|
const id = this.projects[0].id;
|
||||||
|
if (id !== undefined) {
|
||||||
|
this.handleSelectProject(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,13 +92,6 @@
|
|||||||
geo.attributes.position.array[i + 2],
|
geo.attributes.position.array[i + 2],
|
||||||
] as Vector3Tuple;
|
] as Vector3Tuple;
|
||||||
}
|
}
|
||||||
|
|
||||||
// $effect(() => {
|
|
||||||
// console.log({
|
|
||||||
// geometries: $state.snapshot(geometries),
|
|
||||||
// indices: appSettings.value.debug.showIndices,
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Camera {center} {centerCamera} />
|
<Camera {center} {centerCamera} />
|
||||||
|
|||||||
@@ -85,21 +85,16 @@
|
|||||||
callback: () => randomGenerate(),
|
callback: () => randomGenerate(),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let graphSettings = $state<Record<string, any>>({});
|
let graphSettings = $state<Record<string, any>>({});
|
||||||
|
let graphSettingTypes = $state({
|
||||||
|
randomSeed: { type: "boolean", value: false },
|
||||||
|
});
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (graphSettings) {
|
if (graphSettings && graphSettingTypes) {
|
||||||
manager?.setSettings($state.snapshot(graphSettings));
|
manager?.setSettings($state.snapshot(graphSettings));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
type BooleanSchema = {
|
|
||||||
[key: string]: {
|
|
||||||
type: "boolean";
|
|
||||||
value: false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
let graphSettingTypes = $state<BooleanSchema>({
|
|
||||||
randomSeed: { type: "boolean", value: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
async function update(
|
async function update(
|
||||||
g: Graph,
|
g: Graph,
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
let nodeWasmWrapper = $state<ReturnType<typeof createWasmWrapper>>();
|
let nodeWasmWrapper = $state<ReturnType<typeof createWasmWrapper>>();
|
||||||
|
|
||||||
async function fetchNodeData(nodeId?: NodeId) {
|
async function fetchNodeData(nodeId?: NodeId) {
|
||||||
console.log("FETCHING", { nodeId });
|
|
||||||
nodeWasm = undefined;
|
nodeWasm = undefined;
|
||||||
nodeInstance = undefined;
|
nodeInstance = undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export type Socket = {
|
|||||||
export type Edge = [NodeInstance, number, NodeInstance, string];
|
export type Edge = [NodeInstance, number, NodeInstance, string];
|
||||||
|
|
||||||
export const GraphSchema = z.object({
|
export const GraphSchema = z.object({
|
||||||
id: z.number().optional(),
|
id: z.number(),
|
||||||
meta: z
|
meta: z
|
||||||
.object({
|
.object({
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
|
|||||||
Reference in New Issue
Block a user