Skip to content

Commit

Permalink
Add source viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
spaaaacccee committed Apr 19, 2024
1 parent 296964c commit 6b9336e
Show file tree
Hide file tree
Showing 39 changed files with 37,097 additions and 126 deletions.
6 changes: 3 additions & 3 deletions client/src/components/generic/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ export function Select<T extends string>({
<Menu
{...bindMenu(state)}
anchorOrigin={{
horizontal: "center",
vertical: -itemHeight(sm) * index - padding,
horizontal: "left",
vertical: "bottom",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
horizontal: "left",
}}
>
{map(items, ({ value: v, label, disabled, icon }) => (
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/generic/SelectMulti.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ export function SelectMulti<T extends string>({
<Menu
{...bindMenu(state)}
anchorOrigin={{
horizontal: "center",
vertical: -itemHeight(sm) * index - padding,
horizontal: "left",
vertical: "bottom",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
horizontal: "left",
}}
>
{map(items, ({ value: v, label, disabled }) => (
Expand Down
10 changes: 4 additions & 6 deletions client/src/components/layer-editor/TracePreview.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Box } from "@mui/material";
import { ScriptViewer } from "components/script-editor/ScriptEditor";
import beautify from "json-beautify";
import { dump } from "js-yaml";
import { take } from "lodash";
import { Trace } from "protocol";

export function TracePreview({
trace,
language = "json",
language = "yaml",
}: {
trace?: Trace;
language?: string;
Expand All @@ -18,14 +18,12 @@ export function TracePreview({
language={language}
value={
trace
? beautify(
? dump(
{
...trace,
events: take(trace.events, 10),
},
null as any,
2,
1
{ noCompatMode: true }
)
: "No data"
}
Expand Down
13 changes: 13 additions & 0 deletions client/src/components/renderer/parser-v140/Context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Dictionary as Dict } from "lodash";
import { CompiledComponent, EventContext, Properties as Props } from "protocol";

export type Context<T extends Props = Record<string, any>> =
| PropMap<T>
| EventContext
| CompiledComponent<string, T>;

export type Key<T> = Extract<keyof T, string>;

export type Prop<T extends Props> = (ctx: Context<T>) => T[keyof T];

export type PropMap<T extends Props> = Dict<Prop<T>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest";
import { applyScope } from "../applyScope";

describe("applyScope", () => {
it("correctly propagates context", () => {
const result = applyScope<{ a: number; b: number }>(
{ a: ({ b }) => b() + 1 },
{ a: ({ a }) => a() * 3 }
);
expect(result.a({ b: 3 })).toEqual(12);
expect(result.a({ b: () => 3 })).toEqual(12);
});
it("correctly chains contexts", () => {
const result = applyScope<{ a: number; b: number }>(
{ a: ({ b }) => b() + 1 },
{ a: ({ a }) => a() * 3 }
);
const result2 = applyScope<{ a: number; b: number }>(
{ b: ({ b }) => b() + 3 },
result
);
expect(result2.a({ b: 3 })).toEqual(21);
expect(result2.a({ b: () => 3 })).toEqual(21);
});
});
33 changes: 33 additions & 0 deletions client/src/components/renderer/parser-v140/__tests__/parse.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import { Context } from "../Context";
import { parse } from "../parse";

describe("parse", () => {
const ctx: Context = { a: 1 };
const result = parse<{
rect: { x: number; y: string; z: number; w: number };
}>([{ $: "rect2", y: 4 }], {
rect2: [
{ $: "rect", x: 2, y: "{{ctx.y + 1}}" },
{ $: "rect", y: "{{1 + 2}} a", z: "{{1 + 2}}", w: "{{ctx.a}}" },
],
})(ctx);
it("flattens correctly", () => {
expect(result).toMatchObject([{ $: "rect" }, { $: "rect" }]);
});
it("creates a deferred property", () => {
expect(result[0].x).toEqual(2);
});
it("evaluates a computed property", () => {
expect(result[1].z).toEqual(3);
});
it("evaluates a template string computed property", () => {
expect(result[1].y).toEqual("3 a");
});
it("reads from context", () => {
expect(result[1].w).toEqual(1);
});
it("propagates context correctly", () => {
expect(result[0].y).toEqual(5);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { describe, expect, it } from "vitest";
import { parseProperty } from "../parseProperty";

describe("parseProperty", () => {
it("parses a simple property", () => {
const f = parseProperty(3);
expect(f({})).toEqual(3);
});
it("parses nested array", () => {
const f = parseProperty([1, 2, [1, 2]]);
expect(f({})).toEqual([1, 2, [1, 2]]);
});
it("parses nested object", () => {
const f = parseProperty({ a: 1, b: { x: 1, y: 2 } });
expect(f({})).toEqual({ a: 1, b: { x: 1, y: 2 } });
});
it("parses mixed object", () => {
const f = parseProperty({ a: 1, b: [1, 2] });
expect(f({})).toEqual({ a: 1, b: [1, 2] });
});
it("parses computed property", () => {
const f = parseProperty("{{1}}");
expect(f({})).toEqual(1);
});
it("parses computed with context", () => {
const f = parseProperty("{{ctx.a}}");
expect(f({ a: 1 })).toEqual(1);
});
it("parses computed with nested context", () => {
const f = parseProperty("{{ctx.a.b[0]}}");
expect(f({ a: { b: [1] } })).toEqual(1);
});
it("parses string coercion property", () => {
const f = parseProperty("a {{ctx.a}}");
expect(f({ a: 2 })).toEqual("a 2");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, expect, it } from "vitest";
import { parseString } from "../parseString";

describe("parseString", () => {
it("parses single terms", () => {
const f = parseString("{{1}}");
expect(f({})).toEqual(1);
});
it("parses multiple terms", () => {
const f = parseString("{{false && true}}");
expect(f({})).toEqual(false);
});
it("coerces templates into strings", () => {
const f = parseString("B{{'a' + + 'a'}}a");
expect(f({})).toEqual("BaNaNa");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { describe, expect, it } from "vitest";
import { parseToken } from "../parseToken";

describe("parseToken", () => {
it("parses expressions", () => {
const f = parseToken("1 + ctx.a + ctx.b");
expect(
f({
a: 2,
b: 3,
})
).toEqual(6);
});
});
13 changes: 13 additions & 0 deletions client/src/components/renderer/parser-v140/applyScope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Properties as Props } from "protocol";
import { Context, PropMap } from "./Context";
import { mapProperties } from "./mapProperties";

export function applyScope<T extends Props>(
scope: PropMap<T>,
props: PropMap<T>
): PropMap<T> {
return Object.setPrototypeOf(
mapProperties(props, (prop) => (provided: Context<T>) => prop(scope)),
scope
);
}
1 change: 1 addition & 0 deletions client/src/components/renderer/parser-v140/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { parse } from "./parse";
21 changes: 21 additions & 0 deletions client/src/components/renderer/parser-v140/mapProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ObjectIterator } from "lodash";
import { Properties as Props } from "protocol";
import { Context } from "./Context";

/**
* Iterates over the properties of a scope or component,
* ignoring the component name property `$`.
* @param context The properties to iterate against.
* @param f
* @returns
*/
export function mapProperties<T extends Props, TResult>(
context: Context<T> = {},
f: ObjectIterator<Context<T>, TResult>
): Context<T> & { $: string } {
const out: any = {};
for (const key of Object.keys(context)) {
out[key] = key === "$" ? context[key] : f(context[key], key, context);
}
return Object.setPrototypeOf(out, context);
}
17 changes: 17 additions & 0 deletions client/src/components/renderer/parser-v140/normalize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { isFunction } from "lodash";
import { Properties as Props } from "protocol";
import { Context, PropMap } from "./Context";
import { mapProperties } from "./mapProperties";
import { parseProperty } from "./parseProperty";

export function normalize<T extends Props>(
context: Context<T> = {}
): PropMap<T> {
return mapProperties(context, (v) => (isFunction(v) ? v : parseProperty(v)));
}

export function normalizeConstant(obj: Context<any> = {}) {
return new Proxy(obj, {
get: (obj, p) => (typeof obj[p] === "function" ? obj[p] : () => obj[p]),
});
}
74 changes: 74 additions & 0 deletions client/src/components/renderer/parser-v140/parse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Dictionary as Dict, flatMap, map, mapValues, range } from "lodash";
import {
CompiledComponent,
ComponentDefinition,
ComponentDefinitionMap,
IntrinsicComponentMap,
TraceComponent,
} from "protocol";
import { Context } from "./Context";
import { applyScope } from "./applyScope";
import { mapProperties } from "./mapProperties";
import { normalize } from "./normalize";
import { parseProperty } from "./parseProperty";

function transformOne(component: TraceComponent<string, Dict<any>>) {
const { $for, $if, ...rest } = component;
if ($for || $if) {
const { $let = "i", $from = 0, $to = 1, $step = 1 } = $for ?? {};
const condition = parseProperty($if);
const from = parseProperty($from);
const to = parseProperty($to);
const step = parseProperty($step);
return ($: Context = {}) =>
!$if || condition($)
? range(from($), to($), step($)).map((i) => {
const scoped = applyScope($, normalize({ [$let]: i }));
return applyScope(scoped, normalize(rest as any));
})
: [];
} else {
return ($: Context = {}) => {
const scoped = applyScope($, normalize(rest as any));
return [scoped];
};
}
}

type Compiled<T extends string> = (
context: Context
) => Compiled<T>[] | CompiledComponent<T, Dict<any>>[];

/**
* A parser for a list of Components
* @param definition a list of Components
* @param context user injected context (from parent Components)
* @returns a list of parsed Components
*/
export function parse<T extends IntrinsicComponentMap>(
definition: ComponentDefinition<string, Dict<any>>,
components: ComponentDefinitionMap
): (
context: Context
) => CompiledComponent<Extract<keyof T, string>, Record<string, any>>[] {
const parseOne = (element: TraceComponent<string, Dict<any>>) => {
const { $ } = element;
const scoped = transformOne(element);
return $ in components
? (context: Context) =>
flatMap(scoped(context), (elem) => flatMap(store[$], (f) => f(elem)))
: (context: Context) =>
map(scoped(context), (elem) =>
Object.setPrototypeOf(
mapProperties(elem, (prop) => prop(elem)),
null
)
);
};
const store: Dict<Compiled<Extract<keyof T, string>>[]> = mapValues(
components,
(elements) => map(elements, parseOne)
);
const entry = flatMap(definition, parseOne);
return (context) => flatMap(entry, (e) => e(context));
}
25 changes: 25 additions & 0 deletions client/src/components/renderer/parser-v140/parseProperty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { map, mapValues } from "lodash";
import { Prop } from "./Context";
import { parseString } from "./parseString";
/**
* Parses a single property (recursively calling down if required)
* @param prop the value of the property to parse
* @param context additional context for the property
* @returns a Function which takes in context and returns the properties value
*/
export function parseProperty(prop: any): Prop<any> {
switch (prop?.constructor) {
case Array: {
const parsed = map(prop, parseProperty);
return (ctx) => map(parsed, (f) => f(ctx));
}
case Object: {
const parsed = mapValues(prop, parseProperty);
return (ctx) => mapValues(parsed, (f) => f(ctx));
}
case String:
return parseString(prop);
default:
return () => prop;
}
}
Loading

0 comments on commit 6b9336e

Please sign in to comment.