From 776c0b5f0e307498f6d841e9c6922efe892b1489 Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Fri, 25 Jun 2021 13:09:53 +0100 Subject: [PATCH] Fixes types for code editor --- __tests__/__snapshots__/code.test.ts.snap | 175 ++++++++++++++++++++++ __tests__/code.test.ts | 137 +++++++++++++++++ components/code-panel/code-editor.tsx | 23 ++- components/code-panel/types-import.ts | 65 +++++--- scripts/type-gen.js | 40 ++++- state/shape-styles.ts | 2 +- types.ts | 8 +- 7 files changed, 415 insertions(+), 35 deletions(-) diff --git a/__tests__/__snapshots__/code.test.ts.snap b/__tests__/__snapshots__/code.test.ts.snap index b08240dfa..26ec8ccab 100644 --- a/__tests__/__snapshots__/code.test.ts.snap +++ b/__tests__/__snapshots__/code.test.ts.snap @@ -2,6 +2,181 @@ exports[`selection creates a code control: generated code controls from code 1`] = `Object {}`; +exports[`selection generates a draw shape: generated draw from code 1`] = ` +Array [ + Object { + "childIndex": 1, + "id": "test-draw", + "isAspectRatioLocked": false, + "isGenerated": true, + "isHidden": false, + "isLocked": false, + "name": "Test draw", + "parentId": "page1", + "point": Array [ + 0, + 0, + ], + "points": Array [ + Array [ + 100, + 100, + ], + Array [ + 200, + 200, + ], + Array [ + 300, + 300, + ], + ], + "rotation": 0, + "style": Object { + "color": "Red", + "dash": "Dotted", + "isFilled": false, + "size": "Medium", + }, + "type": "draw", + }, +] +`; + +exports[`selection generates a rectangle shape: generated rectangle from code 1`] = ` +Array [ + Object { + "childIndex": 1, + "id": "test-rectangle", + "isAspectRatioLocked": false, + "isGenerated": true, + "isHidden": false, + "isLocked": false, + "name": "Test Rectangle", + "parentId": "page1", + "point": Array [ + 100, + 100, + ], + "radius": 2, + "rotation": 0, + "size": Array [ + 200, + 200, + ], + "style": Object { + "color": "Red", + "dash": "Dotted", + "isFilled": false, + "size": "Medium", + }, + "type": "rectangle", + }, +] +`; + +exports[`selection generates an arrow shape: generated draw from code 1`] = ` +Array [ + Object { + "bend": 0, + "childIndex": 1, + "decorations": Object { + "end": "Arrow", + "middle": null, + "start": null, + }, + "handles": Object { + "bend": Object { + "id": "bend", + "index": 2, + "point": Array [ + 0, + 0, + ], + }, + "end": Object { + "id": "end", + "index": 1, + "point": Array [ + 0, + 0, + ], + }, + "start": Object { + "id": "start", + "index": 0, + "point": Array [ + 0, + 0, + ], + }, + }, + "id": "test-draw", + "isAspectRatioLocked": false, + "isGenerated": true, + "isHidden": false, + "isLocked": false, + "name": "Test draw", + "parentId": "page1", + "point": Array [ + 0, + 0, + ], + "points": Array [ + Array [ + 100, + 100, + ], + Array [ + 200, + 200, + ], + Array [ + 300, + 300, + ], + ], + "rotation": 0, + "style": Object { + "color": "Red", + "dash": "Dotted", + "isFilled": false, + "size": "Medium", + }, + "type": "arrow", + }, +] +`; + +exports[`selection generates an ellipse shape: generated ellipse from code 1`] = ` +Array [ + Object { + "childIndex": 1, + "id": "test-ellipse", + "isAspectRatioLocked": false, + "isGenerated": true, + "isHidden": false, + "isLocked": false, + "name": "Test ellipse", + "parentId": "page1", + "point": Array [ + 100, + 100, + ], + "radiusX": 100, + "radiusY": 200, + "rotation": 0, + "style": Object { + "color": "Red", + "dash": "Dotted", + "isFilled": false, + "size": "Medium", + }, + "type": "ellipse", + }, +] +`; + exports[`selection generates shapes: generated rectangle from code 1`] = ` Array [ Object { diff --git a/__tests__/code.test.ts b/__tests__/code.test.ts index c4f837eb2..fa5e604d1 100644 --- a/__tests__/code.test.ts +++ b/__tests__/code.test.ts @@ -131,5 +131,142 @@ describe('selection', () => { expect(state.data.document.code[state.data.currentCodeFileId].code).toBe( code ) + + state.send('TOGGLED_READ_ONLY').send('SAVED_CODE', { code: '' }) + + expect(state.data.document.code[state.data.currentCodeFileId].code).toBe('') + }) + + /* --------------------- Methods -------------------- */ + + it('moves shape to front', async () => { + null + }) + + it('moves shape forward', async () => { + null + }) + + it('moves shape backward', async () => { + null + }) + + it('moves shape to back', async () => { + null + }) + + it('rotates a shape', async () => { + null + }) + + it('rotates a shape by a delta', async () => { + null + }) + + it('translates a shape', async () => { + null + }) + + it('translates a shape by a delta', async () => { + null + }) + + /* --------------------- Shapes --------------------- */ + + it('generates a rectangle shape', async () => { + state.send('CLEARED_PAGE') + const code = ` + const rectangle = new Rectangle({ + id: "test-rectangle", + name: 'Test Rectangle', + point: [100, 100], + size: [200, 200], + style: { + size: SizeStyle.Medium, + color: ColorStyle.Red, + dash: DashStyle.Dotted, + }, + }) + ` + + const { controls, shapes } = await generateFromCode(state.data, code) + + state.send('GENERATED_FROM_CODE', { controls, shapes }) + + expect(getShapes(state.data)).toMatchSnapshot( + 'generated rectangle from code' + ) + }) + + it('changes a rectangle size', async () => { + null + }) + + it('generates an ellipse shape', async () => { + state.send('CLEARED_PAGE') + const code = ` + const ellipse = new Ellipse({ + id: 'test-ellipse', + name: 'Test ellipse', + point: [100, 100], + radiusX: 100, + radiusY: 200, + style: { + size: SizeStyle.Medium, + color: ColorStyle.Red, + dash: DashStyle.Dotted, + }, + }) + ` + + const { controls, shapes } = await generateFromCode(state.data, code) + + state.send('GENERATED_FROM_CODE', { controls, shapes }) + + expect(getShapes(state.data)).toMatchSnapshot('generated ellipse from code') + }) + + it('generates a draw shape', async () => { + state.send('CLEARED_PAGE') + const code = ` + const ellipse = new Draw({ + id: 'test-draw', + name: 'Test draw', + points: [[100, 100], [200,200], [300,300]], + style: { + size: SizeStyle.Medium, + color: ColorStyle.Red, + dash: DashStyle.Dotted, + }, + }) + ` + + const { controls, shapes } = await generateFromCode(state.data, code) + + state.send('GENERATED_FROM_CODE', { controls, shapes }) + + expect(getShapes(state.data)).toMatchSnapshot('generated draw from code') + }) + + it('generates an arrow shape', async () => { + state.send('CLEARED_PAGE') + const code = ` + const ellipse = new Arrow({ + id: 'test-draw', + name: 'Test draw', + points: [[100, 100], [200,200], [300,300]], + style: { + size: SizeStyle.Medium, + color: ColorStyle.Red, + dash: DashStyle.Dotted, + }, + }) + ` + + const { controls, shapes } = await generateFromCode(state.data, code) + + state.send('GENERATED_FROM_CODE', { controls, shapes }) + + expect(getShapes(state.data)).toMatchSnapshot('generated draw from code') }) }) diff --git a/components/code-panel/code-editor.tsx b/components/code-panel/code-editor.tsx index 1111720ba..10a033a26 100644 --- a/components/code-panel/code-editor.tsx +++ b/components/code-panel/code-editor.tsx @@ -43,7 +43,9 @@ export default function CodeEditor({ const rMonaco = useRef(null) const handleBeforeMount = useCallback((monaco: Monaco) => { - if (monacoRef) monacoRef.current = monaco + if (monacoRef) { + monacoRef.current = monaco + } rMonaco.current = monaco monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ @@ -52,26 +54,37 @@ export default function CodeEditor({ strict: false, noLib: true, lib: ['es6'], - target: monaco.languages.typescript.ScriptTarget.ES2015, + target: monaco.languages.typescript.ScriptTarget.ES2016, allowNonTsExtensions: true, }) monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ allowJs: true, - checkJs: false, - strict: false, + checkJs: true, + strict: true, noLib: true, lib: ['es6'], - target: monaco.languages.typescript.ScriptTarget.ES2015, + target: monaco.languages.typescript.ScriptTarget.ES2016, allowNonTsExtensions: true, }) monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true) monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true) + monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({ + noSemanticValidation: false, + noSyntaxValidation: false, + }) + + monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ + noSemanticValidation: false, + noSyntaxValidation: false, + }) + monaco.languages.typescript.typescriptDefaults.addExtraLib( typesImport.content ) + monaco.languages.typescript.javascriptDefaults.addExtraLib( typesImport.content ) diff --git a/components/code-panel/types-import.ts b/components/code-panel/types-import.ts index 95ec98839..8f29f3b65 100644 --- a/components/code-panel/types-import.ts +++ b/components/code-panel/types-import.ts @@ -1,3 +1,5 @@ +/* eslint-disable */ + // HEY! DO NOT MODIFY THIS FILE. THE CONTENTS OF THIS FILE // ARE AUTO-GENERATED BY A SCRIPT AT: /scripts/type-gen.js // ANY CHANGES WILL BE LOST WHEN THE SCRIPT RUNS AGAIN! @@ -5,7 +7,14 @@ export default { name: 'types.ts', content: ` - + +type Partial = { [P in keyof T]?: T[P]; }; + +type Omit = Pick>; + +type DeepPartial = { + [P in keyof T]?: DeepPartial; +}; @@ -141,8 +150,12 @@ interface GroupShape extends BaseShape { size: number[] } -type ShapeProps = Partial> & { - style?: Partial +// type DeepPartial = { +// [P in keyof T]?: DeepPartial +// } + +type ShapeProps = { + [P in keyof T]?: P extends 'style' ? Partial : T[P] } type MutableShape = @@ -1240,10 +1253,10 @@ interface ShapeUtility { P: number[], side: number ): number[] { - const B = vec.lrp(C, P, 0.5), - r1 = vec.dist(C, B), - delta = vec.sub(B, C), - d = vec.len(delta) + const B = Vec.lrp(C, P, 0.5), + r1 = Vec.dist(C, B), + delta = Vec.sub(B, C), + d = Vec.len(delta) if (!(d <= r + r1 && d >= Math.abs(r - r1))) { return @@ -1251,11 +1264,11 @@ interface ShapeUtility { const a = (r * r - r1 * r1 + d * d) / (2.0 * d), n = 1 / d, - p = vec.add(C, vec.mul(delta, a * n)), + p = Vec.add(C, Vec.mul(delta, a * n)), h = Math.sqrt(r * r - a * a), - k = vec.mul(vec.per(delta), h * n) + k = Vec.mul(Vec.per(delta), h * n) - return side === 0 ? vec.add(p, k) : vec.sub(p, k) + return side === 0 ? Vec.add(p, k) : Vec.sub(p, k) } /** @@ -1274,8 +1287,8 @@ interface ShapeUtility { C1: number[], r1: number ): number[][] { - const a0 = vec.angle(C0, C1) - const d = vec.dist(C0, C1) + const a0 = Vec.angle(C0, C1) + const d = Vec.dist(C0, C1) // Circles are overlapping, no tangents if (d < Math.abs(r1 - r0)) return @@ -1303,8 +1316,8 @@ interface ShapeUtility { r: number, P: number[] ): number[] { - const v = vec.sub(C, P) - return vec.sub(C, vec.mul(vec.div(v, vec.len(v)), r)) + const v = Vec.sub(C, P) + return Vec.sub(C, Vec.mul(Vec.div(v, Vec.len(v)), r)) } static det( @@ -1433,7 +1446,7 @@ interface ShapeUtility { * @param B */ static getSweep(C: number[], A: number[], B: number[]): number { - return Utils.angleDelta(vec.angle(C, A), vec.angle(C, B)) + return Utils.angleDelta(Vec.angle(C, A), Vec.angle(C, B)) } /** @@ -1571,7 +1584,7 @@ interface ShapeUtility { return Array.from(Array(steps)) .map((_, i) => ease(i / steps)) - .map((t) => [...vec.lrp(a, b, t), (1 - t) / 2]) + .map((t) => [...Vec.lrp(a, b, t), (1 - t) / 2]) } static getRayRayIntersection( @@ -1580,8 +1593,8 @@ interface ShapeUtility { p1: number[], n1: number[] ): number[] { - const p0e = vec.add(p0, n0), - p1e = vec.add(p1, n1), + const p0e = Vec.add(p0, n0), + p1e = Vec.add(p1, n1), m0 = (p0e[1] - p0[1]) / (p0e[0] - p0[0]), m1 = (p1e[1] - p1[1]) / (p1e[0] - p1[0]), b0 = p0[1] - m0 * p0[0], @@ -2470,6 +2483,20 @@ class VectorControl extends Control { } -declare const controls: {[key:string]: any} = {} +const codeShapes = new Set>() +const controls: Record = {} +const defaultStyle: ShapeStyles = { + color: ColorStyle.Black, + size: SizeStyle.Medium, + isFilled: false, + dash: DashStyle.Solid, +} +const uniqueId = () => '' +const codeControls = new Set([]) + +declare function createShape(type: ShapeType, shape: Shape): any +declare function getShapeUtils(shape: T): any +declare function getOrderedShapes(): CodeShape[] + `, } diff --git a/scripts/type-gen.js b/scripts/type-gen.js index 7bef5a00e..2fdf968c4 100644 --- a/scripts/type-gen.js +++ b/scripts/type-gen.js @@ -27,6 +27,7 @@ async function inlineFileContents(path) { .replaceAll('/* ----------------- Start Copy Here ---------------- */', '') .replaceAll('export default', '') .replaceAll('export ', '') + .replaceAll('vec.', 'Vec.') } async function copyTypesToFile() { @@ -34,15 +35,24 @@ async function copyTypesToFile() { const content = ` -// HEY! DO NOT MODIFY THIS FILE. THE CONTENTS OF THIS FILE -// ARE AUTO-GENERATED BY A SCRIPT AT: /scripts/type-gen.js -// ANY CHANGES WILL BE LOST WHEN THE SCRIPT RUNS AGAIN! +/* eslint-disable */ -export default {` + + // HEY! DO NOT MODIFY THIS FILE. THE CONTENTS OF THIS FILE + // ARE AUTO-GENERATED BY A SCRIPT AT: /scripts/type-gen.js + // ANY CHANGES WILL BE LOST WHEN THE SCRIPT RUNS AGAIN! + + export default {` + ` - name: "types.ts", - content: \` - + name: "types.ts", + content: \` + +type Partial = { [P in keyof T]?: T[P]; }; + +type Omit = Pick>; + +type DeepPartial = { + [P in keyof T]?: DeepPartial; +}; ${await inlineFileContents('/types.ts')} @@ -72,7 +82,21 @@ ${await inlineFileContents('/state/code/rectangle.ts')} ${await inlineFileContents('/state/code/control.ts')} -declare const controls: {[key:string]: any} = {} +const codeShapes = new Set>() +const controls: Record = {} +const defaultStyle: ShapeStyles = { + color: ColorStyle.Black, + size: SizeStyle.Medium, + isFilled: false, + dash: DashStyle.Solid, +} +const uniqueId = () => '' +const codeControls = new Set([]) + +declare function createShape(type: ShapeType, shape: Shape): any +declare function getShapeUtils(shape: T): any +declare function getOrderedShapes(): CodeShape[] + \`}` await fs.writeFile( diff --git a/state/shape-styles.ts b/state/shape-styles.ts index 98713f20d..a83746e64 100644 --- a/state/shape-styles.ts +++ b/state/shape-styles.ts @@ -85,7 +85,7 @@ export function getShapeStyle( } } -export const defaultStyle = { +export const defaultStyle: ShapeStyles = { color: ColorStyle.Black, size: SizeStyle.Medium, isFilled: false, diff --git a/types.ts b/types.ts index 7c82fd2b4..bcacdbf4b 100644 --- a/types.ts +++ b/types.ts @@ -191,8 +191,12 @@ export interface GroupShape extends BaseShape { size: number[] } -export type ShapeProps = Partial> & { - style?: Partial +// type DeepPartial = { +// [P in keyof T]?: DeepPartial +// } + +export type ShapeProps = { + [P in keyof T]?: P extends 'style' ? Partial : T[P] } export type MutableShape =