Skip to content

Commit

Permalink
WIP - Making app DOM match the schema
Browse files Browse the repository at this point in the history
  • Loading branch information
apedroferreira committed Jun 6, 2023
1 parent 1599b63 commit 8b5d8ec
Show file tree
Hide file tree
Showing 24 changed files with 310 additions and 448 deletions.
84 changes: 35 additions & 49 deletions packages/toolpad-app/src/appDom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { generateKeyBetween } from 'fractional-indexing';
import {
NodeId,
NodeReference,
ConstantAttrValue,
BindableAttrValue,
BindableAttrValues,
SecretAttrValue,
Expand Down Expand Up @@ -73,9 +72,9 @@ export interface ThemeNode extends AppDomNodeBase {
export interface ConnectionNode<P = unknown> extends AppDomNodeBase {
readonly type: 'connection';
readonly attributes: {
readonly dataSource: ConstantAttrValue<string>;
readonly dataSource: string;
readonly params: SecretAttrValue<P | null>;
readonly status: ConstantAttrValue<ConnectionStatus | null>;
readonly status: ConnectionStatus | null;
};
}

Expand All @@ -84,31 +83,31 @@ export type PageDisplayMode = 'standalone' | 'shell';
export interface PageNode extends AppDomNodeBase {
readonly type: 'page';
readonly attributes: {
readonly title: ConstantAttrValue<string>;
readonly parameters?: ConstantAttrValue<[string, string][]>;
readonly module?: ConstantAttrValue<string>;
readonly display?: ConstantAttrValue<PageDisplayMode>;
readonly title: string;
readonly parameters?: [string, string][];
readonly module?: string;
readonly display?: PageDisplayMode;
};
}

export interface ElementNode<P = any> extends AppDomNodeBase {
readonly type: 'element';
readonly attributes: {
readonly component: ConstantAttrValue<string>;
readonly component: string;
};
readonly props?: BindableAttrValues<P>;
readonly layout?: {
readonly horizontalAlign?: ConstantAttrValue<BoxProps['justifyContent']>;
readonly verticalAlign?: ConstantAttrValue<BoxProps['alignItems']>;
readonly columnSize?: ConstantAttrValue<number>;
readonly horizontalAlign?: BoxProps['justifyContent'];
readonly verticalAlign?: BoxProps['alignItems'];
readonly columnSize?: number;
};
}

export interface CodeComponentNode extends AppDomNodeBase {
readonly type: 'codeComponent';
readonly attributes: {
readonly code: ConstantAttrValue<string>;
readonly isNew?: ConstantAttrValue<boolean>;
readonly code: string;
readonly isNew?: boolean;
};
}

Expand All @@ -124,18 +123,18 @@ export interface QueryNode<Q = any> extends AppDomNodeBase {
readonly type: 'query';
readonly params?: BindableAttrEntries;
readonly attributes: {
readonly mode?: ConstantAttrValue<FetchMode>;
readonly dataSource?: ConstantAttrValue<string>;
readonly connectionId: ConstantAttrValue<NodeReference | null>;
readonly query: ConstantAttrValue<Q>;
readonly transform?: ConstantAttrValue<string>;
readonly transformEnabled?: ConstantAttrValue<boolean>;
readonly mode?: FetchMode;
readonly dataSource?: string;
readonly connectionId: NodeReference | null;
readonly query: Q;
readonly transform?: string;
readonly transformEnabled?: boolean;
/** @deprecated Not necessary to be user-facing, we will expose staleTime instead if necessary */
readonly refetchOnWindowFocus?: ConstantAttrValue<boolean>;
readonly refetchOnWindowFocus?: boolean;
/** @deprecated Not necessary to be user-facing, we will expose staleTime instead if necessary */
readonly refetchOnReconnect?: ConstantAttrValue<boolean>;
readonly refetchInterval?: ConstantAttrValue<number>;
readonly cacheTime?: ConstantAttrValue<number>;
readonly refetchOnReconnect?: boolean;
readonly refetchInterval?: number;
readonly cacheTime?: number;
readonly enabled?: BindableAttrValue<boolean>;
};
}
Expand All @@ -147,9 +146,9 @@ export interface MutationNode<Q = any> extends AppDomNodeBase {
readonly type: 'mutation';
readonly params?: BindableAttrValues;
readonly attributes: {
readonly dataSource?: ConstantAttrValue<string>;
readonly connectionId: ConstantAttrValue<NodeReference | null>;
readonly query: ConstantAttrValue<Q>;
readonly dataSource?: string;
readonly connectionId: NodeReference | null;
readonly query: Q;
};
}

Expand Down Expand Up @@ -238,12 +237,8 @@ export function createId(): NodeId {
return nanoid(7) as NodeId;
}

export function createConst<V>(value: V): ConstantAttrValue<V> {
return { type: 'const', value };
}

export function createSecret<V>(value: V): SecretAttrValue<V> {
return { type: 'secret', value };
return { $$secret: value };
}

export function getMaybeNode<T extends AppDomNodeType>(
Expand Down Expand Up @@ -547,7 +542,7 @@ export function createElement<P>(
name: name || uncapitalize(component),
props,
attributes: {
component: createConst(component),
component,
},
layout,
});
Expand Down Expand Up @@ -612,7 +607,7 @@ export function getPageAncestor(dom: AppDom, node: AppDomNode): PageNode | null
*/
export function getComponentTypeNodes(dom: AppDom, componentId: string): readonly AppDomNode[] {
return Object.values(dom.nodes).filter(
(node) => isElement(node) && node.attributes.component.value === componentId,
(node) => isElement(node) && node.attributes.component === componentId,
);
}

Expand Down Expand Up @@ -735,13 +730,8 @@ export function setQueryProp<Q, K extends keyof Q>(
prop: K,
value: Q[K],
): QueryNode<Q> {
const original = node.attributes.query.value;
return setNamespacedProp(
node,
'attributes',
'query',
createConst<Q>({ ...original, [prop]: value }),
);
const original = node.attributes.query;
return setNamespacedProp(node, 'attributes', 'query', { ...original, [prop]: value });
}

export function setNodeNamespacedProp<
Expand Down Expand Up @@ -863,21 +853,17 @@ export function removeNode(dom: AppDom, nodeId: NodeId) {
});
}

