Skip to content

Commit

Permalink
Merge branch 'master' into string-nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
mck authored Dec 17, 2024
2 parents e5e005c + cc85d3b commit 3abfe5f
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 11 deletions.
6 changes: 6 additions & 0 deletions .changeset/modern-moons-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tokens-studio/graph-engine": minor
"@tokens-studio/graph-engine-ui": minor
---

Add find First and linear search nodes.
6 changes: 4 additions & 2 deletions packages/graph-engine/src/nodes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { nodes as gradient } from './gradient/index.js';
import { nodes as logic } from './logic/index.js';
import { nodes as math } from './math/index.js';
import { nodes as preview } from './preview/index.js';
import { nodes as search } from './search/index.js';
import { nodes as series } from './series/index.js';
import { nodes as string } from './string/index.js';
import { nodes as typing } from './typing/index.js';
Expand All @@ -29,11 +30,12 @@ export const nodes: (typeof Node)[] = ([] as (typeof Node)[]).concat(
logic,
math,
preview,
search,
series,
string,
typing,
vector2,
typography
typography,
vector2
);

/**
Expand Down
10 changes: 1 addition & 9 deletions packages/graph-engine/src/nodes/logic/compare.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import { AnySchema, BooleanSchema, StringSchema } from '../../schemas/index.js';
import { INodeDefinition, ToInput, ToOutput } from '../../index.js';
import { Node } from '../../programmatic/node.js';

export enum Operator {
EQUAL = '==',
NOT_EQUAL = '!=',
GREATER_THAN = '>',
LESS_THAN = '<',
GREATER_THAN_OR_EQUAL = '>=',
LESS_THAN_OR_EQUAL = '<='
}
import { Operator } from '../../schemas/operators.js';

export default class NodeDefinition<T> extends Node {
static title = 'Compare';
Expand Down
93 changes: 93 additions & 0 deletions packages/graph-engine/src/nodes/search/findFirst.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import {
BooleanSchema,
NumberSchema,
StringSchema
} from '../../schemas/index.js';
import { INodeDefinition, ToInput, ToOutput } from '../../index.js';
import { Node } from '../../programmatic/node.js';
import { Operator } from '../../schemas/operators.js';
import { arrayOf } from '../../schemas/utils.js';

export default class NodeDefinition extends Node {
static title = 'Find First Match';
static type = 'studio.tokens.search.findFirst';
static description =
'Finds the first array element that matches a comparison condition with a target value';

declare inputs: ToInput<{
array: any[];
target: number;
operator: Operator.GREATER_THAN | Operator.LESS_THAN;
}>;

declare outputs: ToOutput<{
value: any;
index: number;
found: boolean;
}>;

constructor(props: INodeDefinition) {
super(props);

this.addInput('array', {
type: {
...arrayOf(NumberSchema),
description: 'Array of numbers to search through'
}
});

this.addInput('target', {
type: {
...NumberSchema,
description: 'Target value to compare against'
}
});

this.addInput('operator', {
type: {
...StringSchema,
enum: [Operator.GREATER_THAN, Operator.LESS_THAN],
default: Operator.GREATER_THAN,
description: 'Comparison operator to use (greater than or less than)'
}
});

this.addOutput('value', {
type: {
...NumberSchema,
description: 'The first matching value found (null if no match)'
}
});

this.addOutput('index', {
type: {
...NumberSchema,
description: 'Index of the first matching value (-1 if no match)'
}
});

this.addOutput('found', {
type: {
...BooleanSchema,
description: 'Whether a matching value was found'
}
});
}

execute(): void {
const { array, target, operator } = this.getAllInputs();

const comparisonFn =
operator === Operator.GREATER_THAN
? (value: number) => value > target
: (value: number) => value < target;

const index = array.findIndex(comparisonFn);
const found = index !== -1;
const value = found ? array[index] : undefined;

this.outputs.value.set(value);
this.outputs.index.set(index);
this.outputs.found.set(found);
}
}
4 changes: 4 additions & 0 deletions packages/graph-engine/src/nodes/search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import findFirst from './findFirst.js';
import linear from './linear.js';

export const nodes = [linear, findFirst];
72 changes: 72 additions & 0 deletions packages/graph-engine/src/nodes/search/linear.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
AnyArraySchema,
AnySchema,
BooleanSchema,
NumberSchema
} from '../../schemas/index.js';
import { INodeDefinition, ToInput, ToOutput } from '../../index.js';
import { Node } from '../../programmatic/node.js';

export default class NodeDefinition<T> extends Node {
static title = 'Linear Search';
static type = 'studio.tokens.search.linear';
static description =
'Performs a linear search on an array to find the index of a target value';

declare inputs: ToInput<{
array: T[];
target: T;
}>;

declare outputs: ToOutput<{
index: number;
found: boolean;
}>;

constructor(props: INodeDefinition) {
super(props);

this.addInput('array', {
type: {
...AnyArraySchema,
description: 'The array to search through'
}
});

this.addInput('target', {
type: {
...AnySchema,
description: 'The value to search for'
}
});

this.addOutput('index', {
type: {
...NumberSchema,
description: 'The index of the target value (-1 if not found)'
}
});

this.addOutput('found', {
type: {
...BooleanSchema,
description: 'Whether the target value was found'
}
});
}

execute(): void | Promise<void> {
const { array, target } = this.getAllInputs();

if (!Array.isArray(array)) {
throw new Error('Input must be an array');
}

const index = array.findIndex(
item => JSON.stringify(item) === JSON.stringify(target)
);

this.outputs.index.set(index);
this.outputs.found.set(index !== -1);
}
}
8 changes: 8 additions & 0 deletions packages/graph-engine/src/schemas/operators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export enum Operator {
EQUAL = '==',
NOT_EQUAL = '!=',
GREATER_THAN = '>',
LESS_THAN = '<',
GREATER_THAN_OR_EQUAL = '>=',
LESS_THAN_OR_EQUAL = '<='
}
66 changes: 66 additions & 0 deletions packages/graph-engine/tests/suites/nodes/search/findFirst.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Graph } from '../../../../src/graph/graph.js';
import { Operator } from '../../../../src/schemas/operators.js';
import { describe, expect, test } from 'vitest';
import Node from '../../../../src/nodes/search/findFirst.js';

describe('search/findFirst', () => {
test('finds first number greater than target', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.array.setValue([1, 3, 5, 7, 9]);
node.inputs.target.setValue(4);
node.inputs.operator.setValue(Operator.GREATER_THAN);

await node.execute();

expect(node.outputs.value.value).toBe(5);
expect(node.outputs.index.value).toBe(2);
expect(node.outputs.found.value).toBe(true);
});

test('finds first number less than target', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.array.setValue([9, 7, 5, 3, 1]);
node.inputs.target.setValue(6);
node.inputs.operator.setValue(Operator.LESS_THAN);

await node.execute();

expect(node.outputs.value.value).toBe(5);
expect(node.outputs.index.value).toBe(2);
expect(node.outputs.found.value).toBe(true);
});

test('handles no match found', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.array.setValue([1, 2, 3, 4, 5]);
node.inputs.target.setValue(10);
node.inputs.operator.setValue(Operator.GREATER_THAN);

await node.execute();

expect(node.outputs.value.value).toBe(undefined);
expect(node.outputs.index.value).toBe(-1);
expect(node.outputs.found.value).toBe(false);
});

test('works with empty array', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.array.setValue([]);
node.inputs.target.setValue(5);
node.inputs.operator.setValue(Operator.GREATER_THAN);

await node.execute();

expect(node.outputs.value.value).toBe(undefined);
expect(node.outputs.index.value).toBe(-1);
expect(node.outputs.found.value).toBe(false);
});
});
63 changes: 63 additions & 0 deletions packages/graph-engine/tests/suites/nodes/search/linear.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Graph } from '../../../../src/graph/graph.js';
import { describe, expect, test } from 'vitest';
import Node from '../../../../src/nodes/search/linear.js';

describe('search/linear', () => {
test('finds exact match in array of numbers', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.array.setValue([1, 2, 3, 4, 5]);
node.inputs.target.setValue(3);

await node.execute();

expect(node.outputs.index.value).toBe(2);
expect(node.outputs.found.value).toBe(true);
});

test('finds exact match in array of objects', async () => {
const graph = new Graph();
const node = new Node({ graph });

const array = [
{ id: 1, value: 'a' },
{ id: 2, value: 'b' },
{ id: 3, value: 'c' }
];

node.inputs.array.setValue(array);
node.inputs.target.setValue({ id: 2, value: 'b' });

await node.execute();

expect(node.outputs.index.value).toBe(1);
expect(node.outputs.found.value).toBe(true);
});

test('handles no match found', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.array.setValue([1, 2, 3, 4, 5]);
node.inputs.target.setValue(6);

await node.execute();

expect(node.outputs.index.value).toBe(-1);
expect(node.outputs.found.value).toBe(false);
});

test('handles empty array', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.array.setValue([]);
node.inputs.target.setValue(1);

await node.execute();

expect(node.outputs.index.value).toBe(-1);
expect(node.outputs.found.value).toBe(false);
});
});
2 changes: 2 additions & 0 deletions packages/ui/src/components/editor/panelItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import EaseCurveControlPoints from '@tokens-studio/icons/EaseCurveControlPoints.
import EditPencil from '@tokens-studio/icons/EditPencil.js';
import Eye from '@tokens-studio/icons/Eye.js';
import FillColor from '@tokens-studio/icons/FillColor.js';
import Search from '@tokens-studio/icons/Search.js';
import SigmaFunction from '@tokens-studio/icons/SigmaFunction.js';
import SoundHigh from '@tokens-studio/icons/SoundHigh.js';
import Star from '@tokens-studio/icons/Star.js';
Expand All @@ -35,6 +36,7 @@ const icons = {
logic: <CodeBrackets />,
math: <Calculator />,
preview: <Eye />,
search: <Search />,
series: <SigmaFunction />,
string: <Text />,
typing: <Type />,
Expand Down

0 comments on commit 3abfe5f

Please sign in to comment.