Make updating code controls async

This commit is contained in:
Steve Ruiz 2021-06-25 12:01:22 +01:00
parent 85dc3028b4
commit 0ee26a8493
27 changed files with 212 additions and 75 deletions

View file

@ -0,0 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`selection creates a code control: generated code controls from code 1`] = `Object {}`;
exports[`selection generates shapes: 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 updates a code control: data in state after changing control 1`] = `
Object {
"test-number-control": Object {
"id": "test-number-control",
"label": "x",
"step": 1,
"type": "number",
"value": 100,
},
"test-vector-control": Object {
"id": "test-vector-control",
"isNormalized": false,
"label": "size",
"type": "vector",
"value": Array [
0,
0,
],
},
}
`;

View file

@ -33,6 +33,7 @@ describe('selection', () => {
it('generates shapes', async () => { it('generates shapes', async () => {
const code = ` const code = `
const rectangle = new Rectangle({ const rectangle = new Rectangle({
id: "test-rectangle",
name: 'Test Rectangle', name: 'Test Rectangle',
point: [100, 100], point: [100, 100],
size: [200, 200], size: [200, 200],
@ -45,20 +46,72 @@ describe('selection', () => {
` `
const { controls, shapes } = await generateFromCode(state.data, code) const { controls, shapes } = await generateFromCode(state.data, code)
state.send('GENERATED_FROM_CODE', { controls, shapes }) state.send('GENERATED_FROM_CODE', { controls, shapes })
expect(getShapes(state.data).length).toBe(1)
expect(getShapes(state.data)).toMatchSnapshot(
'generated rectangle from code'
)
}) })
it('creates a code control', () => { it('creates a code control', async () => {
null 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(state.data.codeControls).toMatchSnapshot(
'generated code controls from code'
)
}) })
it('updates a code control', () => { it('updates a code control', async () => {
null 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,
},
}) })
it('updates a code control', () => { new NumberControl({
null id: "test-number-control",
label: "x"
})
new VectorControl({
id: "test-vector-control",
label: "size"
})
`
const { controls, shapes } = await generateFromCode(state.data, code)
state.send('GENERATED_FROM_CODE', { controls, shapes })
state.send('CHANGED_CODE_CONTROL', { 'test-number-control': 100 })
expect(state.data.codeControls).toMatchSnapshot(
'data in state after changing control'
)
}) })
/* -------------------- Readonly -------------------- */ /* -------------------- Readonly -------------------- */

View file

@ -65,7 +65,6 @@ type ShapeStyles = {
interface BaseShape { interface BaseShape {
id: string id: string
seed: number
type: ShapeType type: ShapeType
parentId: string parentId: string
childIndex: number childIndex: number
@ -335,27 +334,27 @@ interface BaseCodeControl {
interface NumberCodeControl extends BaseCodeControl { interface NumberCodeControl extends BaseCodeControl {
type: ControlType.Number type: ControlType.Number
min: number
max: number
value: number value: number
step: number min?: number
format: (value: number) => number max?: number
step?: number
format?: (value: number) => number
} }
interface VectorCodeControl extends BaseCodeControl { interface VectorCodeControl extends BaseCodeControl {
type: ControlType.Vector type: ControlType.Vector
min: number
max: number
step: number
value: number[] value: number[]
isNormalized: boolean min?: number
format: (value: number[]) => number[] max?: number
step?: number
isNormalized?: boolean
format?: (value: number[]) => number[]
} }
interface TextCodeControl extends BaseCodeControl { interface TextCodeControl extends BaseCodeControl {
type: ControlType.Text type: ControlType.Text
value: string value: string
format: (value: string) => string format?: (value: string) => string
} }
interface SelectCodeControl<T extends string = ''> interface SelectCodeControl<T extends string = ''>
@ -1972,6 +1971,10 @@ interface ShapeUtility<K extends Shape> {
return this return this
} }
get id(): string {
return this._shape.id
}
/** /**
* The shape's underlying shape. * The shape's underlying shape.
*/ */
@ -2057,7 +2060,7 @@ interface ShapeUtility<K extends Shape> {
constructor(props = {} as ShapeProps<DotShape>) { constructor(props = {} as ShapeProps<DotShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
parentId: (window as any).currentPageId, parentId: (window as any).currentPageId,
type: ShapeType.Dot, type: ShapeType.Dot,
isGenerated: true, isGenerated: true,
@ -2085,7 +2088,6 @@ interface ShapeUtility<K extends Shape> {
constructor(props = {} as ShapeProps<EllipseShape>) { constructor(props = {} as ShapeProps<EllipseShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
parentId: (window as any).currentPageId, parentId: (window as any).currentPageId,
type: ShapeType.Ellipse, type: ShapeType.Ellipse,
isGenerated: true, isGenerated: true,
@ -2119,7 +2121,7 @@ interface ShapeUtility<K extends Shape> {
constructor(props = {} as ShapeProps<LineShape>) { constructor(props = {} as ShapeProps<LineShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
parentId: (window as any).currentPageId, parentId: (window as any).currentPageId,
type: ShapeType.Line, type: ShapeType.Line,
isGenerated: true, isGenerated: true,
@ -2152,7 +2154,7 @@ interface ShapeUtility<K extends Shape> {
constructor(props = {} as ShapeProps<PolylineShape>) { constructor(props = {} as ShapeProps<PolylineShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
parentId: (window as any).currentPageId, parentId: (window as any).currentPageId,
type: ShapeType.Polyline, type: ShapeType.Polyline,
isGenerated: true, isGenerated: true,
@ -2184,7 +2186,7 @@ interface ShapeUtility<K extends Shape> {
constructor(props = {} as ShapeProps<RayShape>) { constructor(props = {} as ShapeProps<RayShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Ray, type: ShapeType.Ray,
isGenerated: true, isGenerated: true,
name: 'Ray', name: 'Ray',
@ -2242,7 +2244,7 @@ interface ShapeUtility<K extends Shape> {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Arrow, type: ShapeType.Arrow,
isGenerated: false, isGenerated: false,
name: 'Arrow', name: 'Arrow',
@ -2311,7 +2313,7 @@ interface ShapeUtility<K extends Shape> {
constructor(props = {} as ShapeProps<DrawShape>) { constructor(props = {} as ShapeProps<DrawShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Draw, type: ShapeType.Draw,
isGenerated: false, isGenerated: false,
parentId: (window as any).currentPageId, parentId: (window as any).currentPageId,
@ -2339,7 +2341,7 @@ interface ShapeUtility<K extends Shape> {
constructor(props = {} as ShapeProps<RectangleShape>) { constructor(props = {} as ShapeProps<RectangleShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
parentId: (window as any).currentPageId, parentId: (window as any).currentPageId,
type: ShapeType.Rectangle, type: ShapeType.Rectangle,
isGenerated: true, isGenerated: true,

1
next-env.d.ts vendored
View file

@ -1,2 +1,3 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/types/global" /> /// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />

View file

@ -36,7 +36,7 @@ export default class Arrow extends CodeShape<ArrowShape> {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Arrow, type: ShapeType.Arrow,
isGenerated: false, isGenerated: false,
name: 'Arrow', name: 'Arrow',

View file

@ -13,17 +13,17 @@ export const codeControls = new Set<CodeControl>([])
/* ----------------- Start Copy Here ---------------- */ /* ----------------- Start Copy Here ---------------- */
export class Control<T extends CodeControl> { export class Control<T extends CodeControl> {
control: T _control: T
constructor(control: Omit<T, 'id'>) { constructor(control: T) {
this.control = { ...control, id: uniqueId() } as T this._control = { ...control }
codeControls.add(this.control) codeControls.add(this._control)
// Could there be a better way to prevent this? // Could there be a better way to prevent this?
// When updating, constructor should just bind to // When updating, constructor should just bind to
// the existing control rather than creating a new one? // the existing control rather than creating a new one?
if (!(window as any).isUpdatingCode) { if (!(window as any).isUpdatingCode) {
controls[this.control.label] = this.control.value controls[this._control.label] = this._control.value
} }
} }
@ -32,39 +32,52 @@ export class Control<T extends CodeControl> {
delete controls[this.control.label] delete controls[this.control.label]
} }
get control(): T {
return this._control
}
get id(): string {
return this.control.id
}
get value(): T['value'] { get value(): T['value'] {
return this.control.value return this.control.value
} }
set value(value: T['value']) {
this.control.value = value
}
} }
type ControlProps<T extends CodeControl> = Omit<Partial<T>, 'id' | 'type'> type ControlProps<T extends CodeControl> = Omit<Partial<T>, 'type'>
export class NumberControl extends Control<NumberCodeControl> { export class NumberControl extends Control<NumberCodeControl> {
constructor(options: ControlProps<NumberCodeControl>) { constructor(options: ControlProps<NumberCodeControl>) {
const { label = 'Number', value = 0, step = 1 } = options const { id = uniqueId(), label = 'Number', value = 0, step = 1 } = options
super({ super({
type: ControlType.Number, type: ControlType.Number,
...options, ...options,
label, label,
value, value,
step, step,
id,
}) })
} }
} }
export class VectorControl extends Control<VectorCodeControl> { export class VectorControl extends Control<VectorCodeControl> {
constructor(options: ControlProps<VectorCodeControl>) { constructor(options: ControlProps<VectorCodeControl>) {
const { label = 'Vector', value = [0, 0], isNormalized = false } = options const {
id = uniqueId(),
label = 'Vector',
value = [0, 0],
isNormalized = false,
} = options
super({ super({
type: ControlType.Vector, type: ControlType.Vector,
...options, ...options,
label, label,
value, value,
isNormalized, isNormalized,
id,
}) })
} }
} }

View file

@ -9,7 +9,7 @@ export default class Dot extends CodeShape<DotShape> {
constructor(props = {} as ShapeProps<DotShape>) { constructor(props = {} as ShapeProps<DotShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
parentId: (window as any).currentPageId, parentId: (window as any).currentPageId,
type: ShapeType.Dot, type: ShapeType.Dot,
isGenerated: true, isGenerated: true,

View file

@ -9,7 +9,7 @@ export default class Draw extends CodeShape<DrawShape> {
constructor(props = {} as ShapeProps<DrawShape>) { constructor(props = {} as ShapeProps<DrawShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Draw, type: ShapeType.Draw,
isGenerated: false, isGenerated: false,
parentId: (window as any).currentPageId, parentId: (window as any).currentPageId,

View file

@ -9,7 +9,6 @@ export default class Ellipse extends CodeShape<EllipseShape> {
constructor(props = {} as ShapeProps<EllipseShape>) { constructor(props = {} as ShapeProps<EllipseShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
parentId: (window as any).currentPageId, parentId: (window as any).currentPageId,
type: ShapeType.Ellipse, type: ShapeType.Ellipse,
isGenerated: true, isGenerated: true,

View file

@ -184,6 +184,10 @@ export default class CodeShape<T extends Shape> {
return this return this
} }
get id(): string {
return this._shape.id
}
/** /**
* The shape's underlying shape. * The shape's underlying shape.
*/ */

View file

@ -9,7 +9,7 @@ export default class Line extends CodeShape<LineShape> {
constructor(props = {} as ShapeProps<LineShape>) { constructor(props = {} as ShapeProps<LineShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
parentId: (window as any).currentPageId, parentId: (window as any).currentPageId,
type: ShapeType.Line, type: ShapeType.Line,
isGenerated: true, isGenerated: true,

View file

@ -9,7 +9,7 @@ export default class Polyline extends CodeShape<PolylineShape> {
constructor(props = {} as ShapeProps<PolylineShape>) { constructor(props = {} as ShapeProps<PolylineShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
parentId: (window as any).currentPageId, parentId: (window as any).currentPageId,
type: ShapeType.Polyline, type: ShapeType.Polyline,
isGenerated: true, isGenerated: true,

View file

@ -9,7 +9,7 @@ export default class Ray extends CodeShape<RayShape> {
constructor(props = {} as ShapeProps<RayShape>) { constructor(props = {} as ShapeProps<RayShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Ray, type: ShapeType.Ray,
isGenerated: true, isGenerated: true,
name: 'Ray', name: 'Ray',

View file

@ -9,7 +9,7 @@ export default class Rectangle extends CodeShape<RectangleShape> {
constructor(props = {} as ShapeProps<RectangleShape>) { constructor(props = {} as ShapeProps<RectangleShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
parentId: (window as any).currentPageId, parentId: (window as any).currentPageId,
type: ShapeType.Rectangle, type: ShapeType.Rectangle,
isGenerated: true, isGenerated: true,

View file

@ -218,7 +218,7 @@ export function getTranslateSnapshot(data: Data) {
const clone: Shape = { const clone: Shape = {
...shape, ...shape,
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
parentId: shape.parentId, parentId: shape.parentId,
childIndex: getChildIndexAbove(cData, shape.id), childIndex: getChildIndexAbove(cData, shape.id),
isGenerated: false, isGenerated: false,

View file

@ -61,7 +61,7 @@ const arrow = registerShapeUtils<ArrowShape>({
return { return {
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Arrow, type: ShapeType.Arrow,
isGenerated: false, isGenerated: false,
name: 'Arrow', name: 'Arrow',

View file

@ -11,7 +11,7 @@ const dot = registerShapeUtils<DotShape>({
create(props) { create(props) {
return { return {
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Dot, type: ShapeType.Dot,
isGenerated: false, isGenerated: false,
name: 'Dot', name: 'Dot',

View file

@ -23,7 +23,7 @@ const draw = registerShapeUtils<DrawShape>({
create(props) { create(props) {
return { return {
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Draw, type: ShapeType.Draw,
isGenerated: false, isGenerated: false,
name: 'Draw', name: 'Draw',

View file

@ -24,7 +24,7 @@ const ellipse = registerShapeUtils<EllipseShape>({
create(props) { create(props) {
return { return {
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Ellipse, type: ShapeType.Ellipse,
isGenerated: false, isGenerated: false,
name: 'Ellipse', name: 'Ellipse',

View file

@ -15,7 +15,7 @@ const group = registerShapeUtils<GroupShape>({
create(props) { create(props) {
return { return {
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Group, type: ShapeType.Group,
isGenerated: false, isGenerated: false,
name: 'Group', name: 'Group',

View file

@ -13,7 +13,7 @@ const line = registerShapeUtils<LineShape>({
create(props) { create(props) {
return { return {
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Line, type: ShapeType.Line,
isGenerated: false, isGenerated: false,
name: 'Line', name: 'Line',

View file

@ -16,7 +16,7 @@ const polyline = registerShapeUtils<PolylineShape>({
create(props) { create(props) {
return { return {
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Polyline, type: ShapeType.Polyline,
isGenerated: false, isGenerated: false,
name: 'Polyline', name: 'Polyline',

View file

@ -13,7 +13,7 @@ const ray = registerShapeUtils<RayShape>({
create(props) { create(props) {
return { return {
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Ray, type: ShapeType.Ray,
isGenerated: false, isGenerated: false,
name: 'Ray', name: 'Ray',

View file

@ -15,7 +15,7 @@ const rectangle = registerShapeUtils<RectangleShape>({
create(props) { create(props) {
return { return {
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Rectangle, type: ShapeType.Rectangle,
isGenerated: false, isGenerated: false,
name: 'Rectangle', name: 'Rectangle',

View file

@ -53,7 +53,7 @@ const text = registerShapeUtils<TextShape>({
create(props) { create(props) {
return { return {
id: uniqueId(), id: uniqueId(),
seed: Math.random(),
type: ShapeType.Text, type: ShapeType.Text,
isGenerated: false, isGenerated: false,
name: 'Text', name: 'Text',

View file

@ -300,7 +300,7 @@ const state = createState({
ZOOMED_CAMERA: 'zoomCamera', ZOOMED_CAMERA: 'zoomCamera',
INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize', INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
DECREASED_CODE_FONT_SIZE: 'decreaseCodeFontSize', DECREASED_CODE_FONT_SIZE: 'decreaseCodeFontSize',
CHANGED_CODE_CONTROL: 'updateControls', CHANGED_CODE_CONTROL: { to: 'updatingControls' },
TOGGLED_TOOL_LOCK: 'toggleToolLock', TOGGLED_TOOL_LOCK: 'toggleToolLock',
ZOOMED_TO_SELECTION: { ZOOMED_TO_SELECTION: {
if: 'hasSelection', if: 'hasSelection',
@ -605,6 +605,13 @@ const state = createState({
CANCELLED: { do: 'cancelSession', to: 'selecting' }, CANCELLED: { do: 'cancelSession', to: 'selecting' },
}, },
}, },
updatingControls: {
onEnter: 'updateControls',
async: {
await: 'getUpdatedShapes',
onResolve: { do: 'updateGeneratedShapes', to: 'selecting' },
},
},
}, },
}, },
editingShape: { editingShape: {
@ -1742,6 +1749,15 @@ const state = createState({
) { ) {
commands.generate(data, payload.shapes) commands.generate(data, payload.shapes)
}, },
updateGeneratedShapes(data, payload, result: { shapes: Shape[] }) {
setSelectedIds(data, [])
history.disable()
commands.generate(data, result.shapes)
history.enable()
},
setCodeControls(data, payload: { controls: CodeControl[] }) { setCodeControls(data, payload: { controls: CodeControl[] }) {
data.codeControls = Object.fromEntries( data.codeControls = Object.fromEntries(
payload.controls.map((control) => [control.id, control]) payload.controls.map((control) => [control.id, control])
@ -1757,21 +1773,6 @@ const state = createState({
for (const key in payload) { for (const key in payload) {
data.codeControls[key].value = payload[key] data.codeControls[key].value = payload[key]
} }
history.disable()
setSelectedIds(data, [])
try {
updateFromCode(
data,
data.document.code[data.currentCodeFileId].code
).then(({ shapes }) => commands.generate(data, shapes))
} catch (e) {
console.error(e)
}
history.enable()
}, },
/* -------------------- Settings -------------------- */ /* -------------------- Settings -------------------- */
@ -1896,6 +1897,14 @@ const state = createState({
return commonStyle return commonStyle
}, },
}, },
asyncs: {
async getUpdatedShapes(data) {
return updateFromCode(
data,
data.document.code[data.currentCodeFileId].code
)
},
},
}) })
export default state export default state

View file

@ -115,7 +115,6 @@ export type ShapeStyles = {
export interface BaseShape { export interface BaseShape {
id: string id: string
seed: number
type: ShapeType type: ShapeType
parentId: string parentId: string
childIndex: number childIndex: number