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 7, 2023
1 parent 1599b63 commit 1ad2316
Show file tree
Hide file tree
Showing 32 changed files with 413 additions and 487 deletions.
86 changes: 37 additions & 49 deletions packages/toolpad-app/src/appDom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { generateKeyBetween } from 'fractional-indexing';
import {
NodeId,
NodeReference,
ConstantAttrValue,
BindableAttrValue,
BindableAttrValues,
SecretAttrValue,
BindableAttrEntries,
JsExpressionAttrValue,
EnvAttrValue,
} from '@mui/toolpad-core';
import invariant from 'invariant';
import { BoxProps } from '@mui/material';
Expand Down Expand Up @@ -73,9 +74,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 +85,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 +125,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 +148,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 +239,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 +544,7 @@ export function createElement<P>(
name: name || uncapitalize(component),
props,
attributes: {
component: createConst(component),
component,
},
layout,
});
Expand Down Expand Up @@ -612,7 +609,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 +732,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 +855,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 as JsExpressionAttrValue).$$jsExpression || (prop as EnvAttrValue).$$env) {
throw new Error(`trying to unbox a non-constant prop value`);
}
return prop.value;
return prop as T;
}

export function fromConstPropValues<P>(props: BindableAttrValues<P>): Partial<P> {
Expand Down Expand Up @@ -1081,7 +1069,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 +1117,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
72 changes: 53 additions & 19 deletions packages/toolpad-app/src/bindings.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,60 @@
import { BindableAttrValue } from '@mui/toolpad-core';
import {
BindableAttrValue,
EnvAttrValue,
JsExpressionAction,
JsExpressionAttrValue,
SecretAttrValue,
} from '@mui/toolpad-core';
import { NavigationAction } from './server/schema';

export function toBindable<V>(
value: V | { $$jsExpression: string } | { $$env: string },
): BindableAttrValue<V> {
if (value && typeof value === 'object' && typeof (value as any).$$jsExpression === 'string') {
return { type: 'jsExpression', value: (value as any).$$jsExpression };
type BindingType =
| 'const'
| 'jsExpression'
| 'env'
| 'jsExpressionAction'
| 'navigationAction'
| 'secret';

export function getBindingType<V>(binding: BindableAttrValue<V>): BindingType {
if ((binding as JsExpressionAttrValue).$$jsExpression) {
return 'jsExpression';
}
if ((binding as EnvAttrValue).$$env) {
return 'env';
}
if ((binding as JsExpressionAction).$$jsExpressionAction) {
return 'jsExpressionAction';
}
if ((binding as NavigationAction).$$navigationAction) {
return 'navigationAction';
}
if (value && typeof value === 'object' && typeof (value as any).$$env === 'string') {
return { type: 'env', value: (value as any).$$env };
if ((binding as SecretAttrValue<V>).$$secret) {
return 'secret';
}
return { type: 'const', value: value as V };
return 'const';
}

export function fromBindable<V>(bindable: BindableAttrValue<V>) {
switch (bindable.type) {
case 'const':
return bindable.value;
case 'jsExpression':
return { $$jsExpression: bindable.value };
case 'env':
return { $$env: bindable.value };
default:
throw new Error(`Unsupported bindable "${bindable.type}"`);
export function getBindingValue<V>(binding: BindableAttrValue<V>):
| V
| string
| {
page: string;
parameters?: Record<string, unknown>;
} {
if ((binding as JsExpressionAttrValue).$$jsExpression) {
return (binding as JsExpressionAttrValue).$$jsExpression;
}
if ((binding as EnvAttrValue).$$env) {
return (binding as EnvAttrValue).$$env;
}
if ((binding as JsExpressionAction).$$jsExpressionAction) {
return (binding as JsExpressionAction).$$jsExpressionAction;
}
if ((binding as NavigationAction).$$navigationAction) {
return (binding as NavigationAction).$$navigationAction;
}
if ((binding as SecretAttrValue<V>).$$secret) {
return (binding as SecretAttrValue<V>).$$secret;
}
return binding as V;
}
Loading

0 comments on commit 1ad2316

Please sign in to comment.