Skip to content

Commit 023f130

Browse files
committed
Update forms sources to match Omigost internals
1 parent 8e827dd commit 023f130

File tree

4 files changed

+217
-18
lines changed

4 files changed

+217
-18
lines changed

src/BlinkformsClient.tsx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import {
2+
NodeSchema,
3+
SchemaParserConfigOpt,
4+
RootNode,
5+
} from "./schemaTypes";
6+
7+
import { transformSchemaIntoTree } from "./schemaParser";
8+
9+
export type BlinkformsStateTransformer = (state: NodeState<any>, root: RootNode) => NodeState<any>;
10+
export type BlinkformsContextUpdateHandler = (context: FormContext, source: NodeAny) => void;
11+
export type BlinkformsContextTransformer = (fn: (context: FormContext) => FormContext, source: NodeAny) => void;
12+
13+
export default class BlinkformsClient {
14+
15+
tree: RootNode;
16+
state: NodeState<any>;
17+
18+
stateTransformers: Array<BlinkformsSetStateHandler>;
19+
contextUpdateHandlers: Array<BlinkformsContextUpdateHandler>;
20+
contextTransformers: Array<BlinkformsContextTransformer>;
21+
rootConfig: SchemaParserConfigOpt;
22+
23+
constructor() {
24+
this.tree = null;
25+
this.state = null;
26+
27+
this.stateTransformers = [];
28+
this.contextUpdateHandlers = [];
29+
this.contextTransformers = [];
30+
this.rootConfig = {};
31+
}
32+
33+
configure(conf: SchemaParserConfigOpt) {
34+
this.rootConfig = ({ ...this.rootConfig, ...conf });
35+
}
36+
37+
registerHandlers(handlers) {
38+
const newHandlers = { ...this.rootConfig.handlers };
39+
Object.keys(handlers).forEach((key) => {
40+
newHandlers[key] = { ...newHandlers[key], ...handlers[key] };
41+
});
42+
43+
this.rootConfig = ({
44+
...this.rootConfig,
45+
handlers: {
46+
...this.rootConfig.handlers,
47+
...newHandlers,
48+
},
49+
})
50+
}
51+
52+
handleFormStateUpdate(state: NodeState<any>, root: RootNode) {
53+
this.state = state;
54+
this.stateTransformers.forEach(t => {
55+
this.state = t(this.state, root);
56+
});
57+
}
58+
59+
handleFormContextUpdate(context: FormContext, source: NodeAny) {
60+
this.contextUpdateHandlers.forEach(t => {
61+
t(context, source);
62+
});
63+
}
64+
65+
handleFormContextMapping(fn: (context: FormContext) => FormContext, source: NodeAny) {
66+
this.contextTransformers.forEach(t => {
67+
t(context, source);
68+
});
69+
}
70+
71+
render<S extends NodeSchema>(schema: S, options: SchemaParserConfigOpt = null): BlinkformsClient {
72+
this.tree = transformSchemaIntoTree(schema, null, {
73+
rootSetState: (state, root) => this.handleFormStateUpdate(state, root),
74+
rootSetContext: (context, source) => this.handleFormContextUpdate(context, source),
75+
rootModifyContext: (fn, source) => this.handleFormContextMapping(fn, source),
76+
rootState: {},
77+
...this.rootConfig,
78+
...options,
79+
});
80+
81+
return this;
82+
}
83+
84+
getTree(): RootNode {
85+
return this.tree;
86+
}
87+
88+
getState(): NodeState<any> {
89+
return this.state;
90+
}
91+
92+
};

src/defaultParserConfig.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ export const defaultParserConfig: SchemaParserConfig = {
1111
OBJECT: {
1212
default: null,
1313
},
14+
NUMBER: {
15+
default: null,
16+
},
17+
BOOLEAN: {
18+
default: null,
19+
},
20+
ARRAY: {
21+
default: null,
22+
},
1423
},
1524
rootState: null,
1625
rootSetState: () => {},

