Compare commits
9 Commits
4fdc247904
...
v0.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
f8a2a95bc1
|
|||
|
c9dd143916
|
|||
|
898dd49aee
|
|||
|
9fb69d760f
|
|||
|
bafbcca2b8
|
|||
|
8ad9e5535c
|
|||
|
|
43a3c54838 | ||
|
11eaeb719b
|
|||
|
74c2978cd1
|
@@ -9,11 +9,18 @@ cp CHANGELOG.md app/static/CHANGELOG.md
|
|||||||
REF_TYPE="${GITHUB_REF_TYPE:-branch}"
|
REF_TYPE="${GITHUB_REF_TYPE:-branch}"
|
||||||
REF_NAME="${GITHUB_REF_NAME:-$(basename "$GITHUB_REF")}"
|
REF_NAME="${GITHUB_REF_NAME:-$(basename "$GITHUB_REF")}"
|
||||||
BRANCH="${GITHUB_HEAD_REF:-}"
|
BRANCH="${GITHUB_HEAD_REF:-}"
|
||||||
BASE_BRANCH="${GITHUB_BASE_REF:-}"
|
|
||||||
if [[ -z "$BRANCH" && "$REF_TYPE" == "branch" ]]; then
|
if [[ -z "$BRANCH" && "$REF_TYPE" == "branch" ]]; then
|
||||||
BRANCH="$REF_NAME"
|
BRANCH="$REF_NAME"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Determine last tag and commits since
|
||||||
|
LAST_TAG="$(git describe --tags --abbrev=0 2>/dev/null || true)"
|
||||||
|
if [[ -n "$LAST_TAG" ]]; then
|
||||||
|
COMMITS_SINCE_LAST_RELEASE="$(git rev-list --count "${LAST_TAG}..HEAD")"
|
||||||
|
else
|
||||||
|
COMMITS_SINCE_LAST_RELEASE="0"
|
||||||
|
fi
|
||||||
|
|
||||||
cat >app/static/git.json <<EOF
|
cat >app/static/git.json <<EOF
|
||||||
{
|
{
|
||||||
"ref": "${GITHUB_REF:-}",
|
"ref": "${GITHUB_REF:-}",
|
||||||
@@ -21,14 +28,13 @@ cat >app/static/git.json <<EOF
|
|||||||
"ref_type": "${REF_TYPE}",
|
"ref_type": "${REF_TYPE}",
|
||||||
"sha": "${GITHUB_SHA:-}",
|
"sha": "${GITHUB_SHA:-}",
|
||||||
"run_number": "${GITHUB_RUN_NUMBER:-}",
|
"run_number": "${GITHUB_RUN_NUMBER:-}",
|
||||||
"server_url": "${GITHUB_SERVER_URL:-}",
|
|
||||||
"event_name": "${GITHUB_EVENT_NAME:-}",
|
"event_name": "${GITHUB_EVENT_NAME:-}",
|
||||||
"workflow": "${GITHUB_WORKFLOW:-}",
|
"workflow": "${GITHUB_WORKFLOW:-}",
|
||||||
"job": "${GITHUB_JOB:-}",
|
"job": "${GITHUB_JOB:-}",
|
||||||
"commit_message": "$(git log -1 --pretty=%B)",
|
"commit_message": "$(git log -1 --pretty=%B)",
|
||||||
"commit_timestamp": "$(git log -1 --pretty=%cI)",
|
"commit_timestamp": "$(git log -1 --pretty=%cI)",
|
||||||
"branch": "${BRANCH}",
|
"branch": "${BRANCH}",
|
||||||
"base_branch": "${BASE_BRANCH}"
|
"commits_since_last_release": "${COMMITS_SINCE_LAST_RELEASE}"
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,21 @@ TAG="$GITHUB_REF_NAME"
|
|||||||
VERSION=$(echo "$TAG" | sed 's/^v//')
|
VERSION=$(echo "$TAG" | sed 's/^v//')
|
||||||
DATE=$(date +%Y-%m-%d)
|
DATE=$(date +%Y-%m-%d)
|
||||||
|
|
||||||
echo "🚀 Creating release for $TAG (safe mode)"
|
echo "🚀 Creating release for $TAG"
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# 1. Extract release notes from annotated tag
|
# 1. Extract release notes from annotated tag
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Ensure the local git knows this is an annotated tag with metadata
|
||||||
|
git fetch origin "refs/tags/$TAG:refs/tags/$TAG" --force
|
||||||
|
|
||||||
|
# %(contents) gets the whole message.
|
||||||
|
# If you want ONLY what you typed after the first line, use %(contents:body)
|
||||||
NOTES=$(git tag -l "$TAG" --format='%(contents)')
|
NOTES=$(git tag -l "$TAG" --format='%(contents)')
|
||||||
|
|
||||||
if [ -z "$NOTES" ]; then
|
if [ -z "$(echo "$NOTES" | tr -d '[:space:]')" ]; then
|
||||||
echo "❌ Tag message is empty"
|
echo "❌ Tag message is empty or tag is not annotated"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -23,10 +28,8 @@ git checkout main
|
|||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# 2. Update all package.json versions
|
# 2. Update all package.json versions
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
echo "🔧 Updating package.json versions to $VERSION"
|
echo "🔧 Updating package.json versions to $VERSION"
|
||||||
|
find . -name package.json ! -path "*/node_modules/*" | while read -r file; do
|
||||||
find . -name package.json ! -path "*/node_modules/*" | while read file; do
|
|
||||||
tmp_file="$file.tmp"
|
tmp_file="$file.tmp"
|
||||||
jq --arg v "$VERSION" '.version = $v' "$file" >"$tmp_file"
|
jq --arg v "$VERSION" '.version = $v' "$file" >"$tmp_file"
|
||||||
mv "$tmp_file" "$file"
|
mv "$tmp_file" "$file"
|
||||||
@@ -35,20 +38,18 @@ done
|
|||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# 3. Generate commit list since last release
|
# 3. Generate commit list since last release
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
# Get the previous tag (fallback to empty if none)
|
|
||||||
LAST_TAG=$(git tag --sort=-creatordate | grep -v "^$TAG$" | head -n 1 || echo "")
|
LAST_TAG=$(git tag --sort=-creatordate | grep -v "^$TAG$" | head -n 1 || echo "")
|
||||||
|
|
||||||
if [ -n "$LAST_TAG" ]; then
|
if [ -n "$LAST_TAG" ]; then
|
||||||
COMMITS=$(git log "$LAST_TAG"..HEAD --pretty=format:'[%h](https://git.max-richter.dev/max/nodarium/commit/%H) %s')
|
# Filter out previous 'chore(release)' commits so the list stays clean
|
||||||
|
COMMITS=$(git log "$LAST_TAG"..HEAD --pretty=format:'* [%h](https://git.max-richter.dev/max/nodarium/commit/%H) %s' | grep -v "chore(release)")
|
||||||
else
|
else
|
||||||
COMMITS=$(git log HEAD --pretty=format:'[%h](https://git.max-richter.dev/max/nodarium/commit/%H) %s')
|
COMMITS=$(git log HEAD --pretty=format:'* [%h](https://git.max-richter.dev/max/nodarium/commit/%H) %s' | grep -v "chore(release)")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# 4. Update CHANGELOG.md (prepend)
|
# 4. Update CHANGELOG.md (prepend)
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
tmp_changelog="CHANGELOG.tmp"
|
tmp_changelog="CHANGELOG.tmp"
|
||||||
{
|
{
|
||||||
echo "## $TAG ($DATE)"
|
echo "## $TAG ($DATE)"
|
||||||
@@ -56,7 +57,7 @@ tmp_changelog="CHANGELOG.tmp"
|
|||||||
echo "$NOTES"
|
echo "$NOTES"
|
||||||
echo ""
|
echo ""
|
||||||
if [ -n "$COMMITS" ]; then
|
if [ -n "$COMMITS" ]; then
|
||||||
echo "All Commits:"
|
echo "### All Commits in this version:"
|
||||||
echo "$COMMITS"
|
echo "$COMMITS"
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
@@ -74,24 +75,17 @@ pnpm exec dprint fmt CHANGELOG.md
|
|||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# 5. Create release commit
|
# 5. Create release commit
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
git config user.name "release-bot"
|
git config user.name "release-bot"
|
||||||
git config user.email "release-bot@ci"
|
git config user.email "release-bot@ci"
|
||||||
|
|
||||||
git add CHANGELOG.md $(find . -name package.json ! -path "*/node_modules/*")
|
git add CHANGELOG.md $(find . -name package.json ! -path "*/node_modules/*")
|
||||||
|
|
||||||
# Skip commit if nothing changed
|
|
||||||
if git diff --cached --quiet; then
|
if git diff --cached --quiet; then
|
||||||
echo "No changes to commit for release $TAG"
|
echo "No changes to commit for release $TAG"
|
||||||
exit 0
|
else
|
||||||
|
git commit -m "chore(release): $TAG"
|
||||||
|
git push origin main
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git commit -m "chore(release): $TAG"
|
cp CHANGELOG.md app/static/CHANGELOG.md
|
||||||
|
echo "✅ Release process for $TAG complete"
|
||||||
# -------------------------------------------------------------------
|
|
||||||
# 6. Push changes
|
|
||||||
# -------------------------------------------------------------------
|
|
||||||
|
|
||||||
git push origin main
|
|
||||||
|
|
||||||
echo "✅ Release commit for $TAG created successfully (tag untouched)"
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nodarium/app",
|
"name": "@nodarium/app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.2",
|
"version": "0.0.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
|||||||
265
app/src/lib/graph-interface/graph-manager.svelte.test.ts
Normal file
265
app/src/lib/graph-interface/graph-manager.svelte.test.ts
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { GraphManager } from './graph-manager.svelte';
|
||||||
|
import {
|
||||||
|
createMockNodeRegistry,
|
||||||
|
mockFloatInputNode,
|
||||||
|
mockFloatOutputNode,
|
||||||
|
mockGeometryOutputNode,
|
||||||
|
mockPathInputNode,
|
||||||
|
mockVec3OutputNode
|
||||||
|
} from './test-utils';
|
||||||
|
|
||||||
|
describe('GraphManager', () => {
|
||||||
|
describe('getPossibleSockets', () => {
|
||||||
|
describe('when dragging an output socket', () => {
|
||||||
|
it('should return compatible input sockets based on type', () => {
|
||||||
|
const registry = createMockNodeRegistry([
|
||||||
|
mockFloatOutputNode,
|
||||||
|
mockFloatInputNode,
|
||||||
|
mockGeometryOutputNode,
|
||||||
|
mockPathInputNode
|
||||||
|
]);
|
||||||
|
|
||||||
|
const manager = new GraphManager(registry);
|
||||||
|
|
||||||
|
const floatInputNode = manager.createNode({
|
||||||
|
type: 'test/node/input',
|
||||||
|
position: [100, 100],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const floatOutputNode = manager.createNode({
|
||||||
|
type: 'test/node/output',
|
||||||
|
position: [0, 0],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(floatInputNode).toBeDefined();
|
||||||
|
expect(floatOutputNode).toBeDefined();
|
||||||
|
|
||||||
|
const possibleSockets = manager.getPossibleSockets({
|
||||||
|
node: floatOutputNode!,
|
||||||
|
index: 0,
|
||||||
|
position: [0, 0]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(possibleSockets.length).toBe(1);
|
||||||
|
const socketNodeIds = possibleSockets.map(([node]) => node.id);
|
||||||
|
expect(socketNodeIds).toContain(floatInputNode!.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should exclude self node from possible sockets', () => {
|
||||||
|
const registry = createMockNodeRegistry([
|
||||||
|
mockFloatOutputNode,
|
||||||
|
mockFloatInputNode
|
||||||
|
]);
|
||||||
|
|
||||||
|
const manager = new GraphManager(registry);
|
||||||
|
|
||||||
|
const floatInputNode = manager.createNode({
|
||||||
|
type: 'test/node/input',
|
||||||
|
position: [100, 100],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(floatInputNode).toBeDefined();
|
||||||
|
|
||||||
|
const possibleSockets = manager.getPossibleSockets({
|
||||||
|
node: floatInputNode!,
|
||||||
|
index: 'value',
|
||||||
|
position: [0, 0]
|
||||||
|
});
|
||||||
|
|
||||||
|
const socketNodeIds = possibleSockets.map(([node]) => node.id);
|
||||||
|
expect(socketNodeIds).not.toContain(floatInputNode!.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should exclude parent nodes from possible sockets when dragging output', () => {
|
||||||
|
const registry = createMockNodeRegistry([
|
||||||
|
mockFloatOutputNode,
|
||||||
|
mockFloatInputNode
|
||||||
|
]);
|
||||||
|
|
||||||
|
const manager = new GraphManager(registry);
|
||||||
|
|
||||||
|
const parentNode = manager.createNode({
|
||||||
|
type: 'test/node/output',
|
||||||
|
position: [0, 0],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const childNode = manager.createNode({
|
||||||
|
type: 'test/node/input',
|
||||||
|
position: [100, 100],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parentNode).toBeDefined();
|
||||||
|
expect(childNode).toBeDefined();
|
||||||
|
|
||||||
|
if (parentNode && childNode) {
|
||||||
|
manager.createEdge(parentNode, 0, childNode, 'value');
|
||||||
|
}
|
||||||
|
|
||||||
|
const possibleSockets = manager.getPossibleSockets({
|
||||||
|
node: parentNode!,
|
||||||
|
index: 0,
|
||||||
|
position: [0, 0]
|
||||||
|
});
|
||||||
|
|
||||||
|
const socketNodeIds = possibleSockets.map(([node]) => node.id);
|
||||||
|
expect(socketNodeIds).not.toContain(childNode!.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return sockets compatible with accepts property', () => {
|
||||||
|
const registry = createMockNodeRegistry([
|
||||||
|
mockGeometryOutputNode,
|
||||||
|
mockPathInputNode
|
||||||
|
]);
|
||||||
|
|
||||||
|
const manager = new GraphManager(registry);
|
||||||
|
|
||||||
|
const geometryOutputNode = manager.createNode({
|
||||||
|
type: 'test/node/geometry',
|
||||||
|
position: [0, 0],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const pathInputNode = manager.createNode({
|
||||||
|
type: 'test/node/path',
|
||||||
|
position: [100, 100],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(geometryOutputNode).toBeDefined();
|
||||||
|
expect(pathInputNode).toBeDefined();
|
||||||
|
|
||||||
|
const possibleSockets = manager.getPossibleSockets({
|
||||||
|
node: geometryOutputNode!,
|
||||||
|
index: 0,
|
||||||
|
position: [0, 0]
|
||||||
|
});
|
||||||
|
|
||||||
|
const socketNodeIds = possibleSockets.map(([node]) => node.id);
|
||||||
|
expect(socketNodeIds).toContain(pathInputNode!.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array when no compatible sockets exist', () => {
|
||||||
|
const registry = createMockNodeRegistry([
|
||||||
|
mockVec3OutputNode,
|
||||||
|
mockFloatInputNode
|
||||||
|
]);
|
||||||
|
|
||||||
|
const manager = new GraphManager(registry);
|
||||||
|
|
||||||
|
const vec3OutputNode = manager.createNode({
|
||||||
|
type: 'test/node/vec3',
|
||||||
|
position: [0, 0],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const floatInputNode = manager.createNode({
|
||||||
|
type: 'test/node/input',
|
||||||
|
position: [100, 100],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(vec3OutputNode).toBeDefined();
|
||||||
|
expect(floatInputNode).toBeDefined();
|
||||||
|
|
||||||
|
const possibleSockets = manager.getPossibleSockets({
|
||||||
|
node: vec3OutputNode!,
|
||||||
|
index: 0,
|
||||||
|
position: [0, 0]
|
||||||
|
});
|
||||||
|
|
||||||
|
const socketNodeIds = possibleSockets.map(([node]) => node.id);
|
||||||
|
expect(socketNodeIds).not.toContain(floatInputNode!.id);
|
||||||
|
expect(possibleSockets.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return socket info with correct socket key for inputs', () => {
|
||||||
|
const registry = createMockNodeRegistry([
|
||||||
|
mockFloatOutputNode,
|
||||||
|
mockFloatInputNode
|
||||||
|
]);
|
||||||
|
|
||||||
|
const manager = new GraphManager(registry);
|
||||||
|
|
||||||
|
const floatOutputNode = manager.createNode({
|
||||||
|
type: 'test/node/output',
|
||||||
|
position: [0, 0],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const floatInputNode = manager.createNode({
|
||||||
|
type: 'test/node/input',
|
||||||
|
position: [100, 100],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(floatOutputNode).toBeDefined();
|
||||||
|
expect(floatInputNode).toBeDefined();
|
||||||
|
|
||||||
|
const possibleSockets = manager.getPossibleSockets({
|
||||||
|
node: floatOutputNode!,
|
||||||
|
index: 0,
|
||||||
|
position: [0, 0]
|
||||||
|
});
|
||||||
|
|
||||||
|
const matchingSocket = possibleSockets.find(([node]) => node.id === floatInputNode!.id);
|
||||||
|
expect(matchingSocket).toBeDefined();
|
||||||
|
expect(matchingSocket![1]).toBe('value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return multiple compatible sockets', () => {
|
||||||
|
const registry = createMockNodeRegistry([
|
||||||
|
mockFloatOutputNode,
|
||||||
|
mockFloatInputNode,
|
||||||
|
mockGeometryOutputNode,
|
||||||
|
mockPathInputNode
|
||||||
|
]);
|
||||||
|
|
||||||
|
const manager = new GraphManager(registry);
|
||||||
|
|
||||||
|
const floatOutputNode = manager.createNode({
|
||||||
|
type: 'test/node/output',
|
||||||
|
position: [0, 0],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const geometryOutputNode = manager.createNode({
|
||||||
|
type: 'test/node/geometry',
|
||||||
|
position: [200, 0],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const floatInputNode = manager.createNode({
|
||||||
|
type: 'test/node/input',
|
||||||
|
position: [100, 100],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const pathInputNode = manager.createNode({
|
||||||
|
type: 'test/node/path',
|
||||||
|
position: [300, 100],
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(floatOutputNode).toBeDefined();
|
||||||
|
expect(geometryOutputNode).toBeDefined();
|
||||||
|
expect(floatInputNode).toBeDefined();
|
||||||
|
expect(pathInputNode).toBeDefined();
|
||||||
|
|
||||||
|
const possibleSocketsForFloat = manager.getPossibleSockets({
|
||||||
|
node: floatOutputNode!,
|
||||||
|
index: 0,
|
||||||
|
position: [0, 0]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(possibleSocketsForFloat.length).toBe(1);
|
||||||
|
expect(possibleSocketsForFloat.map(([n]) => n.id)).toContain(floatInputNode!.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -757,12 +757,16 @@ export class GraphManager extends EventEmitter<{
|
|||||||
(n) => n.id !== node.id && !parents.has(n.id)
|
(n) => n.id !== node.id && !parents.has(n.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
// get edges from this socket
|
const edges = new SvelteMap<number, string[]>();
|
||||||
const edges = new SvelteMap(
|
this.getEdgesFromNode(node)
|
||||||
this.getEdgesFromNode(node)
|
.filter((e) => e[1] === index)
|
||||||
.filter((e) => e[1] === index)
|
.forEach((e) => {
|
||||||
.map((e) => [e[2].id, e[3]])
|
if (edges.has(e[2].id)) {
|
||||||
);
|
edges.get(e[2].id)?.push(e[3]);
|
||||||
|
} else {
|
||||||
|
edges.set(e[2].id, [e[3]]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const ownType = nodeType.outputs?.[index];
|
const ownType = nodeType.outputs?.[index];
|
||||||
|
|
||||||
@@ -775,7 +779,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
areSocketsCompatible(ownType, otherType)
|
areSocketsCompatible(ownType, otherType)
|
||||||
&& edges.get(node.id) !== key
|
&& !edges.get(node.id)?.includes(key)
|
||||||
) {
|
) {
|
||||||
sockets.push([node, key]);
|
sockets.push([node, key]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ export class MouseEventManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_socket && smallestDist < 0.9) {
|
if (_socket && smallestDist < 1.5) {
|
||||||
this.state.mousePosition = _socket.position;
|
this.state.mousePosition = _socket.position;
|
||||||
this.state.hoveredSocket = _socket;
|
this.state.hoveredSocket = _socket;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
);
|
);
|
||||||
const pathHover = $derived(
|
const pathHover = $derived(
|
||||||
createNodePath({
|
createNodePath({
|
||||||
depth: 8.5,
|
depth: 7,
|
||||||
height: 50,
|
height: 40,
|
||||||
y: 49,
|
y: 49,
|
||||||
cornerTop,
|
cornerTop,
|
||||||
rightBump,
|
rightBump,
|
||||||
@@ -113,6 +113,9 @@
|
|||||||
stroke: var(--stroke);
|
stroke: var(--stroke);
|
||||||
stroke-width: var(--stroke-width);
|
stroke-width: var(--stroke-width);
|
||||||
d: var(--path);
|
d: var(--path);
|
||||||
|
|
||||||
|
stroke-linejoin: round;
|
||||||
|
shape-rendering: geometricPrecision;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
|||||||
@@ -39,16 +39,6 @@
|
|||||||
const aspectRatio = 0.5;
|
const aspectRatio = 0.5;
|
||||||
|
|
||||||
const path = $derived(
|
const path = $derived(
|
||||||
createNodePath({
|
|
||||||
depth: 7,
|
|
||||||
height: 20,
|
|
||||||
y: 50.5,
|
|
||||||
cornerBottom,
|
|
||||||
leftBump,
|
|
||||||
aspectRatio
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const pathDisabled = $derived(
|
|
||||||
createNodePath({
|
createNodePath({
|
||||||
depth: 6,
|
depth: 6,
|
||||||
height: 18,
|
height: 18,
|
||||||
@@ -60,8 +50,8 @@
|
|||||||
);
|
);
|
||||||
const pathHover = $derived(
|
const pathHover = $derived(
|
||||||
createNodePath({
|
createNodePath({
|
||||||
depth: 8,
|
depth: 7,
|
||||||
height: 25,
|
height: 20,
|
||||||
y: 50.5,
|
y: 50.5,
|
||||||
cornerBottom,
|
cornerBottom,
|
||||||
leftBump,
|
leftBump,
|
||||||
@@ -74,7 +64,7 @@
|
|||||||
class="wrapper"
|
class="wrapper"
|
||||||
data-node-type={node.type}
|
data-node-type={node.type}
|
||||||
data-node-input={id}
|
data-node-input={id}
|
||||||
class:disabled={!graphState?.possibleSocketIds.has(socketId)}
|
class:possible-socket={graphState?.possibleSocketIds.has(socketId)}
|
||||||
>
|
>
|
||||||
{#key id && graphId}
|
{#key id && graphId}
|
||||||
<div class="content" class:disabled={graph?.inputSockets?.has(socketId)}>
|
<div class="content" class:disabled={graph?.inputSockets?.has(socketId)}>
|
||||||
@@ -91,10 +81,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if node?.state?.type?.inputs?.[id]?.internal !== true}
|
{#if node?.state?.type?.inputs?.[id]?.internal !== true}
|
||||||
<div data-node-socket class="large target"></div>
|
|
||||||
<div
|
<div
|
||||||
data-node-socket
|
data-node-socket
|
||||||
class="small target"
|
class="target"
|
||||||
onmousedown={handleMouseDown}
|
onmousedown={handleMouseDown}
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -112,7 +101,6 @@
|
|||||||
style={`
|
style={`
|
||||||
--path: path("${path}");
|
--path: path("${path}");
|
||||||
--hover-path: path("${pathHover}");
|
--hover-path: path("${pathHover}");
|
||||||
--hover-path-disabled: path("${pathDisabled}");
|
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<path vector-effect="non-scaling-stroke"></path>
|
<path vector-effect="non-scaling-stroke"></path>
|
||||||
@@ -128,28 +116,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.target {
|
.target {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%) translateX(-50%);
|
transform: translateY(-50%) translateX(-50%);
|
||||||
/* background: red; */
|
|
||||||
/* opacity: 0.1; */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.small.target {
|
.possible-socket .target {
|
||||||
width: 30px;
|
box-shadow: 0px 0px 10px rgba(255, 255, 255, 0.5);
|
||||||
height: 30px;
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
z-index: -10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.large.target {
|
.target:hover ~ svg path{
|
||||||
width: 60px;
|
d: var(--hover-path);
|
||||||
height: 60px;
|
|
||||||
cursor: unset;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.hovering-sockets) .large.target {
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@@ -179,19 +161,16 @@
|
|||||||
stroke: var(--stroke);
|
stroke: var(--stroke);
|
||||||
stroke-width: var(--stroke-width);
|
stroke-width: var(--stroke-width);
|
||||||
d: var(--path);
|
d: var(--path);
|
||||||
}
|
|
||||||
|
|
||||||
:global {
|
stroke-linejoin: round;
|
||||||
.hovering-sockets .large:hover ~ svg path {
|
shape-rendering: geometricPrecision;
|
||||||
d: var(--hover-path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content.disabled {
|
.content.disabled {
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.disabled svg path {
|
.possible-socket svg path {
|
||||||
d: var(--hover-path-disabled) !important;
|
d: var(--hover-path);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
86
app/src/lib/graph-interface/test-utils.ts
Normal file
86
app/src/lib/graph-interface/test-utils.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import type { NodeDefinition, NodeId, NodeRegistry } from '@nodarium/types';
|
||||||
|
|
||||||
|
export function createMockNodeRegistry(nodes: NodeDefinition[]): NodeRegistry {
|
||||||
|
const nodesMap = new Map(nodes.map(n => [n.id, n]));
|
||||||
|
return {
|
||||||
|
status: 'ready' as const,
|
||||||
|
load: async (nodeIds: NodeId[]) => {
|
||||||
|
const loaded: NodeDefinition[] = [];
|
||||||
|
for (const id of nodeIds) {
|
||||||
|
if (nodesMap.has(id)) {
|
||||||
|
loaded.push(nodesMap.get(id)!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loaded;
|
||||||
|
},
|
||||||
|
getNode: (id: string) => nodesMap.get(id as NodeId),
|
||||||
|
getAllNodes: () => Array.from(nodesMap.values()),
|
||||||
|
register: async () => {
|
||||||
|
throw new Error('Not implemented in mock');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mockFloatOutputNode: NodeDefinition = {
|
||||||
|
id: 'test/node/output',
|
||||||
|
inputs: {},
|
||||||
|
outputs: ['float'],
|
||||||
|
meta: { title: 'Float Output' },
|
||||||
|
execute: () => new Int32Array()
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockFloatInputNode: NodeDefinition = {
|
||||||
|
id: 'test/node/input',
|
||||||
|
inputs: { value: { type: 'float' } },
|
||||||
|
outputs: [],
|
||||||
|
meta: { title: 'Float Input' },
|
||||||
|
execute: () => new Int32Array()
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockGeometryOutputNode: NodeDefinition = {
|
||||||
|
id: 'test/node/geometry',
|
||||||
|
inputs: {},
|
||||||
|
outputs: ['geometry'],
|
||||||
|
meta: { title: 'Geometry Output' },
|
||||||
|
execute: () => new Int32Array()
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockPathInputNode: NodeDefinition = {
|
||||||
|
id: 'test/node/path',
|
||||||
|
inputs: { input: { type: 'path', accepts: ['geometry'] } },
|
||||||
|
outputs: [],
|
||||||
|
meta: { title: 'Path Input' },
|
||||||
|
execute: () => new Int32Array()
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockVec3OutputNode: NodeDefinition = {
|
||||||
|
id: 'test/node/vec3',
|
||||||
|
inputs: {},
|
||||||
|
outputs: ['vec3'],
|
||||||
|
meta: { title: 'Vec3 Output' },
|
||||||
|
execute: () => new Int32Array()
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockIntegerInputNode: NodeDefinition = {
|
||||||
|
id: 'test/node/integer',
|
||||||
|
inputs: { value: { type: 'integer' } },
|
||||||
|
outputs: [],
|
||||||
|
meta: { title: 'Integer Input' },
|
||||||
|
execute: () => new Int32Array()
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockBooleanOutputNode: NodeDefinition = {
|
||||||
|
id: 'test/node/boolean',
|
||||||
|
inputs: {},
|
||||||
|
outputs: ['boolean'],
|
||||||
|
meta: { title: 'Boolean Output' },
|
||||||
|
execute: () => new Int32Array()
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockBooleanInputNode: NodeDefinition = {
|
||||||
|
id: 'test/node/boolean-input',
|
||||||
|
inputs: { value: { type: 'boolean' } },
|
||||||
|
outputs: [],
|
||||||
|
meta: { title: 'Boolean Input' },
|
||||||
|
execute: () => new Int32Array()
|
||||||
|
};
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
type Change = { type: string; content: string };
|
type Change = { type: string; content: string };
|
||||||
|
|
||||||
async function fetchChangelog() {
|
|
||||||
const res = await fetch('/CHANGELOG.md');
|
|
||||||
return await res.text();
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeMap: Record<string, string> = {
|
const typeMap: Record<string, string> = {
|
||||||
fix: 'bg-layer-2 bg-red-800',
|
fix: 'bg-layer-2 bg-red-800',
|
||||||
feat: 'bg-layer-2 bg-green-800',
|
feat: 'bg-layer-2 bg-green-800',
|
||||||
@@ -15,6 +10,16 @@
|
|||||||
default: 'bg-layer-2 text-text'
|
default: 'bg-layer-2 text-text'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function fetchChangelog() {
|
||||||
|
const res = await fetch('/CHANGELOG.md');
|
||||||
|
return await res.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchGitInfo() {
|
||||||
|
const res = await fetch('/git.json');
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
function parseChangelog(md: string) {
|
function parseChangelog(md: string) {
|
||||||
const lines = md.split('\n');
|
const lines = md.split('\n');
|
||||||
const parsed: (string | Change)[] = [];
|
const parsed: (string | Change)[] = [];
|
||||||
@@ -45,8 +50,13 @@
|
|||||||
parsed.push({ type: 'default', content: line });
|
parsed.push({ type: 'default', content: line });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove trailing horizontal rule
|
||||||
let lastLine = parsed.at(-1);
|
let lastLine = parsed.at(-1);
|
||||||
if (lastLine !== undefined && typeof lastLine !== 'string' && lastLine.type === 'hr') {
|
if (
|
||||||
|
lastLine !== undefined
|
||||||
|
&& typeof lastLine !== 'string'
|
||||||
|
&& lastLine.type === 'hr'
|
||||||
|
) {
|
||||||
parsed.pop();
|
parsed.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,20 +65,41 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-4 font-mono text-text">
|
<div class="p-4 font-mono text-text">
|
||||||
{#await fetchChangelog()}
|
{#await Promise.all([fetchChangelog(), fetchGitInfo()])}
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
{:then md}
|
{:then [md, git]}
|
||||||
|
<div class="mb-4 p-3 bg-layer-2 text-xs">
|
||||||
|
<p><strong>Branch:</strong> {git.branch}</p>
|
||||||
|
<p>
|
||||||
|
<strong>Commit:</strong>
|
||||||
|
{git.sha.slice(0, 7)} – {git.commit_message}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Commits since last release:</strong>
|
||||||
|
{git.commits_since_last_release}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Timestamp:</strong>
|
||||||
|
{new Date(git.commit_timestamp).toLocaleString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#each parseChangelog(md) as item (item)}
|
{#each parseChangelog(md) as item (item)}
|
||||||
{#if typeof item === 'string'}
|
{#if typeof item === 'string'}
|
||||||
<h2 class="text-xl font-semibold mt-4 mb-4 text-layer-1">{item}</h2>
|
<h2 class="text-xl font-semibold mt-4 mb-4 text-layer-1">{item}</h2>
|
||||||
{:else if item.type === 'hr'}
|
{:else if item.type === 'hr'}{:else}
|
||||||
<p></p>
|
<p class="py-1 mb-1 leading-8 border-b border-b-outline last:border-b-0">
|
||||||
{:else}
|
|
||||||
<p class="px-3 py-1 mb-1 leading-8 border-b-1 border-b-outline last:border-b-0">
|
|
||||||
{#if item.type !== 'default'}
|
{#if item.type !== 'default'}
|
||||||
<span class="p-1 rounded-sm font-semibold {typeMap[item.type]}">
|
<span
|
||||||
|
class="
|
||||||
|
p-1 rounded-sm opacity-80 font-semibold {typeMap[
|
||||||
|
item.type
|
||||||
|
]}
|
||||||
|
"
|
||||||
|
>
|
||||||
{item.content.split(':')[0]}
|
{item.content.split(':')[0]}
|
||||||
</span> {item.content.split(':').slice(1).join(':').trim()}
|
</span>
|
||||||
|
{item.content.split(':').slice(1).join(':').trim()}
|
||||||
{:else}
|
{:else}
|
||||||
{item.content}
|
{item.content}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "0.0.2",
|
"version": "0.0.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "pnpm run -r --filter 'ui' build",
|
"postinstall": "pnpm run -r --filter 'ui' build",
|
||||||
"lint": "pnpm run -r --parallel lint",
|
"lint": "pnpm run -r --parallel lint",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nodarium/types",
|
"name": "@nodarium/types",
|
||||||
"version": "0.0.2",
|
"version": "0.0.3",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nodarium/ui",
|
"name": "@nodarium/ui",
|
||||||
"version": "0.0.2",
|
"version": "0.0.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build && npm run package",
|
"build": "vite build && npm run package",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nodarium/utils",
|
"name": "@nodarium/utils",
|
||||||
"version": "0.0.2",
|
"version": "0.0.3",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Reference in New Issue
Block a user