export function toConstPropValue<T = any>(value: T): ConstantAttrValue<T> {
return { type: 'const', value };
}

export function fromConstPropValue(prop: undefined): undefined;
export function fromConstPropValue<T>(prop: BindableAttrValue<T>): T;
export function fromConstPropValue<T>(prop?: BindableAttrValue<T | undefined>): T | undefined;
export function fromConstPropValue<T>(prop?: BindableAttrValue<T | undefined>): T | undefined {
if (!prop) {
return undefined;
}
if (prop.type !== 'const') {
if (prop.$$jsExpression || prop.$$env) {
throw new Error(`trying to unbox a non-constant prop value`);
}
return prop.value;
return prop;
}

export function fromConstPropValues<P>(props: BindableAttrValues<P>): Partial<P> {
Expand Down Expand Up @@ -1081,7 +1067,7 @@ function createRenderTreeNode(node: AppDomNode): RenderTreeNode | null {
}

if (isQuery(node) || isMutation(node)) {
if (node.attributes.query.value) {
if (node.attributes.query) {
node = setNamespacedProp(node, 'attributes', 'query', null);
}
}
Expand Down Expand Up @@ -1129,8 +1115,8 @@ export function createDefaultDom(): AppDom {
const newPageNode = createNode(dom, 'page', {
name: 'Page 1',
attributes: {
title: createConst('Page 1'),
display: createConst('shell'),
title: 'Page 1',
display: 'shell',
},
});

Expand Down
29 changes: 19 additions & 10 deletions packages/toolpad-app/src/appDom/migrations/v1.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import invariant from 'invariant';
import { AppDom, QueryNode, createConst, isQuery, ElementNode, isElement, ref } from '..';
import { AppDom, QueryNode, isQuery, ElementNode, isElement, ref } from '..';
import { update } from '../../utils/immutability';

function migrateLegacyQueryNode(node: QueryNode<any>): QueryNode<any> {
Expand All @@ -25,11 +25,14 @@ function migrateLegacyQueryNode(node: QueryNode<any>): QueryNode<any> {
attributes: update(node.attributes, {
transformEnabled: undefined,
transform: undefined,
query: createConst({
...node.attributes.query.value,
transformEnabled: node.attributes.transformEnabled?.value,
transform: node.attributes.transform?.value,
}),
query: {
type: 'const',
value: {
...node.attributes.query.value,
transformEnabled: node.attributes.transformEnabled?.value,
transform: node.attributes.transform?.value,
},
},
}),
});
}
Expand All @@ -39,10 +42,16 @@ function migrateLegacyQueryNode(node: QueryNode<any>): QueryNode<any> {
if (typeof node.attributes.query.value?.connectionId?.value === 'string') {
return update(node, {
attributes: update(node.attributes, {
query: createConst({
...node.attributes.query.value,
connectionId: createConst(ref(node.attributes.query.value.connectionId.value)),
}),
query: {
type: 'const',
value: {
...node.attributes.query.value,
connectionId: {
type: 'const',
value: ref(node.attributes.query.value.connectionId.value),
},
},
},
}),
});
}
Expand Down
5 changes: 4 additions & 1 deletion packages/toolpad-app/src/appDom/migrations/v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ function replaceMutation(node: any): appDom.AppDomNode {
parentProp: 'queries',
attributes: {
...node.attributes,
mode: appDom.createConst('mutation'),
mode: {
type: 'const',
value: 'mutation',
},
},
};
}
Expand Down
26 changes: 0 additions & 26 deletions packages/toolpad-app/src/bindings.ts

This file was deleted.

40 changes: 20 additions & 20 deletions packages/toolpad-app/src/runtime/ToolpadApp.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function renderPage(
const page = appDom.createNode(dom, 'page', {
name: 'Page',
attributes: {
title: appDom.createConst(''),
title: '',
},
});
dom = appDom.addNode(dom, page, root, 'pages');
Expand All @@ -46,8 +46,8 @@ function renderPage(
test(`Static Text`, async () => {
renderPage((dom, page) => {
const text = appDom.createNode(dom, 'element', {
attributes: { component: appDom.createConst('Text') },
props: { value: appDom.createConst('Hello World') },
attributes: { component: 'Text' },
props: { value: 'Hello World' },
});
dom = appDom.addNode(dom, text, page, 'children');

Expand All @@ -63,7 +63,7 @@ test(`Static Text`, async () => {
test(`Default Text`, async () => {
renderPage((dom, page) => {
const text = appDom.createNode(dom, 'element', {
attributes: { component: appDom.createConst('Text') },
attributes: { component: 'Text' },
props: {},
});
dom = appDom.addNode(dom, text, page, 'children');
Expand All @@ -81,17 +81,17 @@ test(`simple databinding`, async () => {
renderPage((dom, page) => {
const textField = appDom.createNode(dom, 'element', {
name: 'theTextInput',
attributes: { component: appDom.createConst('TextField') },
attributes: { component: 'TextField' },
props: {
label: appDom.createConst('The Input'),
defaultValue: appDom.createConst('Default Text'),
label: 'The Input',
defaultValue: 'Default Text',
},
});
dom = appDom.addNode(dom, textField, page, 'children');

const text = appDom.createNode(dom, 'element', {
attributes: { component: appDom.createConst('Text') },
props: { value: { type: 'jsExpression', value: 'theTextInput.value' } },
attributes: { component: 'Text' },
props: { value: { $$jsExpression: 'theTextInput.value' } },
});
dom = appDom.addNode(dom, text, page, 'children');

Expand All @@ -115,10 +115,10 @@ test(`default Value for binding`, async () => {
renderPage((dom, page) => {
const select = appDom.createNode(dom, 'element', {
name: 'theTextInput',
attributes: { component: appDom.createConst('Select') },
attributes: { component: 'Select' },
props: {
label: appDom.createConst('The select'),
options: { type: 'jsExpression', value: 'undefined' },
label: 'The select',
options: { $$jsExpression: 'undefined' },
},
});
dom = appDom.addNode(dom, select, page, 'children');
Expand Down Expand Up @@ -147,29 +147,29 @@ test(`Databinding errors`, async () => {
let cyclic2: appDom.ElementNode;
renderPage((dom, page) => {
nonExisting = appDom.createNode(dom, 'element', {
attributes: { component: appDom.createConst('Text') },
props: { value: { type: 'jsExpression', value: 'nonExisting.foo' } },
attributes: { component: 'Text' },
props: { value: { $$jsExpression: 'nonExisting.foo' } },
});
dom = appDom.addNode(dom, nonExisting, page, 'children');

selfReferencing = appDom.createNode(dom, 'element', {
name: 'selfReferencing',
attributes: { component: appDom.createConst('Text') },
props: { value: { type: 'jsExpression', value: 'selfReferencing.value' } },
attributes: { component: 'Text' },
props: { value: { $$jsExpressione: 'selfReferencing.value' } },
});
dom = appDom.addNode(dom, selfReferencing, page, 'children');

cyclic1 = appDom.createNode(dom, 'element', {
name: 'cyclic1',
attributes: { component: appDom.createConst('Text') },
props: { value: { type: 'jsExpression', value: 'cyclic2.value' } },
attributes: { component: 'Text' },
props: { value: { $$jsExpression: 'cyclic2.value' } },
});
dom = appDom.addNode(dom, cyclic1, page, 'children');

cyclic2 = appDom.createNode(dom, 'element', {
name: 'cyclic2',
attributes: { component: appDom.createConst('Text') },
props: { value: { type: 'jsExpression', value: 'cyclic1.value' } },
attributes: { component: 'Text' },
props: { value: { $$jsExpression: 'cyclic1.value' } },
});
dom = appDom.addNode(dom, cyclic2, page, 'children');

Expand Down
Loading

0 comments on commit 8b5d8ec

Please sign in to comment.