diff --git a/package-lock.json b/package-lock.json index cd5097738..b19e2d7cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7339,7 +7339,7 @@ }, "ansi-regex": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { @@ -7350,12 +7350,12 @@ }, "are-we-there-yet": { "version": "1.1.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "optional": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "delegates": "1.0.0", + "readable-stream": "2.2.9" } }, "asn1": { @@ -7446,7 +7446,7 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "combined-stream": { @@ -7459,17 +7459,17 @@ }, "concat-map": { "version": "0.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cryptiles": { @@ -7519,7 +7519,7 @@ }, "delegates": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, @@ -7568,7 +7568,7 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fstream": { @@ -7595,18 +7595,18 @@ }, "gauge": { "version": "2.7.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" } }, "getpass": { @@ -7628,15 +7628,15 @@ }, "glob": { "version": "7.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "graceful-fs": { @@ -7662,7 +7662,7 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, @@ -7695,16 +7695,16 @@ }, "inflight": { "version": "1.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { "version": "2.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { @@ -7715,10 +7715,10 @@ }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "is-typedarray": { @@ -7729,7 +7729,7 @@ }, "isarray": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isstream": { @@ -7815,20 +7815,20 @@ }, "minimatch": { "version": "3.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.7" } }, "minimist": { "version": "0.0.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mkdirp": { "version": "0.5.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -7836,7 +7836,7 @@ }, "ms": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "optional": true }, @@ -7861,12 +7861,12 @@ }, "nopt": { "version": "4.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.1.0", + "osenv": "0.1.4" } }, "npmlog": { @@ -7883,7 +7883,7 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { @@ -7894,27 +7894,27 @@ }, "object-assign": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "os-homedir": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, @@ -7930,7 +7930,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "performance-now": { @@ -8041,13 +8041,13 @@ }, "set-blocking": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, @@ -8086,12 +8086,12 @@ }, "string-width": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "string_decoder": { @@ -8110,15 +8110,15 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-json-comments": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, @@ -8180,7 +8180,7 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { @@ -8200,16 +8200,16 @@ }, "wide-align": { "version": "1.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "1.0.2" } }, "wrappy": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } @@ -14855,6 +14855,11 @@ } } }, + "reflect-metadata": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", + "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==" + }, "reflect.ownkeys": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", diff --git a/package.json b/package.json index c5335d446..07af692ff 100644 --- a/package.json +++ b/package.json @@ -199,6 +199,7 @@ "read-pkg": "3.0.0", "readdir-enhanced": "2.2.1", "readts": "0.2.0", + "reflect-metadata": "0.1.12", "smoothscroll-polyfill": "0.4.3", "styled-components": "3.2.5", "tag-hoc": "1.0.0", diff --git a/src/electron/create-app-message-handler.ts b/src/electron/create-app-message-handler.ts index ad82bfa9d..aa9b51cfb 100644 --- a/src/electron/create-app-message-handler.ts +++ b/src/electron/create-app-message-handler.ts @@ -4,7 +4,6 @@ import * as uuid from 'uuid'; import { checkForUpdates } from './auto-updater'; import { showError } from './show-error'; import { requestAppSafely } from './request-app'; -import { requestProjectSafely } from './request-project'; import { showContextMenu } from './show-context-menu'; import { showMainMenu } from './show-main-menu'; import * as Types from '../types'; @@ -98,13 +97,12 @@ export async function createAppMessageHandler( break; } case Message.MessageType.ChangeApp: { - injection.ephemeralStore.setAppState(message.payload.app); - const project = await requestProjectSafely(injection.sender); + /* injection.ephemeralStore.setAppState(message.payload.app); showMainMenu( { app: message.payload.app, project: project ? project.toJSON() : undefined }, { sender: injection.sender } - ); + ); */ break; } diff --git a/src/electron/create-file-message-handler.ts b/src/electron/create-file-message-handler.ts index 57ccd5dfb..3fef405a6 100644 --- a/src/electron/create-file-message-handler.ts +++ b/src/electron/create-file-message-handler.ts @@ -39,6 +39,8 @@ export async function createFileMessageHandler( path }); + ctx.project = project; + await Persistence.persist(path, project); injection.ephemeralStore.setProjectPath(path); @@ -90,6 +92,8 @@ export async function createFileMessageHandler( project.path = path; } + ctx.project = Model.Project.from(project); + injection.sender.send({ type: Message.MessageType.OpenFileResponse, id: message.id, diff --git a/src/electron/create-server-message-handler.ts b/src/electron/create-server-message-handler.ts index 91ca251b5..508f13cbb 100644 --- a/src/electron/create-server-message-handler.ts +++ b/src/electron/create-server-message-handler.ts @@ -2,6 +2,7 @@ import * as Ephemeral from './ephemeral-store'; import * as Electron from 'electron'; import * as Events from 'events'; import * as Message from '../message'; +import * as Model from '../model'; import { Sender } from '../sender/server'; import { createAppMessageHandler } from './create-app-message-handler'; @@ -12,6 +13,7 @@ import { createLibraryMessageHandler } from './create-library-message-handler'; export interface ServerMessageHandlerContext { port: undefined | number; + project: undefined | Model.Project; win: undefined | Electron.BrowserWindow; } diff --git a/src/electron/main.ts b/src/electron/main.ts index 8de83649f..09dff537d 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -1,15 +1,17 @@ import * as Electron from 'electron'; +import * as Mobx from 'mobx'; import * as Path from 'path'; import { AppContext, startApp as start } from './start-app'; const clearModule = require('clear-module'); const importFresh = require('import-fresh'); -const CONTEXT: AppContext = { +const CONTEXT: AppContext = Mobx.observable({ port: undefined, + project: undefined, sender: undefined, win: undefined -}; +}); async function main(): Promise { const StartApp = importFresh('./start-app'); diff --git a/src/electron/start-app.ts b/src/electron/start-app.ts index a433e9db2..7a763d094 100644 --- a/src/electron/start-app.ts +++ b/src/electron/start-app.ts @@ -5,6 +5,7 @@ import * as Ephemeral from './ephemeral-store'; import * as Events from 'events'; import * as getPort from 'get-port'; import * as Message from '../message'; +import * as Model from '../model'; import { Sender } from '../sender/server'; import { createServer } from '../server'; import { createWindow } from './create-window'; @@ -13,6 +14,7 @@ import * as uuid from 'uuid'; const log = require('electron-log'); export interface AppContext { + project: undefined | Model.Project; port: undefined | number; sender: undefined | Sender; win: undefined | Electron.BrowserWindow; @@ -28,7 +30,7 @@ export async function startApp(ctx: AppContext): Promise<{ emitter: Events.Event ctx.port = await (getPort({ port: ctx.port }) as Promise); const sender = new Sender(); - const server = createServer({ port: ctx.port, sender }); + const server = createServer({ port: ctx.port, sender, context: ctx }); const ephemeralStore = new Ephemeral.EphemeralStore(); const serverMessageHandler = await createServerMessageHandler(ctx, { diff --git a/src/message/message.ts b/src/message/message.ts index 1c04c78ff..bcd869d6a 100644 --- a/src/message/message.ts +++ b/src/message/message.ts @@ -46,6 +46,9 @@ export enum MessageType { KeyboardChange = 'keyboard-change', Log = 'log', Maximize = 'maximize', + MobxAdd = 'mobx-add', + MobxUpdate = 'mobx-update', + MobxSplice = 'mobx-splice', OpenExternalURL = 'open-external-url', OpenFileRequest = 'open-file-request', OpenFileResponse = 'open-file-response', @@ -116,6 +119,9 @@ export type Message = | KeyboardChange | Log | Maximize + | MobxAddMessage + | MobxUpdateMessage + | MobxSpliceMessage | OpenExternalURL | OpenFileRequest | OpenFileResponse @@ -331,3 +337,64 @@ export type Clipboard = Envelope< project: Types.SerializedProject; } >; + +export interface MobxUpdatePayload { + id: string; + name: string; + change: MobxUpdateChange; +} + +export interface MobxAddPayload { + id: string; + name: string; + memberName: string; + valueModel: Types.ModelName | undefined; + change: MobxAddChange; +} + +export interface MobxSplicePayload { + id: string; + name: string; + memberName: string; + change: MobxSpliceChange; +} + +export interface MobxAddChange { + type: Types.MobxChangeType.Add; + key: string; + newValue: T; +} + +export interface MobxSpliceChange { + type: Types.MobxChangeType.Splice; + index: number; + added: T[]; + removed: T[]; +} + +export type MobxUpdateChange = + | MobxArrayUpdatePayload + | MobxMapUpdatePayload + | MobxObjectUpdatePayload; + +export interface MobxArrayUpdatePayload { + type: Types.MobxChangeType.Update; + index: number; + newValue: T; +} + +export interface MobxMapUpdatePayload { + type: Types.MobxChangeType.Update; + key: string; + newValue: T; +} + +export interface MobxObjectUpdatePayload { + type: Types.MobxChangeType.Update; + key: string; + newValue: T; +} + +export type MobxUpdateMessage = Envelope; +export type MobxAddMessage = Envelope; +export type MobxSpliceMessage = Envelope; diff --git a/src/model/alva-app.ts b/src/model/alva-app.ts index 8a699da99..f2828e76f 100644 --- a/src/model/alva-app.ts +++ b/src/model/alva-app.ts @@ -1,7 +1,9 @@ import * as Mobx from 'mobx'; import * as Types from '../types'; +import * as uuid from 'uuid'; export interface AlvaAppInit { + id?: string; activeView: Types.AlvaView; hasFocusedInput: boolean; panes: Set; @@ -12,6 +14,9 @@ export interface AlvaAppInit { } export class AlvaApp { + public readonly model = Types.ModelName.AlvaApp; + private id: string = uuid.v4(); + @Mobx.observable private activeView: Types.AlvaView = Types.AlvaView.SplashScreen; @Mobx.observable private hoverArea: Types.HoverArea = Types.HoverArea.Chrome; @Mobx.observable private paneSelectOpen: boolean = false; @@ -35,6 +40,7 @@ export class AlvaApp { public constructor(init?: AlvaAppInit) { if (init) { + this.id = init.id || uuid.v4(); this.activeView = init.activeView; this.panes = init.panes; this.searchTerm = init.searchTerm; @@ -71,6 +77,10 @@ export class AlvaApp { return this.hoverArea; } + public getId(): string { + return this.id; + } + public getPanes(): Set { return this.panes; } @@ -158,6 +168,7 @@ export class AlvaApp { public toJSON(): Types.SerializedAlvaApp { return { + model: this.model, activeView: serializeView(this.activeView), hasFocusedInput: this.hasFocusedInput, panes: [...this.panes.values()].map(serializePane), @@ -173,7 +184,9 @@ export class AlvaApp { } @Mobx.action - public update(b: AlvaApp): void { + public update(raw: AlvaApp | Types.SerializedAlvaApp): void { + const b = raw instanceof AlvaApp ? raw : AlvaApp.from(raw); + this.activeView = b.activeView; this.panes = b.panes; this.paneSizes = b.paneSizes; diff --git a/src/model/element-action.ts b/src/model/element-action.ts index a87facb4b..97172445b 100644 --- a/src/model/element-action.ts +++ b/src/model/element-action.ts @@ -23,6 +23,8 @@ export interface ElementActionInit { } export class ElementAction { + public readonly model = Types.ModelName.ElementAction; + private elementPropertyId: string; private id: string; private userStore: UserStore; @@ -230,6 +232,7 @@ export class ElementAction { public toJSON(): Types.SerializedElementAction { return { + model: this.model, elementPropertyId: this.elementPropertyId, id: this.id, open: this.open, @@ -246,11 +249,16 @@ export class ElementAction { } @Mobx.action - public update(after: this): void { - this.id = after.id; - this.payload = after.payload; - this.payloadType = after.payloadType; - this.storeActionId = after.storeActionId; - this.storePropertyId = after.storePropertyId; + public update(raw: this | Types.SerializedElementAction): void { + const b = + raw instanceof ElementAction + ? raw + : ElementAction.from(raw, { userStore: this.userStore }); + + this.id = b.id; + this.payload = b.payload; + this.payloadType = b.payloadType; + this.storeActionId = b.storeActionId; + this.storePropertyId = b.storePropertyId; } } diff --git a/src/model/element/element-content.ts b/src/model/element/element-content.ts index 756115a58..574bb6f97 100644 --- a/src/model/element/element-content.ts +++ b/src/model/element/element-content.ts @@ -21,6 +21,8 @@ export interface ElementContentInit { } export class ElementContent { + public readonly model = Types.ModelName.ElementContent; + @Mobx.observable private elementIds: string[] = []; @Mobx.observable private forcedOpen: boolean; @Mobx.observable private highlighted: boolean; @@ -237,6 +239,7 @@ export class ElementContent { public toJSON(): Types.SerializedElementContent { return { + model: this.model, elementIds: Array.from(this.elementIds), forcedOpen: this.forcedOpen, highlighted: this.highlighted, @@ -248,7 +251,8 @@ export class ElementContent { } @Mobx.action - public update(b: ElementContent): void { + public update(raw: ElementContent | Types.SerializedElementContent): void { + const b = raw instanceof ElementContent ? raw.toJSON() : raw; this.elementIds = b.elementIds; this.forcedOpen = b.forcedOpen; this.highlighted = b.highlighted; diff --git a/src/model/element/element-property/element-property.ts b/src/model/element/element-property/element-property.ts index 018a87a40..26190c988 100644 --- a/src/model/element/element-property/element-property.ts +++ b/src/model/element/element-property/element-property.ts @@ -20,6 +20,8 @@ export interface ElementPropertyContext { } export class ElementProperty { + public readonly model = Types.ModelName.ElementProperty; + @Mobx.observable private id: string; @Mobx.observable private patternPropertyId: string; private project: Project; @@ -197,6 +199,7 @@ export class ElementProperty { public toJSON(): Types.SerializedElementProperty { return { + model: this.model, id: this.id, patternPropertyId: this.patternPropertyId, setDefault: this.setDefault, @@ -205,10 +208,12 @@ export class ElementProperty { } @Mobx.action - public update(b: ElementProperty): void { - this.id = b.id; - this.patternPropertyId = b.patternPropertyId; - this.setDefault = b.setDefault; - this.value = b.value; + public update(b: ElementProperty | Types.SerializedElementProperty): void { + const data = b instanceof ElementProperty ? b.toJSON() : b; + + this.id = data.id; + this.patternPropertyId = data.patternPropertyId; + this.setDefault = data.setDefault; + this.value = data.value; } } diff --git a/src/model/element/element.ts b/src/model/element/element.ts index 3957e138f..1d136a75c 100644 --- a/src/model/element/element.ts +++ b/src/model/element/element.ts @@ -33,6 +33,8 @@ export interface ElementContext { } export class Element { + public readonly model = Types.ModelName.Element; + @Mobx.observable private containerId?: string; @Mobx.observable private readonly contentIds: string[] = []; @@ -628,6 +630,7 @@ export class Element { public toJSON(): Types.SerializedElement { return { + model: this.model, containerId: this.containerId, contentIds: Array.from(this.contentIds), dragged: this.dragged, @@ -651,7 +654,9 @@ export class Element { } @Mobx.action - public update(b: Element): void { + public update(b: Element | Types.SerializedElement): void { + const e = b instanceof Element ? b : Element.from(b, { project: this.project }); + if (this.selected) { this.project.unsetSelectedElement(); } @@ -662,22 +667,22 @@ export class Element { const propsChanges = computeDifference({ before: this.getProperties(), - after: b.getProperties() + after: e.getProperties() }); propsChanges.removed.forEach(change => this.removeProperty(change.before)); propsChanges.added.forEach(change => this.addProperty(change.after)); propsChanges.changed.forEach(change => change.before.update(change.after)); - this.shouldHighlight = b.shouldHighlight; - this.dragged = b.dragged; - this.shouldFocus = b.focused; - this.containerId = b.containerId; - this.name = b.name; - this.open = b.open; - this.forcedOpen = b.forcedOpen; - this.shouldPlaceholderHighlight = b.placeholderHighlighted; - this.selected = b.selected; + this.shouldHighlight = e.shouldHighlight; + this.dragged = e.dragged; + this.shouldFocus = e.focused; + this.containerId = e.containerId; + this.name = e.name; + this.open = e.open; + this.forcedOpen = e.forcedOpen; + this.shouldPlaceholderHighlight = e.placeholderHighlighted; + this.selected = e.selected; } } diff --git a/src/model/index.ts b/src/model/index.ts index 214a40341..f137e0f25 100644 --- a/src/model/index.ts +++ b/src/model/index.ts @@ -9,5 +9,37 @@ export * from './pattern'; export * from './project'; export * from './user-store'; export * from './user-store-action'; +export * from './user-store-enhancer'; export * from './user-store-property'; export * from './user-store-reference'; + +import { AlvaApp } from './alva-app'; +import { Element, ElementContent, ElementProperty } from './element'; +import { ElementAction } from './element-action'; +import { Page } from './page'; +import { PatternLibrary } from './pattern-library'; +import { Project } from './project'; +import { AnyPatternProperty } from './pattern-property'; +import { Pattern } from './pattern'; +import { UserStore } from './user-store'; +import { UserStoreAction } from './user-store-action'; +import { UserStoreEnhancer } from './user-store-enhancer'; +import { UserStoreProperty } from './user-store-property'; +import { UserStoreReference } from './user-store-reference'; + +export type AnyModel = + | AlvaApp + | AnyPatternProperty + | Element + | ElementContent + | ElementAction + | ElementProperty + | Page + | Pattern + | PatternLibrary + | Project + | UserStore + | UserStoreAction + | UserStoreEnhancer + | UserStoreProperty + | UserStoreReference; diff --git a/src/model/page/page.ts b/src/model/page/page.ts index 4ba5709c6..27eebe494 100644 --- a/src/model/page/page.ts +++ b/src/model/page/page.ts @@ -24,6 +24,8 @@ export interface PageContext { } export class Page { + public readonly model = Types.ModelName.Page; + @Mobx.observable private focused: boolean; @Mobx.observable private editedName: string = ''; @Mobx.observable private id: string; @@ -262,6 +264,7 @@ export class Page { public toJSON(): Types.SerializedPage { return { + model: this.model, active: this.getActive(), focused: this.getFocused(), id: this.getId(), @@ -270,7 +273,8 @@ export class Page { }; } - public update(b: Page): void { + public update(raw: Page | Types.SerializedPage): void { + const b = raw instanceof Page ? raw.toJSON() : raw; this.active = b.active; this.name = b.name; this.rootId = b.rootId; diff --git a/src/model/pattern-library/pattern-library.ts b/src/model/pattern-library/pattern-library.ts index a76cf89fe..468b34532 100644 --- a/src/model/pattern-library/pattern-library.ts +++ b/src/model/pattern-library/pattern-library.ts @@ -38,6 +38,8 @@ export interface PatternLibraryCreateOptions { } export class PatternLibrary { + public readonly model = Types.ModelName.PatternLibrary; + @Mobx.observable private bundleId: string; @Mobx.observable private bundle: string; @Mobx.observable private description: string; @@ -363,6 +365,7 @@ export class PatternLibrary { public toJSON(): Types.SerializedPatternLibrary { return { + model: this.model, bundleId: this.bundleId, bundle: this.bundle, description: this.description, @@ -376,7 +379,9 @@ export class PatternLibrary { } @Mobx.action - public update(b: PatternLibrary): void { + public update(raw: PatternLibrary | Types.SerializedPatternLibrary): void { + const b = raw instanceof PatternLibrary ? raw : PatternLibrary.from(raw); + this.bundleId = b.bundleId; this.bundle = b.bundle; this.description = b.description; diff --git a/src/model/pattern-property/asset-property.ts b/src/model/pattern-property/asset-property.ts index 6f8ffaae0..d3d11cd6a 100644 --- a/src/model/pattern-property/asset-property.ts +++ b/src/model/pattern-property/asset-property.ts @@ -31,6 +31,7 @@ export class PatternAssetProperty extends PatternPropertyBase { public toJSON(): Types.SerializedPatternEventHandlerProperty { return { + model: this.model, contextId: this.contextId, description: this.description, event: this.event.toJSON(), @@ -70,7 +110,9 @@ export class PatternEventHandlerProperty extends PatternPropertyBase { }; } - public update(prop: PatternEventHandlerProperty): void { + public update(raw: this | Types.SerializedPatternEventHandlerProperty): void { + const prop = + raw instanceof PatternEventHandlerProperty ? raw : PatternEventHandlerProperty.from(raw); this.contextId = prop.getContextId(); this.description = prop.getDescription(); this.event = prop.getEvent(); @@ -85,45 +127,6 @@ export interface PatternEventInit { type: Types.PatternEventType; } -export class PatternEvent { - @Mobx.observable private type: Types.PatternEventType; - - public constructor(init: PatternEventInit) { - this.type = init.type; - } - - public static from(serialized: Types.SerializedPatternEvent): PatternEvent { - return new PatternEvent({ - type: deserializeEventType(serialized.type) - }); - } - - public getPayloadFields(): string[] { - // TODO: Use an Enum - switch (this.type) { - case Types.PatternEventType.FocusEvent: - return ['name', 'value']; - case Types.PatternEventType.ChangeEvent: - case Types.PatternEventType.InputEvent: - return ['name', 'value']; - case Types.PatternEventType.MouseEvent: - return ['x', 'y']; - default: - return []; - } - } - - public getType(): Types.PatternEventType { - return this.type; - } - - public toJSON(): Types.SerializedPatternEvent { - return { - type: serializeEventType(this.type) - }; - } -} - function deserializeEventType(type: Types.SerializedPatternEventType): Types.PatternEventType { switch (type) { case 'FocusEvent': diff --git a/src/model/pattern-property/href-property.ts b/src/model/pattern-property/href-property.ts index 2ded7ef3e..4bc656b6e 100644 --- a/src/model/pattern-property/href-property.ts +++ b/src/model/pattern-property/href-property.ts @@ -31,6 +31,7 @@ export class PatternHrefProperty extends PatternPropertyBase public toJSON(): Types.SerializedHrefProperty { return { + model: this.model, contextId: this.contextId, defaultValue: this.defaultValue, description: this.description, @@ -46,7 +47,8 @@ export class PatternHrefProperty extends PatternPropertyBase }; } - public update(prop: PatternHrefProperty): void { + public update(raw: PatternHrefProperty | Types.SerializedHrefProperty): void { + const prop = raw instanceof PatternHrefProperty ? raw : PatternHrefProperty.from(raw); this.contextId = prop.getContextId(); this.description = prop.getDescription(); this.defaultValue = prop.getDefaultValue(); diff --git a/src/model/pattern-property/index.ts b/src/model/pattern-property/index.ts index 926154951..74ce50b75 100644 --- a/src/model/pattern-property/index.ts +++ b/src/model/pattern-property/index.ts @@ -3,9 +3,7 @@ export * from './boolean-property'; export * from './enum-property'; export * from './event-handler-property'; export * from './href-property'; -export * from './number-array-property'; export * from './number-property'; export * from './property-base'; export * from './property'; -export * from './string-array-property'; export * from './string-property'; diff --git a/src/model/pattern-property/number-array-property.ts b/src/model/pattern-property/number-array-property.ts deleted file mode 100644 index 612bb6271..000000000 --- a/src/model/pattern-property/number-array-property.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as AlvaUtil from '../../alva-util'; -import { deserializeOrigin, PatternPropertyBase, serializeOrigin } from './property-base'; -import * as Types from '../../types'; - -/** - * A number array property is a property that supports a list of numbers only. - * As designer content value (raw value), the number array property accepts - * strings, numbers, undefined, and null, both as an array or as a single value. - * See coerceValue(). But everything is converted into a proper array of numbers. - * @see Property - */ -export class PatternNumberArrayProperty extends PatternPropertyBase { - public readonly type = Types.PatternPropertyType.NumberArray; - - public static from( - serialized: Types.SerializedPatternNumberArrayProperty - ): PatternNumberArrayProperty { - return new PatternNumberArrayProperty({ - contextId: serialized.contextId, - hidden: serialized.hidden, - defaultValue: serialized.defaultValue, - example: serialized.example, - id: serialized.id, - inputType: serialized.inputType, - label: serialized.label, - origin: deserializeOrigin(serialized.origin), - propertyName: serialized.propertyName, - required: serialized.required - }); - } - - public coerceValue(value: T): number[] { - return AlvaUtil.ensureArray(value).map(parseFloat); - } - - public toJSON(): Types.SerializedPatternNumberArrayProperty { - return { - contextId: this.contextId, - defaultValue: this.defaultValue, - description: this.description, - example: this.example, - hidden: this.hidden, - id: this.id, - inputType: this.inputType, - label: this.label, - origin: serializeOrigin(this.origin), - propertyName: this.propertyName, - required: this.required, - type: this.type - }; - } - - public update(prop: PatternNumberArrayProperty): void { - this.contextId = prop.getContextId(); - this.description = prop.getDescription(); - this.example = prop.getExample(); - this.hidden = prop.getHidden(); - this.label = prop.getLabel(); - this.propertyName = prop.getPropertyName(); - this.required = prop.getRequired(); - } -} diff --git a/src/model/pattern-property/number-property.ts b/src/model/pattern-property/number-property.ts index 0b31311ab..de4e21e68 100644 --- a/src/model/pattern-property/number-property.ts +++ b/src/model/pattern-property/number-property.ts @@ -35,6 +35,7 @@ export class PatternNumberProperty extends PatternPropertyBase { } export abstract class PatternPropertyBase { + public readonly model = Types.ModelName.PatternProperty; + @Mobx.observable protected contextId: string; @Mobx.observable protected defaultValue: T; @Mobx.observable protected description: string; @@ -107,7 +109,9 @@ export abstract class PatternPropertyBase { } public abstract toJSON(): Types.SerializedPatternProperty; - public abstract update(patternProperty: PatternPropertyBase): void; + public abstract update( + patternProperty: PatternPropertyBase | Types.SerializedPatternProperty + ): void; } export function deserializeOrigin( diff --git a/src/model/pattern-property/property.ts b/src/model/pattern-property/property.ts index c2a991eaf..3a08cf674 100644 --- a/src/model/pattern-property/property.ts +++ b/src/model/pattern-property/property.ts @@ -3,10 +3,8 @@ import { PatternBooleanProperty } from './boolean-property'; import { PatternEnumProperty } from './enum-property'; import { PatternEventHandlerProperty } from './event-handler-property'; import { PatternHrefProperty } from './href-property'; -import { PatternNumberArrayProperty } from './number-array-property'; import { PatternNumberProperty } from './number-property'; import { PatternPropertyBase } from './property-base'; -import { PatternStringArrayProperty } from './string-array-property'; import { PatternStringProperty } from './string-property'; import * as Types from '../../types'; @@ -25,12 +23,8 @@ export class PatternProperty { return PatternEnumProperty.from(serialized); case Types.PatternPropertyType.EventHandler: return PatternEventHandlerProperty.from(serialized); - case Types.PatternPropertyType.NumberArray: - return PatternNumberArrayProperty.from(serialized); case Types.PatternPropertyType.Number: return PatternNumberProperty.from(serialized); - case Types.PatternPropertyType.StringArray: - return PatternStringArrayProperty.from(serialized); case Types.PatternPropertyType.Href: return PatternHrefProperty.from(serialized); case Types.PatternPropertyType.String: diff --git a/src/model/pattern-property/string-array-property.ts b/src/model/pattern-property/string-array-property.ts deleted file mode 100644 index 24376bb02..000000000 --- a/src/model/pattern-property/string-array-property.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as AlvaUtil from '../../alva-util'; -import { deserializeOrigin, PatternPropertyBase, serializeOrigin } from './property-base'; -import * as Types from '../../types'; - -/** - * A string array property is a property that supports a list of text only. - * As designer content value (raw value), the string array property accepts - * strings, numbers, undefined, and null, both as an array or as a single value. - * See coerceValue(). But everything is converted into a proper string array - * (where all elements are never undefined or null). - * @see Property - */ -export class PatternStringArrayProperty extends PatternPropertyBase { - public readonly type = Types.PatternPropertyType.StringArray; - - public static from( - serialized: Types.SerializedPatternStringArrayProperty - ): PatternStringArrayProperty { - return new PatternStringArrayProperty({ - contextId: serialized.contextId, - defaultValue: serialized.defaultValue, - description: serialized.description, - example: serialized.example, - hidden: serialized.hidden, - id: serialized.id, - inputType: serialized.inputType, - label: serialized.label, - origin: deserializeOrigin(serialized.origin), - propertyName: serialized.propertyName, - required: serialized.required - }); - } - - public coerceValue(value: T): string[] { - return AlvaUtil.ensureArray(value).map( - item => (typeof item === 'string' ? item : item.toString()) - ); - } - - public toJSON(): Types.SerializedPatternStringArrayProperty { - return { - contextId: this.contextId, - defaultValue: this.defaultValue, - description: this.description, - example: this.example, - hidden: this.hidden, - id: this.id, - inputType: this.inputType, - label: this.label, - origin: serializeOrigin(this.origin), - propertyName: this.propertyName, - required: this.required, - type: this.type - }; - } - - public update(prop: PatternStringArrayProperty): void { - this.contextId = prop.getContextId(); - this.description = prop.getDescription(); - this.example = prop.getExample(); - this.hidden = prop.getHidden(); - this.label = prop.getLabel(); - this.propertyName = prop.getPropertyName(); - this.required = prop.getRequired(); - } -} diff --git a/src/model/pattern-property/string-property.ts b/src/model/pattern-property/string-property.ts index 60f8a39c9..7a5f2e46a 100644 --- a/src/model/pattern-property/string-property.ts +++ b/src/model/pattern-property/string-property.ts @@ -38,6 +38,7 @@ export class PatternStringProperty extends PatternPropertyBase = new Map(); @Mobx.observable private elementActions: Map = new Map(); @@ -578,6 +580,7 @@ export class Project { public toJSON(): Types.SerializedProject { return { + model: this.model, elements: this.getElements().map(e => e.toJSON()), elementActions: this.getElementActions().map(e => e.toJSON()), elementContents: this.getElementContents().map(e => e.toJSON()), @@ -633,4 +636,8 @@ export class Project { .filter(element => !options || options.ignore.getId() !== element.getId()) .forEach(element => element.setPlaceholderHighlighted(false)); } + + public update(raw: this | Types.SerializedProject): void { + /** */ + } } diff --git a/src/model/user-store-action/user-store-action.ts b/src/model/user-store-action/user-store-action.ts index ab9cddfcf..aa9fa2b53 100644 --- a/src/model/user-store-action/user-store-action.ts +++ b/src/model/user-store-action/user-store-action.ts @@ -14,6 +14,8 @@ export interface UserStoreActionInit { } export class UserStoreAction { + public readonly model = 'UserStoreAction'; + private acceptsProperty: boolean; private id: string; @Mobx.observable private name: string; @@ -92,6 +94,7 @@ export class UserStoreAction { public toJSON(): Types.SerializedUserStoreAction { return { + model: 'UserStoreAction', acceptsProperty: this.acceptsProperty, id: this.id, name: this.name, @@ -106,12 +109,14 @@ export class UserStoreAction { } @Mobx.action - public update(b: this): void { + public update(raw: this | Types.SerializedUserStoreAction): void { + const b = raw instanceof UserStoreAction ? raw.toJSON() : raw; + this.acceptsProperty = b.acceptsProperty; this.id = b.id; this.name = b.name; - this.userStorePropertyId = b.userStorePropertyId; - this.type = b.type; + this.userStorePropertyId = b.storePropertyId; + this.type = deserializeType(b.type); } } diff --git a/src/model/user-store-enhancer.ts b/src/model/user-store-enhancer.ts index 5e8ab1238..ed4159e70 100644 --- a/src/model/user-store-enhancer.ts +++ b/src/model/user-store-enhancer.ts @@ -84,6 +84,8 @@ export function onStoreCreate(store: Alva.DesignTimeUserStore): Alva.DesignTimeU `; export class UserStoreEnhancer { + public readonly model = Types.ModelName.UserStoreEnhancer; + @Mobx.observable private id: string; @Mobx.observable private code: string; @@ -130,6 +132,10 @@ export class UserStoreEnhancer { return this.code; } + public getId(): string { + return this.id; + } + public getJavaScript(): string { return this.js; } @@ -149,12 +155,14 @@ export class UserStoreEnhancer { public toJSON(): Types.SerializedUserStoreEnhancer { return { + model: this.model, id: this.id, code: this.code }; } - public update(b: this): void { + public update(raw: this | UserStoreEnhancer): void { + const b = raw instanceof UserStoreEnhancer ? raw : UserStoreEnhancer.from(raw); this.code = b.code; } } diff --git a/src/model/user-store-property/user-store-property.ts b/src/model/user-store-property/user-store-property.ts index 8ee32118a..2007446fd 100644 --- a/src/model/user-store-property/user-store-property.ts +++ b/src/model/user-store-property/user-store-property.ts @@ -16,6 +16,8 @@ export interface UserStorePropertyInit { const NOOP = () => ''; export class UserStoreProperty { + public readonly model = Types.ModelName.UserStoreProperty; + private id: string; private project: Project; @@ -116,6 +118,7 @@ export class UserStoreProperty { public toJSON(): Types.SerializedUserStoreProperty { return { + model: this.model, id: this.id, name: this.name, concreteValue: this.concreteValue, @@ -126,11 +129,13 @@ export class UserStoreProperty { } @Mobx.action - public update(b: UserStoreProperty): void { + public update(raw: UserStoreProperty | Types.SerializedUserStoreProperty): void { + const b = raw instanceof UserStoreProperty ? raw.toJSON() : raw; + this.id = b.id; this.name = b.name; - this.concreteValue = b.value; - this.valueType = b.valueType; + this.concreteValue = b.concreteValue; + this.valueType = deserializeValueType(b.valueType); } } diff --git a/src/model/user-store-reference.ts b/src/model/user-store-reference.ts index f8936be60..922ed160f 100644 --- a/src/model/user-store-reference.ts +++ b/src/model/user-store-reference.ts @@ -10,6 +10,8 @@ export interface UserStoreReferenceInit { } export class UserStoreReference { + public readonly model = Types.ModelName.UserStoreReference; + private id: string; @Mobx.observable private open: boolean; @Mobx.observable private elementPropertyId: string; @@ -57,7 +59,9 @@ export class UserStoreReference { } @Mobx.action - public update(b: this): void { + public update(raw: this | Types.SerializedUserStoreReference): void { + const b = raw instanceof UserStoreReference ? raw : UserStoreReference.from(raw); + this.id = b.id; this.open = b.open; this.elementPropertyId = b.elementPropertyId; @@ -66,6 +70,7 @@ export class UserStoreReference { public toJSON(): Types.SerializedUserStoreReference { return { + model: this.model, id: this.id, open: this.open, elementPropertyId: this.elementPropertyId, diff --git a/src/model/user-store.ts b/src/model/user-store.ts index 76597e4d9..999090ac3 100644 --- a/src/model/user-store.ts +++ b/src/model/user-store.ts @@ -24,6 +24,8 @@ export interface UserStoreContext { } export class UserStore { + public readonly model = Types.ModelName.UserStore; + private id: string; private previousDesignTimeStore: DesignTime.DesignTimeUserStore; @@ -190,6 +192,10 @@ export class UserStore { return [...this.actions.values()]; } + public getId(): string { + return this.id; + } + public getNoopAction(): UserStoreAction { return this.getActions().find( a => a.getType() === Types.UserStoreActionType.Noop @@ -234,6 +240,10 @@ export class UserStore { return this.getPropertyById(propertyId); } + public getReferenceById(id: string): UserStoreReference | undefined { + return this.references.get(id); + } + public getReferences(): UserStoreReference[] { return [...this.references.values()]; } @@ -289,6 +299,7 @@ export class UserStore { public toJSON(): Types.SerializedUserStore { return { + model: this.model, actions: this.getActions().map(a => a.toJSON()), currentPageProperty: this.currentPageProperty.toJSON(), enhancer: this.enhancer.toJSON(), @@ -297,4 +308,8 @@ export class UserStore { references: this.getReferences().map(r => r.toJSON()) }; } + + public update(b: UserStore | Types.SerializedUserStore): void { + // noop + } } diff --git a/src/preview/preview-store.ts b/src/preview/preview-store.ts index e895506e7..23bc5a116 100644 --- a/src/preview/preview-store.ts +++ b/src/preview/preview-store.ts @@ -1,10 +1,10 @@ import { ElementArea } from './element-area'; -import * as Message from '../message'; import * as Mobx from 'mobx'; +// import * as Message from '../message'; import * as Model from '../model'; import { Sender } from '../sender/client'; import * as Types from '../types'; -import * as uuid from 'uuid'; +// import * as uuid from 'uuid'; export type RequestIdleCallbackHandle = number; @@ -140,6 +140,99 @@ export class PreviewStore { return this.metaDown; } + public getModel(modelName?: Types.ModelName): Types.ModelSurface | undefined { + switch (modelName) { + case Types.ModelName.AlvaApp: + return Model.AlvaApp; + case Types.ModelName.Element: + return Model.Element; + case Types.ModelName.ElementAction: + return Model.ElementAction; + case Types.ModelName.ElementContent: + return Model.ElementContent; + case Types.ModelName.ElementProperty: + return Model.ElementProperty; + case Types.ModelName.Page: + return Model.Page; + case Types.ModelName.Pattern: + return Model.Pattern; + case Types.ModelName.PatternLibrary: + return Model.PatternLibrary; + case Types.ModelName.PatternProperty: + return Model.PatternProperty; + case Types.ModelName.PatternSlot: + return Model.PatternSlot; + case Types.ModelName.UserStore: + return Model.UserStore; + case Types.ModelName.UserStoreAction: + return Model.UserStoreAction; + case Types.ModelName.UserStoreEnhancer: + return Model.UserStoreEnhancer; + case Types.ModelName.UserStoreProperty: + return Model.UserStoreProperty; + case Types.ModelName.UserStoreReference: + return Model.UserStoreReference; + default: + return; + } + } + + public getObject(constructorName: string, id: string): Model.AnyModel | undefined { + if (constructorName === 'Element') { + return this.project.getElementById(id); + } + + if (constructorName === 'ElementAction') { + return this.project.getElementActionById(id); + } + + if (constructorName === 'ElementContent') { + return this.project.getElementContentById(id); + } + + if (constructorName === 'ElementProperty') { + return this.project.getElementPropertyById(id); + } + + if (constructorName === 'Page') { + return this.project.getPageById(id); + } + + if (constructorName === 'Pattern') { + return this.project.getPatternById(id); + } + + if (constructorName === 'PatternLibrary') { + return this.project.getPatternLibraryById(id); + } + + if (constructorName === 'Project') { + return this.project; + } + + if (constructorName === 'UserStore') { + return this.project.getUserStore(); + } + + if (constructorName === 'UserStoreAction') { + return this.project.getUserStore().getActionById(id); + } + + if (constructorName === 'UserStoreProperty') { + return this.project.getUserStore().getPropertyById(id); + } + + if (constructorName === 'UserStoreEnhancer') { + return this.project.getUserStore().getEnhancer(); + } + + if (constructorName === 'UserStoreReference') { + return this.project.getUserStore().getReferenceById(id); + } + + return; + } + public getProperties( element: Model.Element ): { [propName: string]: Types.ElementPropertyValue } { @@ -285,13 +378,13 @@ export class PreviewStore { this.project.setSelectedElement(data.element); } - if (this.sender) { - this.sender.send({ - type: Message.MessageType.SelectElement, - id: uuid.v4(), - payload: { element: data.element.toJSON() } - }); - } + // if (this.sender) { + // this.sender.send({ + // type: Message.MessageType.SelectElement, + // id: uuid.v4(), + // payload: { element: data.element.toJSON() } + // }); + // } } @Mobx.action @@ -311,13 +404,13 @@ export class PreviewStore { this.setHighlightedElement(data.element); } - if (this.sender) { - this.sender.send({ - type: Message.MessageType.HighlightElement, - id: uuid.v4(), - payload: { element: data.element.toJSON() } - }); - } + // if (this.sender) { + // this.sender.send({ + // type: Message.MessageType.HighlightElement, + // id: uuid.v4(), + // payload: { element: data.element.toJSON() } + // }); + // } } @Mobx.action diff --git a/src/preview/preview.ts b/src/preview/preview.ts index 60fa34293..41486a48f 100644 --- a/src/preview/preview.ts +++ b/src/preview/preview.ts @@ -104,73 +104,85 @@ function main(): void { }); }); - sender.match(Message.MessageType.ChangeElements, message => { - Mobx.transaction(() => { - const els = message.payload.elements.map(e => Model.Element.from(e, { project })); + sender.match(Message.MessageType.MobxUpdate, message => { + if (message.payload.change.hasOwnProperty('key')) { + const change = message.payload.change as + | Message.MobxObjectUpdatePayload + | Message.MobxMapUpdatePayload; + const object = store.getObject(message.payload.name, message.payload.id); + + if (!object) { + // console.log(message); + return; + } - const changes = computeDifference({ - before: project.getElements(), - after: els - }); + const changedData = object.toJSON(); + changedData[change.key] = change.newValue; - changes.added.forEach(change => project.addElement(change.after)); - changes.removed.forEach(change => project.removeElement(change.before)); - changes.changed.forEach(change => change.before.update(change.after)); + // tslint:disable-next-line:no-any + (object.update as any)(changedData); + } - const selectedElement = els.find(e => e.getSelected()); - const highlightedElement = els.find(e => e.getHighlighted()); + if (message.payload.change.hasOwnProperty('index')) { + console.log('MobxArrayUpdatePayload', message); + // const change = message.payload.change as Message.MobxArrayUpdatePayload; + } + }); - if (selectedElement) { - const el = project.getElementById(selectedElement.getId()); - if (el) { - project.setSelectedElement(el); - } - } + sender.match(Message.MessageType.MobxAdd, message => { + const parent = store.getObject(message.payload.name, message.payload.id); + const ValueModel = store.getModel(message.payload.valueModel); - if (highlightedElement) { - const el = project.getElementById(highlightedElement.getId()); - if (el) { - project.setHighlightedElement(el); - } - } - }); + if (!parent) { + console.log(message); + return; + } + + const mayBeMember = parent[message.payload.memberName]; + + if (!mayBeMember) { + return; + } + + const value = ValueModel + ? ValueModel.from(message.payload.change.newValue, { project }) + : message.payload.change.newValue; + + if (typeof value === 'object' && !ValueModel) { + console.log(message); + } + + const member = mayBeMember as Map; + member.set(message.payload.change.key, value); }); - sender.match( - Message.MessageType.ChangeElementContents, - message => { - Mobx.transaction(() => { - const contentChanges = computeDifference({ - before: project.getElementContents(), - after: message.payload.elementContents.map(e => - Model.ElementContent.from(e, { project }) - ) - }); - contentChanges.added.forEach(change => project.addElementContent(change.after)); - contentChanges.removed.forEach(change => - project.removeElementContent(change.before) - ); - contentChanges.changed.forEach(change => change.before.update(change.after)); - }); + sender.match(Message.MessageType.MobxSplice, message => { + const parent = store.getObject(message.payload.name, message.payload.id); + + if (!parent) { + console.log(message); + return; } - ); - sender.match( - Message.MessageType.ChangeElementActions, - message => { - Mobx.transaction(() => { - const changes = computeDifference({ - before: project.getElementActions(), - after: message.payload.elementActions.map(e => - Model.ElementAction.from(e, { userStore: project.getUserStore() }) - ) - }); - changes.added.forEach(change => project.addElementAction(change.after)); - changes.removed.forEach(change => project.removeElementAction(change.before)); - changes.changed.forEach(change => change.before.update(change.after)); - }); + const changedData = parent.toJSON(); + const target = changedData[message.payload.memberName]; + + if (!Array.isArray(target)) { + console.log(message); + return; } - ); + + if (message.payload.change.removed.length > 0) { + target.splice(message.payload.change.index, message.payload.change.removed.length); + } + + if (message.payload.change.added.length > 0) { + target.splice(message.payload.change.index, 0, ...message.payload.change.added); + } + + // tslint:disable-next-line:no-any + (parent.update as any)(changedData); + }); sender.match( Message.MessageType.ChangePatternLibraries, @@ -212,10 +224,6 @@ function main(): void { } ); - sender.match(Message.MessageType.ChangeUserStore, message => { - project.getUserStore().sync(message); - }); - Mobx.reaction( () => store.hasSelectedItem(), hasSelection => { @@ -267,7 +275,7 @@ function main(): void { } ); - Mobx.autorun( + /* Mobx.autorun( () => { const userStore = store.getProject().getUserStore(); @@ -280,7 +288,7 @@ function main(): void { { scheduler: window.requestIdleCallback } - ); + ); */ } } diff --git a/src/renderer/create-change-notifiers.ts b/src/renderer/create-change-notifiers.ts index 116decac1..870243ba3 100644 --- a/src/renderer/create-change-notifiers.ts +++ b/src/renderer/create-change-notifiers.ts @@ -17,46 +17,6 @@ export function createChangeNotifiers({ app, store }: NotifierContext): void { const sender = store.getSender(); - Mobx.autorun(() => { - sender.send({ - id: uuid.v4(), - payload: { - pages: store.getPages().map(p => p.toJSON()) - }, - type: Message.MessageType.ChangePages - }); - }, opts); - - Mobx.autorun(() => { - const elements = store.getElements().map(e => e.toJSON()); - - sender.send({ - id: uuid.v4(), - payload: { elements }, - type: Message.MessageType.ChangeElements - }); - }, opts); - - Mobx.autorun(() => { - const elementContents = store.getElementContents().map(e => e.toJSON()); - - sender.send({ - id: uuid.v4(), - payload: { elementContents }, - type: Message.MessageType.ChangeElementContents - }); - }, opts); - - Mobx.autorun(() => { - const elementActions = store.getElementActions().map(e => e.toJSON()); - - sender.send({ - id: uuid.v4(), - payload: { elementActions }, - type: Message.MessageType.ChangeElementActions - }); - }, opts); - Mobx.autorun(() => { const metaDown = store.getMetaDown(); @@ -102,32 +62,175 @@ export function createChangeNotifiers({ app, store }: NotifierContext): void { }, opts); Mobx.autorun(() => { + let dispose; + const project = store.getProject(); + if (!project && dispose) { + dispose(); + } + if (!project) { return; } - sender.send({ - id: uuid.v4(), - payload: { - userStore: project.getUserStore().toJSON() - }, - type: Message.MessageType.ChangeUserStore + dispose = Mobx.spy((change: Types.MobxChange) => { + window.requestIdleCallback(() => { + switch (change.type) { + case Types.MobxChangeType.Update: { + if (change.hasOwnProperty('index')) { + const arrayChange = change as Types.MobxArrayUpdate; + + sender.send({ + id: uuid.v4(), + type: Message.MessageType.MobxUpdate, + payload: { + id: change.object.id, + name: '', + change: { + type: arrayChange.type, + index: arrayChange.index, + newValue: change.newValue + } + } + }); + } + + if (change.hasOwnProperty('key')) { + const objectChange = change as Types.MobxObjectUpdate | Types.MobxMapUpdate; + + sender.send({ + id: uuid.v4(), + type: Message.MessageType.MobxUpdate, + payload: { + id: change.object.id, + name: change.object.constructor.name, + change: { + type: objectChange.type, + key: objectChange.key, + newValue: change.newValue + } + } + }); + } + + break; + } + + case Types.MobxChangeType.Add: { + if (change.newValue === undefined) { + return; + } + + const parent = getParentByMember(change.object, { name: change.name, project }); + const name = parseChangeName(change.name); + + if (!parent) { + return; + } + + // tslint:disable-next-line:no-any + const newValue = + change.newValue && typeof (change.newValue as any).toJSON === 'function' + ? (change.newValue as any).toJSON() + : change.newValue; + + if (typeof newValue === 'object' && !newValue.model) { + console.log(change); + return; + } + + sender.send({ + id: uuid.v4(), + type: Message.MessageType.MobxAdd, + payload: { + id: parent.getId(), + name: name.parentName, + memberName: name.memberName, + // tslint:disable-next-line:no-any + valueModel: typeof newValue === 'object' ? newValue.model : undefined, + change: { + type: change.type, + key: change.key, + newValue + } + } + }); + + break; + } + + case Types.MobxChangeType.Splice: { + const parent = getParentByMember(change.object, { name: change.name, project }); + const name = parseChangeName(change.name); + + if (!parent) { + return; + } + + sender.send({ + id: uuid.v4(), + type: Message.MessageType.MobxSplice, + payload: { + id: parent.getId(), + name: name.parentName, + memberName: name.memberName, + change: { + type: change.type, + index: change.index, + added: change.added, + removed: change.removed + } + } + }); + } + } + }); }); - }, opts); + }); +} + +function getParentByMember( + member: unknown, + { name, project }: { name: string; project: Model.Project } +): Model.AnyModel | undefined { + const { parentName, parentId } = parseChangeName(name); + + switch (parentName) { + case 'Project': + return project; + case 'UserStore': + return project.getUserStore(); + case 'Element': + case 'ElementContent': + return getObjectsByName(parentName, { project }).find((e: unknown) => { + const admin = Mobx._getAdministration(e); + return admin.name === parentId; + }); + default: + return; + } +} - // Mobx.autorun(() => { - // const project = store.getProject(); - // sender.send({ - // id: uuid.v4(), - // payload: { - // project: project ? project.toJSON() : undefined - // }, - // type: Message.MessageType.ChangeProject - // }); - // }, { - // delay: 5000, - // scheduler: window.requestIdleCallback - // }); +function getObjectsByName(name: string, { project }: { project: Model.Project }): Model.AnyModel[] { + switch (name) { + case 'Element': + return project.getElements(); + case 'ElementContent': + return project.getElementContents(); + } + + return []; +} + +function parseChangeName( + name: string +): { parentName: string; parentId: string; memberName: string } { + const [rawParentName = '', rawMemberName = ''] = name.split('.'); + + return { + parentName: rawParentName.split('@')[0] || '', + parentId: rawParentName || '', + memberName: rawMemberName.split('@')[0] || '' + }; } diff --git a/src/renderer/renderer.tsx b/src/renderer/renderer.tsx index 610084f44..5c7209f99 100644 --- a/src/renderer/renderer.tsx +++ b/src/renderer/renderer.tsx @@ -28,14 +28,9 @@ export function startRenderer(): void { const history = new Model.EditHistory(); const store = new ViewStore({ app, history, sender }); - const id = `s${uuid - .v4() - .split('-') - .join('')}`; - // tslint:disable-next-line:no-any - (window as any)[id] = store; - console.log(`Access ViewStore at ${id}`); + (window as any).store = store; + console.log('Access ViewStore at .store'); sender.receive(createServerMessageHandler({ app, history, store })); registerGlobalListeners({ store }); diff --git a/src/server/create-libraries-route.ts b/src/server/create-libraries-route.ts index 5291fbea7..162aa1918 100644 --- a/src/server/create-libraries-route.ts +++ b/src/server/create-libraries-route.ts @@ -1,13 +1,11 @@ import * as Express from 'express'; -import { ProjectRequestResponsePair, MessageType } from '../message'; import { Sender } from '../sender/server'; import * as Model from '../model'; import * as Path from 'path'; -import * as Types from '../types'; -import * as uuid from 'uuid'; export interface LibrariesRouteOptions { - sender: Sender; + sender: Sender | undefined; + project: Model.Project | undefined; } export function createLibrariesRoute(options: LibrariesRouteOptions): Express.RequestHandler { @@ -15,36 +13,13 @@ export function createLibrariesRoute(options: LibrariesRouteOptions): Express.Re req: Express.Request, res: Express.Response ): Promise { - const projectResponse = await options.sender.request( - { - id: uuid.v4(), - type: MessageType.ProjectRequest, - payload: undefined - }, - MessageType.ProjectResponse - ); - - if (projectResponse.payload.status === Types.ProjectStatus.None) { - res.sendStatus(404); - return; - } - - if (projectResponse.payload.status === Types.ProjectStatus.Error) { - res.sendStatus(500); - return; - } - - if ( - projectResponse.payload.status !== Types.ProjectStatus.Ok || - typeof projectResponse.payload.data === 'undefined' - ) { - res.sendStatus(500); + if (!options.project) { + res.send(404); return; } const id = Path.basename(req.path, Path.extname(req.path)); - const project = Model.Project.from(projectResponse.payload.data); - const patternLibrary = project.getPatternLibraryById(id); + const patternLibrary = options.project.getPatternLibraryById(id); if (typeof patternLibrary === 'undefined') { res.sendStatus(404); diff --git a/src/server/create-preview-route.ts b/src/server/create-preview-route.ts index f8ff7265f..2b1ece09f 100644 --- a/src/server/create-preview-route.ts +++ b/src/server/create-preview-route.ts @@ -1,51 +1,27 @@ import * as Express from 'express'; import * as PreviewDocument from '../preview-document'; -import { ProjectRequestResponsePair, MessageType } from '../message'; import { Sender } from '../sender/server'; import * as Model from '../model'; -import * as Types from '../types'; -import * as uuid from 'uuid'; export interface PreviewRouteOptions { - sender: Sender; + sender: Sender | undefined; + project: Model.Project | undefined; } export function createPreviewRoute(options: PreviewRouteOptions): Express.RequestHandler { return async function previewRoute(req: Express.Request, res: Express.Response): Promise { res.type('html'); - const projectResponse = await options.sender.request( - { - id: uuid.v4(), - type: MessageType.ProjectRequest, - payload: undefined - }, - MessageType.ProjectResponse - ); + const { project } = options; - if (projectResponse.payload.status === Types.ProjectStatus.None) { + if (!project) { res.sendStatus(404); return; } - if (projectResponse.payload.status === Types.ProjectStatus.Error) { - res.sendStatus(500); - return; - } - - if ( - projectResponse.payload.status !== Types.ProjectStatus.Ok || - typeof projectResponse.payload.data === 'undefined' - ) { - res.sendStatus(500); - return; - } - - const project = Model.Project.from(projectResponse.payload.data); - res.send( PreviewDocument.previewDocument({ - data: projectResponse.payload.data, + data: project.toJSON(), scripts: project .getPatternLibraries() .map( diff --git a/src/server/create-renderer-route.ts b/src/server/create-renderer-route.ts index 61101b37e..cb63c9d2f 100644 --- a/src/server/create-renderer-route.ts +++ b/src/server/create-renderer-route.ts @@ -1,15 +1,9 @@ import * as Express from 'express'; -import { Sender } from '../sender/server'; import * as RendererDocument from '../renderer/renderer-document'; -export interface RendererRouteOptions { - sender: Sender; -} - -export function createRendererRoute(options: RendererRouteOptions): Express.RequestHandler { +export function createRendererRoute(): Express.RequestHandler { return async function previewRoute(req: Express.Request, res: Express.Response): Promise { res.type('html'); - res.send(RendererDocument.rendererDocument()); }; } diff --git a/src/server/create-screenshot-route.ts b/src/server/create-screenshot-route.ts index a336d21ad..4f11b74b4 100644 --- a/src/server/create-screenshot-route.ts +++ b/src/server/create-screenshot-route.ts @@ -9,8 +9,11 @@ import * as Types from '../types'; import * as uuid from 'uuid'; export interface ScreenshotRouteOptions { + context: { + project: Model.Project | undefined; + sender: Sender | undefined; + }; previewLocation: string; - sender: Sender; } const SUPPORTED_EXTENSIONS = ['.png']; @@ -23,6 +26,11 @@ export function createScreenshotRoute(options: ScreenshotRouteOptions): Express. const extname = Path.extname(req.path); const id = Path.basename(req.path, Path.extname(req.path)); + if (!options.context.sender) { + res.sendStatus(404); + return; + } + if (!id || !extname) { res.sendStatus(400); return; @@ -35,7 +43,7 @@ export function createScreenshotRoute(options: ScreenshotRouteOptions): Express. res.type(extname); - const projectResponse = await options.sender.request( + const projectResponse = await options.context.sender.request( { id: uuid.v4(), type: MessageType.ProjectRequest, diff --git a/src/server/create-sketch-route.ts b/src/server/create-sketch-route.ts index 8db1d3641..fb5cf1ae8 100644 --- a/src/server/create-sketch-route.ts +++ b/src/server/create-sketch-route.ts @@ -9,8 +9,11 @@ import * as Types from '../types'; import * as uuid from 'uuid'; export interface ScreenshotRouteOptions { + context: { + sender: Sender | undefined; + project: Model.Project | undefined; + }; previewLocation: string; - sender: Sender; } const SUPPORTED_EXTENSIONS = ['.json', '.html']; @@ -20,6 +23,11 @@ export function createSketchRoute(options: ScreenshotRouteOptions): Express.Requ const extname = Path.extname(req.path); const id = Path.basename(req.path, Path.extname(req.path)); + if (!options.context.sender) { + res.sendStatus(404); + return; + } + if (!id || !extname) { res.sendStatus(400); return; @@ -32,7 +40,7 @@ export function createSketchRoute(options: ScreenshotRouteOptions): Express.Requ res.type(extname); - const projectResponse = await options.sender.request( + const projectResponse = await options.context.sender.request( { id: uuid.v4(), type: MessageType.ProjectRequest, diff --git a/src/server/create-static-route.ts b/src/server/create-static-route.ts index 52419b3a0..e9ca77894 100644 --- a/src/server/create-static-route.ts +++ b/src/server/create-static-route.ts @@ -1,48 +1,24 @@ import * as Express from 'express'; import * as Path from 'path'; import * as PreviewDocument from '../preview-document'; -import { ProjectRequestResponsePair, MessageType } from '../message'; import { Sender } from '../sender/server'; import * as Model from '../model'; -import * as Types from '../types'; -import * as uuid from 'uuid'; export interface StaticRouteOptions { - sender: Sender; + project: Model.Project | undefined; + sender: Sender | undefined; } export function createStaticRoute(options: StaticRouteOptions): Express.RequestHandler { return async function staticRoute(req: Express.Request, res: Express.Response): Promise { res.type('html'); - const projectResponse = await options.sender.request( - { - id: uuid.v4(), - type: MessageType.ProjectRequest, - payload: undefined - }, - MessageType.ProjectResponse - ); - - if (projectResponse.payload.status === Types.ProjectStatus.None) { - res.sendStatus(404); - return; - } - - if (projectResponse.payload.status === Types.ProjectStatus.Error) { - res.sendStatus(500); - return; - } - - if ( - projectResponse.payload.status !== Types.ProjectStatus.Ok || - typeof projectResponse.payload.data === 'undefined' - ) { - res.sendStatus(500); + if (!options.project) { + res.send(404); return; } - const project = Model.Project.from(projectResponse.payload.data); + const project = options.project; const firstPage = project.getPages()[0]; const id = Path.basename(req.path, Path.extname(req.path)); diff --git a/src/server/server.ts b/src/server/server.ts index f5a3a95f8..2102e4329 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -11,11 +11,20 @@ import { EventEmitter } from 'events'; import * as express from 'express'; import * as Http from 'http'; import * as Message from '../message'; +import * as Model from '../model'; import * as WS from 'ws'; import { isMessage } from '../sender/is-message'; import { Sender } from '../sender/server'; +export interface AppContext { + project: undefined | Model.Project; + port: undefined | number; + sender: undefined | Sender; + win: undefined | unknown; +} + export interface ServerOptions { + context: AppContext; port: number; sender: Sender; } @@ -40,17 +49,15 @@ export class AlvaServer extends EventEmitter { this.server = init.server; this.webSocketServer = init.webSocketServer; - this.app.get('/', createRendererRoute({ sender: this.options.sender })); - - this.app.get('/preview.html', createPreviewRoute({ sender: this.options.sender })); - - this.app.use('/static', createStaticRoute({ sender: this.options.sender })); + this.app.get('/', createRendererRoute()); + this.app.get('/preview.html', createPreviewRoute(this.options.context)); + this.app.use('/static', createStaticRoute(this.options.context)); this.app.use( '/sketch', createSketchRoute({ previewLocation: `http://localhost:${this.options.port}/sketch`, - sender: this.options.sender + context: this.options.context }) ); @@ -58,12 +65,12 @@ export class AlvaServer extends EventEmitter { '/screenshots', createScreenshotRoute({ previewLocation: `http://localhost:${this.options.port}/static`, - sender: this.options.sender + context: this.options.context }) ); this.app.use('/scripts', createScriptsRoute()); - this.app.use('/libraries', createLibrariesRoute({ sender: this.options.sender })); + this.app.use('/libraries', createLibrariesRoute(this.options.context)); this.webSocketServer.on('connection', createConnectionHandler({ emitter: this })); this.on('message', createServerMessageHandler({ webSocketServer: this.webSocketServer })); diff --git a/src/store/view-store.ts b/src/store/view-store.ts index 79ecaef1a..bfdfb2660 100644 --- a/src/store/view-store.ts +++ b/src/store/view-store.ts @@ -711,7 +711,8 @@ export class ViewStore { @Mobx.action public save(): void { - const savedProjects = this.getSavedProjects(); + // TODO TODO: send rework saving + /* const savedProjects = this.getSavedProjects(); const savedProject = savedProjects[savedProjects.length - 1]; if (savedProject && Model.Project.equals(savedProject, this.project.toDisk())) { @@ -732,7 +733,7 @@ export class ViewStore { payload, type: MessageType.Save }); - } + } */ } @Mobx.action diff --git a/src/types/index.ts b/src/types/index.ts index f559b945c..e511034cd 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,6 @@ export * from './export'; +export * from './mobx'; +export * from './serialized-model'; +export * from './pattern-property'; export * from './types'; export * from './user-store'; -export * from './pattern-property'; diff --git a/src/types/mobx.ts b/src/types/mobx.ts new file mode 100644 index 000000000..26b2a40e0 --- /dev/null +++ b/src/types/mobx.ts @@ -0,0 +1,129 @@ +export type MobxChange = + | MobxAction + | MobxScheduledReaction + | MobxReaction + | MobxCompute + // tslint:disable-next-line:no-any + | MobxUpdate + | MobxSplice + // tslint:disable-next-line:no-any + | MobxAdd + // tslint:disable-next-line:no-any + | MobxDelete + // tslint:disable-next-line:no-any + | MobxCreate + | MobxReportEnd; + +export interface MobxReportStart { + spyReportStart?: true; + spyReportEnd?: false; +} + +export enum MobxChangeType { + Action = 'action', + ScheduledReaction = 'scheduled-reaction', + Reaction = 'reaction', + Compute = 'compute', + Error = 'error', + Update = 'update', + Splice = 'splice', + Add = 'add', + Delete = 'delete', + Create = 'create', + ReportEnd = 'report-end' +} + +export interface MobxAction extends MobxReportStart { + type: MobxChangeType.Action; + name: string; + object: unknown; + arguments: unknown; +} + +export interface MobxScheduledReaction extends MobxReportStart { + type: MobxChangeType.ScheduledReaction; +} + +export interface MobxReaction extends MobxReportStart { + type: MobxChangeType.Reaction; +} + +export interface MobxCompute extends MobxReportStart { + type: MobxChangeType.Compute; +} + +export interface MobxSplice extends MobxReportStart { + type: MobxChangeType.Splice; + added: T[]; + addedCount: number; + name: string; + object: T[]; + removed: T[]; + removedCount: number; + index: number; +} + +export type MobxAdd = MobxMapAdd; + +export interface MobxMapAdd extends MobxReportStart { + object: T; + key: V; + name: string; + newValue: T; + type: MobxChangeType.Add; +} + +export interface MobxDelete extends MobxReportStart { + object: T; + name: string; + oldValue: T; + type: MobxChangeType.Delete; +} + +export interface MobxCreate extends MobxReportStart { + object: T; + name: string; + newValue: T; + type: MobxChangeType.Create; +} + +export type MobxUpdate = MobxArrayUpdate | MobxMapUpdate | MobxObjectUpdate; + +export interface MobxArrayUpdate extends MobxReportStart { + type: MobxChangeType.Update; + object: T[]; + name: string; + index: number; + newValue: T; + oldValue: T; +} + +export interface MobxMapUpdate extends MobxReportStart { + type: MobxChangeType.Update; + object: Map; + key: string; + name: string; + newValue: T; + oldValue: T; +} + +export interface MobxObjectUpdate extends MobxReportStart { + type: MobxChangeType.Update; + object: T; + key: string; + name: string; + newValue: T; + oldValue: T; +} + +export enum MobxUpdateDataType { + Array = 'array', + Map = 'map', + Object = 'object' +} + +export interface MobxReportEnd { + type?: MobxChangeType.ReportEnd; + spyReportStart?: false; + spyReportEnd: true; +} diff --git a/src/types/pattern-property.ts b/src/types/pattern-property.ts index 2af3ce26d..b7529494f 100644 --- a/src/types/pattern-property.ts +++ b/src/types/pattern-property.ts @@ -1,4 +1,5 @@ import { IconName } from '../components'; +import { ModelName } from './types'; export enum PatternPropertyType { Asset = 'asset', @@ -50,6 +51,7 @@ export type SerializedPatternProperty = | SerializedHrefProperty; export interface SerializedPropertyBase { + model: ModelName.PatternProperty; contextId: string; description: string; example: string; diff --git a/src/types/serialized-model.ts b/src/types/serialized-model.ts new file mode 100644 index 000000000..174dfe1cf --- /dev/null +++ b/src/types/serialized-model.ts @@ -0,0 +1,182 @@ +import * as PatternProperty from './pattern-property'; +import * as Types from './types'; +import * as UserStore from './user-store'; + +export enum PatternOrigin { + BuiltIn = 'built-in', + UserProvided = 'user-provided' +} + +export enum PatternLibraryState { + Pristine = 'pristine', + Connected = 'connected', + Disconnected = 'disconnected' +} + +export type SerializedAppPane = + | 'pages-pane' + | 'patterns-pane' + | 'elements-pane' + | 'properties-pane' + | 'development-pane'; + +export type SerializedAlvaView = 'PageDetail' | 'SplashScreen'; + +export type SerializedPatternOrigin = 'built-in' | 'user-provided'; + +export type SerializedPatternType = + | 'pattern' + | 'synthetic:box' + | 'synthetic:conditional' + | 'synthetic:image' + | 'synthetic:link' + | 'synthetic:page' + | 'synthetic:text'; + +export type SerializedPatternLibraryOrigin = 'built-in' | 'user-provided'; + +export type SerializedAppState = 'starting' | 'started'; + +export type SerializedElementRole = 'root' | 'node'; + +export type SerializedItemType = 'none' | 'page' | 'element'; + +export type SerializedItem = SerializedPage | SerializedElement; + +export type SerializedRightSidebarTab = 'properties' | 'project-settings'; + +export interface SerializedPaneSize { + pane: SerializedAppPane; + width?: number; + height?: number; +} + +export interface SerializedPattern { + model: Types.ModelName.Pattern; + contextId: string; + description: string; + exportName: string; + id: string; + name: string; + origin: SerializedPatternOrigin; + propertyIds: string[]; + slots: SerializedPatternSlot[]; + type: SerializedPatternType; +} + +export interface SerializedPatternSlot { + model: Types.ModelName.PatternSlot; + contextId: string; + description: string; + example: string; + hidden: boolean; + id: string; + label: string; + propertyName: string; + required: boolean; + type: string; +} + +export interface SerializedPatternLibrary { + model: Types.ModelName.PatternLibrary; + bundleId: string; + bundle: string; + description: string; + id: string; + name: string; + origin: SerializedPatternLibraryOrigin; + patternProperties: PatternProperty.SerializedPatternProperty[]; + patterns: SerializedPattern[]; + state: PatternLibraryState; +} + +export interface SavedProject { + model: Types.ModelName.Project; + elementActions: SerializedElementAction[]; + elementContents: SerializedElementContent[]; + elements: SerializedElement[]; + id: string; + name: string; + pages: SerializedPage[]; + patternLibraries: SerializedPatternLibrary[]; + userStore: UserStore.SerializedUserStore; +} + +export interface SerializedProject extends SavedProject { + path: string; +} + +export interface SerializedPage { + model: Types.ModelName.Page; + active: boolean; + focused: boolean; + id: string; + name: string; + rootId: string; +} + +export interface SerializedElement { + model: Types.ModelName.Element; + containerId?: string; + contentIds: string[]; + dragged: boolean; + focused: boolean; + forcedOpen: boolean; + highlighted: boolean; + id: string; + name: string; + open: boolean; + patternId: string; + placeholderHighlighted: boolean; + properties: SerializedElementProperty[]; + role: SerializedElementRole; + selected: boolean; +} + +export interface SerializedElementContent { + model: Types.ModelName.ElementContent; + elementIds: string[]; + forcedOpen: boolean; + highlighted: boolean; + id: string; + open: boolean; + parentElementId?: string; + slotId: string; +} + +export interface SerializedElementProperty { + model: Types.ModelName.ElementProperty; + id: string; + patternPropertyId: string; + setDefault: boolean; + value: Types.ElementPropertyValue; +} + +export interface SerializedAlvaApp { + model: Types.ModelName.AlvaApp; + activeView: SerializedAlvaView; + hasFocusedInput: boolean; + panes: SerializedAppPane[]; + paneSizes: SerializedPaneSize[]; + rightSidebarTab: SerializedRightSidebarTab; + searchTerm: string; + state: SerializedAppState; +} + +export interface SerializedElementAction { + model: Types.ModelName.ElementAction; + elementPropertyId: string; + id: string; + open: boolean; + payload: string; + payloadType: Types.ElementActionPayloadType; + storeActionId: string; + storePropertyId: string; +} + +export interface SerializedElementActionPayload { + model: Types.ModelName.ElementActionPayload; + id: string; + type: Types.ElementActionPayloadType; + value: string; +} diff --git a/src/types/types.ts b/src/types/types.ts index 91a4da0ef..7f7b9738b 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,4 +1,5 @@ import * as PatternProperty from './pattern-property'; +import * as SerializedModel from './serialized-model'; import * as UserStore from './user-store'; export enum AppState { @@ -6,78 +7,6 @@ export enum AppState { Started = 'started' } -export type SerializedAppState = 'starting' | 'started'; - -export interface SavedProject { - elementActions: SerializedElementAction[]; - elementContents: SerializedElementContent[]; - elements: SerializedElement[]; - id: string; - name: string; - pages: SerializedPage[]; - patternLibraries: SerializedPatternLibrary[]; - userStore: UserStore.SerializedUserStore; -} - -export interface SerializedProject extends SavedProject { - path: string; -} - -export interface SerializedPage { - active: boolean; - focused: boolean; - id: string; - name: string; - rootId: string; -} - -export type SerializedElementRole = 'root' | 'node'; - -export interface SerializedElement { - containerId?: string; - contentIds: string[]; - dragged: boolean; - focused: boolean; - forcedOpen: boolean; - highlighted: boolean; - id: string; - name: string; - open: boolean; - patternId: string; - placeholderHighlighted: boolean; - properties: SerializedElementProperty[]; - role: SerializedElementRole; - selected: boolean; -} - -export interface SerializedElementContent { - elementIds: string[]; - forcedOpen: boolean; - highlighted: boolean; - id: string; - open: boolean; - parentElementId?: string; - slotId: string; -} - -export enum PatternLibraryState { - Pristine = 'pristine', - Connected = 'connected', - Disconnected = 'disconnected' -} - -export interface SerializedPatternLibrary { - bundleId: string; - bundle: string; - description: string; - id: string; - name: string; - origin: SerializedPatternLibraryOrigin; - patternProperties: PatternProperty.SerializedPatternProperty[]; - patterns: SerializedPattern[]; - state: PatternLibraryState; -} - export enum PatternType { Pattern = 'pattern', SyntheticBox = 'synthetic:box', @@ -88,53 +17,11 @@ export enum PatternType { SyntheticText = 'synthetic:text' } -export type SerializedPatternType = - | 'pattern' - | 'synthetic:box' - | 'synthetic:conditional' - | 'synthetic:image' - | 'synthetic:link' - | 'synthetic:page' - | 'synthetic:text'; - -export enum PatternOrigin { - BuiltIn = 'built-in', - UserProvided = 'user-provided' -} - -export type SerializedPatternOrigin = 'built-in' | 'user-provided'; - -export interface SerializedPattern { - contextId: string; - description: string; - exportName: string; - id: string; - name: string; - origin: SerializedPatternOrigin; - propertyIds: string[]; - slots: SerializedPatternSlot[]; - type: SerializedPatternType; -} - -export interface SerializedPatternSlot { - contextId: string; - description: string; - example: string; - hidden: boolean; - id: string; - label: string; - propertyName: string; - required: boolean; - type: string; -} - export enum AlvaView { PageDetail = 'PageDetail', SplashScreen = 'SplashScreen' } -export type SerializedAlvaView = 'PageDetail' | 'SplashScreen'; - export enum EditableTitleState { Editable = 'Editable', Editing = 'Editing' @@ -150,13 +37,6 @@ export enum SlotType { Property = 'property' } -export interface SerializedElementProperty { - id: string; - patternPropertyId: string; - setDefault: boolean; - value: ElementPropertyValue; -} - export interface RenderPage { id: string; name: string; @@ -191,7 +71,7 @@ export interface LibraryAnalysisError { export interface PatternAnalysis { path: string; - pattern: SerializedPattern; + pattern: SerializedModel.SerializedPattern; properties: PatternProperty.SerializedPatternProperty[]; } @@ -211,11 +91,11 @@ export interface FilePayload { } export interface PageChangePayload { - elementActions: SerializedElementAction[]; - elementContents: SerializedElementContent[]; - elements: SerializedElement[]; + elementActions: SerializedModel.SerializedElementAction[]; + elementContents: SerializedModel.SerializedElementContent[]; + elements: SerializedModel.SerializedElement[]; pageId: string; - pages: SerializedPage[]; + pages: SerializedModel.SerializedPage[]; userStore: UserStore.SerializedUserStore; } @@ -232,14 +112,14 @@ export interface ProjectPayloadError { } export interface ProjectPayloadSuccess { - contents: SerializedProject; + contents: SerializedModel.SerializedProject; path: string; status: ProjectPayloadStatus; } export interface SavePayload { path: string; - project: SerializedProject; + project: SerializedModel.SerializedProject; } export interface SketchExportPayload { @@ -302,10 +182,10 @@ export interface RenderSelectArea { export interface RenderPreviewStore { mode: 'static' | 'live' | 'live-mirror'; - elements: SerializedElement[]; + elements: SerializedModel.SerializedElement[]; highlightedElementId?: string; pageId: string; - pages: SerializedPage[]; + pages: SerializedModel.SerializedPage[]; selectedElementId?: string; } @@ -324,25 +204,9 @@ export interface PaneSize { height?: number; } -export interface SerializedPaneSize { - pane: SerializedAppPane; - width?: number; - height?: number; -} - -export interface SerializedAlvaApp { - activeView: SerializedAlvaView; - hasFocusedInput: boolean; - panes: SerializedAppPane[]; - paneSizes: SerializedPaneSize[]; - rightSidebarTab: SerializedRightSidebarTab; - searchTerm: string; - state: SerializedAppState; -} - export interface EditHistoryItem { - app: SerializedAlvaApp; - project: SerializedProject; + app: SerializedModel.SerializedAlvaApp; + project: SerializedModel.SerializedProject; } export type ElementPropertyValue = @@ -354,23 +218,11 @@ export type ElementPropertyValue = | string | string[]; -export interface SerializedElementAction { - elementPropertyId: string; - id: string; - open: boolean; - payload: string; - payloadType: ElementActionPayloadType; - storeActionId: string; - storePropertyId: string; -} - export enum RightSidebarTab { Properties = 'properties', ProjectSettings = 'project-settings' } -export type SerializedRightSidebarTab = 'properties' | 'project-settings'; - export interface SerializedPatternLibraryFile { content: Buffer; id: string; @@ -382,8 +234,6 @@ export enum PatternLibraryOrigin { UserProvided = 'user-provided' } -export type SerializedPatternLibraryOrigin = 'built-in' | 'user-provided'; - export enum ProjectStatus { None = 'none', Ok = 'ok', @@ -409,8 +259,8 @@ export interface ElementContextMenuRequest { } export interface ElementContextMenuRequestPayload { - element: SerializedElement; - project: SerializedProject; + element: SerializedModel.SerializedElement; + project: SerializedModel.SerializedProject; } export interface LayoutContextMenuRequest { @@ -419,7 +269,7 @@ export interface LayoutContextMenuRequest { } export interface LayoutContextMenuRequestPayload { - app: SerializedAlvaApp; + app: SerializedModel.SerializedAlvaApp; } export enum AppPane { @@ -430,16 +280,9 @@ export enum AppPane { DevelopmentPane = 'development-pane' } -export type SerializedAppPane = - | 'pages-pane' - | 'patterns-pane' - | 'elements-pane' - | 'properties-pane' - | 'development-pane'; - export interface MainMenuContext { - app: SerializedAlvaApp; - project?: SerializedProject; + app: SerializedModel.SerializedAlvaApp; + project?: SerializedModel.SerializedProject; } export enum ItemType { @@ -448,10 +291,6 @@ export enum ItemType { Element = 'element' } -export type SerializedItemType = 'none' | 'page' | 'element'; - -export type SerializedItem = SerializedPage | SerializedElement; - export enum HoverArea { Chrome, Preview @@ -485,8 +324,28 @@ export enum ElementActionPayloadType { PropertyPayload = 'property-payload' } -export interface SerializedElementActionPayload { - id: string; - type: ElementActionPayloadType; - value: string; +export enum ModelName { + AlvaApp = 'AlvaApp', + Element = 'Element', + ElementAction = 'ElementAction', + ElementActionPayload = 'ElementActionPayload', + ElementContent = 'ElementContent', + ElementProperty = 'ElementProperty', + Page = 'Page', + Pattern = 'Pattern', + PatternLibrary = 'PatternLibrary', + PatternProperty = 'PatternProperty', + PatternSlot = 'PatternSlot', + Project = 'Project', + UserStore = 'UserStore', + UserStoreAction = 'UserStoreAction', + UserStoreEnhancer = 'UserStoreEnhancer', + UserStoreProperty = 'UserStoreProperty', + UserStoreReference = 'UserStoreReference' +} + +declare class ModelSurface { + public from(data: unknown, context?: unknown): unknown; } + +export { ModelSurface }; diff --git a/src/types/user-store.ts b/src/types/user-store.ts index 70185eafc..5b895cbf9 100644 --- a/src/types/user-store.ts +++ b/src/types/user-store.ts @@ -1,3 +1,5 @@ +import { ModelName } from './types'; + export enum UserStorePropertyValueType { String = 'string', Page = 'page' @@ -17,6 +19,7 @@ export type SerializedUserStorePropertyType = 'concrete' | 'computed'; export type SerializedUserStoreActionType = 'noop' | 'set' | 'set-page' | 'open-external'; export interface SerializedUserStoreProperty { + model: ModelName.UserStoreProperty; id: string; name: string; concreteValue: string | undefined; @@ -31,6 +34,7 @@ export enum UserStorePropertyType { } export interface SerializedUserStoreAction { + model: 'UserStoreAction'; acceptsProperty: boolean; id: string; name: string; @@ -39,6 +43,7 @@ export interface SerializedUserStoreAction { } export interface SerializedUserStore { + model: ModelName.UserStore; actions: SerializedUserStoreAction[]; currentPageProperty: SerializedUserStoreProperty; enhancer: SerializedUserStoreEnhancer; @@ -50,6 +55,7 @@ export interface SerializedUserStore { export type UserStoreActionPayload = string; export interface SerializedUserStoreReference { + model: ModelName.UserStoreReference; id: string; open: boolean; elementPropertyId: string; @@ -57,6 +63,7 @@ export interface SerializedUserStoreReference { } export interface SerializedUserStoreEnhancer { + model: ModelName.UserStoreEnhancer; id: string; code: string; } diff --git a/tsconfig.json b/tsconfig.json index a6430bdfc..00cafe2e2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "checkJs": false, "declaration": true, "downlevelIteration": true, - "emitDecoratorMetadata": false, + "emitDecoratorMetadata": true, "experimentalDecorators": true, "forceConsistentCasingInFileNames": true, "importHelpers": false,