src/schemaParser.tsx

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import {
32
isNodeErrorPure,
43
Node,
@@ -16,7 +15,6 @@ import {
1615
} from "./schemaTypes";
1716

1817
import Ajv from "ajv";
19-
2018
import { defaultParserConfig } from "./defaultParserConfig";
2119

2220
export interface AjvError {
@@ -39,11 +37,18 @@ function getHandlerForType<M extends NodeSchema>(node: M, config: SchemaParserCo
3937
return config.handlers.OBJECT;
4038
case NodeType.STRING:
4139
return config.handlers.STRING;
40+
case NodeType.NUMBER:
41+
return config.handlers.NUMBER;
42+
case NodeType.BOOLEAN:
43+
return config.handlers.BOOLEAN;
44+
case NodeType.ARRAY:
45+
return config.handlers.ARRAY;
4246
case NodeType.ROOT:
4347
return null;
48+
default:
49+
throw `Unknown node type ${node.type}`;
50+
return null;
4451
}
45-
const never: never = t;
46-
return never;
4752
}
4853

4954
function createNode<M extends NodeSchema>(node: M, parentNode: NodeAny, config: SchemaParserConfig, handler: NodeHandler<any, any, M>): Node<any, any, M> {
@@ -97,7 +102,7 @@ export function transformOutputToRawData(metaOutput: any): any {
97102
if (isNodeMetaOutputValue(metaOutput)) {
98103
return transformOutputToRawData(metaOutput.__data);
99104
} else if (Array.isArray(metaOutput)) {
100-
metaOutput.map(item => transformOutputToRawData(item));
105+
return metaOutput.map(item => transformOutputToRawData(item));
101106
} else if (metaOutput instanceof Object) {
102107
const result = {};
103108
Object.keys(metaOutput).forEach(key => {
@@ -109,14 +114,31 @@ export function transformOutputToRawData(metaOutput: any): any {
109114
}
110115
}
111116

117+
function debounce(func: any, wait: number, immediate?: boolean) {
118+
let timeout;
119+
return function() {
120+
const context = this, args = arguments;
121+
const later = () => {
122+
timeout = null;
123+
if (!immediate) func.apply(context, args);
124+
};
125+
const callNow = immediate && !timeout;
126+
clearTimeout(timeout);
127+
timeout = setTimeout(later, wait);
128+
if (callNow) func.apply(context, args);
129+
};
130+
}
131+
112132
export async function validateRoot(rootNode: RootNode) {
113133

114134
const ajv = new Ajv({
115135
allErrors: true,
116136
...rootNode.getConfig().ajvOptions,
117137
});
118-
const validateSchema = ajv.compile(rootNode.getSchema() as unknown as object);
119-
const output = rootNode.getOutput();
138+
139+
const validateSchema = ajv.compile(rootNode.getJsonSchema() as unknown as object);
140+
const output = rootNode.getOutput({ enableFormat: false });
141+
120142
const data = transformOutputToRawData(output);
121143

122144
validateSchema(data);
@@ -162,11 +184,11 @@ export function transformSchemaIntoTree<M extends NodeSchema>(node: M, rootNode:
162184

163185
if (!rootNode) {
164186
rootNode = new RootNode(
165-
node, conf, recTransformSchemaIntoTree, transformOutputToRawData, validateRoot,
187+
node, conf, recTransformSchemaIntoTree, transformOutputToRawData, debounce(validateRoot, 750),
166188
);
167189
rootNode.resolve();
168190
}
169191

170192
recTransformSchemaIntoTree(node, rootNode, conf);
171193
return rootNode;
172-
}
194+
}

src/schemaTypes.tsx

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,29 @@ type ValueOf<T> = T[keyof T];
44

55
export enum NodeType {
66
STRING = "string",
7+
NUMBER = "number",
8+
BOOLEAN = "boolean",
79
OBJECT = "object",
10+
ARRAY = "array",
811
ROOT = "root",
912
}
1013

1114
export interface NodeTypeSchemas {
1215
STRING: NodeStringSchema;
16+
NUMBER: NodeNumberSchema;
17+
BOOLEAN: NodeBooleanSchema;
1318
OBJECT: NodeObjectSchema;
19+
ARRAY: NodeArraySchema;
1420
ROOT: any;
1521
}
1622

1723
export type NodeBaseSchema = {
1824
title?: string;
1925
description?: string;
2026
ui?: string;
21-
renderID?: string;
2227
[key: string]: any;
28+
formatOutput?: (output: any) => any;
29+
formatInput?: (output: any) => any;
2330
};
2431

2532
export interface NodeProperties {
@@ -36,6 +43,19 @@ export interface NodeStringSchema extends NodeBaseSchema {
3643
type: NodeType.STRING;
3744
}
3845

46+
export interface NodeNumberSchema extends NodeBaseSchema {
47+
type: NodeType.NUMBER;
48+
}
49+
50+
export interface NodeBooleanSchema extends NodeBaseSchema {
51+
type: NodeType.BOOLEAN;
52+
}
53+
54+
export interface NodeArraySchema extends NodeBaseSchema {
55+
type: NodeType.ARRAY;
56+
items: Array<NodeBaseSchema> | NodeBaseSchema;
57+
}
58+
3959
export type NodeSchema = ValueOf<NodeTypeSchemas>;
4060

4161
export type Schema = NodeObjectSchema;
@@ -70,6 +90,10 @@ export abstract class Node<
7090
return this.handler;
7191
}
7292

93+
isOutputAvailable(): boolean {
94+
return true;
95+
}
96+
7397
validateCustom(): Array<NodeError> {
7498
if (this.children.length === 0) {
7599
return [];
@@ -136,6 +160,10 @@ export abstract class Node<
136160
this.children.push(child);
137161
}
138162

163+
overrideChildren(children: Array<Node<CS, CO, CM>>) {
164+
this.children = [ ...children ];
165+
}
166+
139167
getChildren() {
140168
return this.children;
141169
}
@@ -170,7 +198,7 @@ export abstract class Node<
170198

171199
abstract render(context: FormContext): React.ReactNode;
172200

173-
getRawOutput(): NodeOutputValue<O> {
201+
getRawOutput(options): NodeOutputValue<O> {
174202
return null;
175203
}
176204

@@ -201,12 +229,22 @@ export abstract class Node<
201229
return [];
202230
}
203231

204-
getOutput(): NodeMetaOutputValue<O> {
232+
getOutput(options): NodeMetaOutputValue<O> {
233+
if ((!options || (options && options.enableFormat !== false)) && this.getSchema().formatOutput) {
234+
return {
235+
__data: this.getSchema().formatOutput(this.getRawOutput(options)),
236+
__source: this,
237+
};
238+
}
205239
return {
206-
__data: this.getRawOutput(),
240+
__data: this.getRawOutput(options),
207241
__source: this,
208242
};
209243
}
244+
245+
setValue(value: NodeOutputValue<O>) {
246+
// Do nothing
247+
}
210248
}
211249

212250
export class RootNode extends Node<any, any, NodeSchema> {
@@ -228,27 +266,27 @@ export class RootNode extends Node<any, any, NodeSchema> {
228266
this.validator(this);
229267
}
230268

231-
getOutput() {
269+
getOutput(options) {
232270
if (this.getChildren().length === 0) {
233271
return {
234272
__data: null,
235273
__source: this,
236274
};
237275
} else if (this.getChildren().length === 1) {
238276
return {
239-
__data: this.getChildren()[0].getOutput(),
277+
__data: this.getChildren()[0].getOutput(options),
240278
__source: this,
241279
};
242280
} else {
243281
return {
244-
__data: this.getChildren().map(child => child.getOutput()),
282+
__data: this.getChildren().map(child => child.getOutput(options)),
245283
__source: this,
246284
};
247285
}
248286
}
249287

250-
getData() {
251-
return this.dataTransformer(this.getOutput());
288+
getData(options?: any) {
289+
return this.dataTransformer(this.getOutput(options));
252290
}
253291

254292
render(context: FormContext) {
@@ -259,6 +297,40 @@ export class RootNode extends Node<any, any, NodeSchema> {
259297
return "root";
260298
}
261299

300+
getJsonSchema(): NodeSchema {
301+
const compatID = "id-" + (Math.random() * 1000000000);
302+
const recParse = (node, removeLabels) => {
303+
if (!node || typeof node !== "object") {
304+
return;
305+
} else if (!removeLabels && node._jsonSchemaCompat === compatID) {
306+
return;
307+
} else if (removeLabels && !node._jsonSchemaCompat) {
308+
return;
309+
}
310+
311+
if (!removeLabels) {
312+
node._jsonSchemaCompat = compatID;
313+
} else {
314+
delete node._jsonSchemaCompat;
315+
}
316+
317+
if (!removeLabels) {
318+
if (node.nullable && node.type && typeof node.type !== "object") {
319+
node.type = [node.type, "null"];
320+
}
321+
}
322+
Object.keys(node).forEach(key => {
323+
if (key !== "type") {
324+
recParse(node[key], removeLabels);
325+
}
326+
});
327+
};
328+
const schema = this.getSchema();
329+
recParse(schema, false);
330+
recParse(schema, true);
331+
return schema;
332+
}
333+
262334
// FIXME: ROOT
263335
onChildStateChanged(state: NodeState<any>, source: NodeAny, originalSource?: NodeAny) {
264336
this.setState(state);
@@ -268,6 +340,10 @@ export class RootNode extends Node<any, any, NodeSchema> {
268340
this.setStateSilently(state, source, originalSource);
269341
this.getConfig().rootSetState(this.getState(), this, originalSource);
270342
}
343+
344+
setValue(value: NodeOutputValue<any>) {
345+
this.getChildren().forEach(child => child.setValue(value));
346+
}
271347
}
272348

273349
export type NodeHandler<

0 commit comments

Comments
 (0)