diff --git a/src/alva-util/compute-difference.ts b/src/alva-util/compute-difference.ts new file mode 100644 index 000000000..5f26b91b6 --- /dev/null +++ b/src/alva-util/compute-difference.ts @@ -0,0 +1,64 @@ +export interface Difference { + added: AddItem[]; + changed: ChangeItem[]; + removed: RemoveItem[]; +} + +export interface AddItem { + before: undefined; + after: T; +} + +export interface ChangeItem { + before: T; + after: T; +} + +export interface RemoveItem { + before: T; + after: undefined; +} + +export type Diffable = Identifyable & Equalifyable; + +export interface Identifyable { + getId(): string; +} + +export interface Equalifyable { + equals(this: T, a: T): boolean; +} + +export function computeDifference>(init: { + before: T[]; + after: T[]; +}): Difference { + const { before, after } = init; + + const added = after.filter( + afterItem => !before.some(beforeItem => afterItem.getId() === beforeItem.getId()) + ); + + const removed = before + .filter(beforeItem => !added.some(addItem => addItem.getId() === beforeItem.getId())) + .filter(beforeItem => !after.some(afterItem => afterItem.getId() === beforeItem.getId())); + + const changed = before + .filter(beforeItem => !added.some(addItem => addItem.getId() === beforeItem.getId())) + .filter(beforeItem => !removed.some(addItem => addItem.getId() === beforeItem.getId())) + .filter(beforeItem => { + const matchingAfterItem = after.find( + afterItem => afterItem.getId() === beforeItem.getId() + ); + return matchingAfterItem && !beforeItem.equals(matchingAfterItem); + }); + + return { + added: added.map(item => ({ before: undefined, after: item })), + changed: changed.map(beforeItem => ({ + after: after.find(afterItem => afterItem.getId() === beforeItem.getId()) as T, + before: beforeItem + })), + removed: removed.map(item => ({ before: item, after: undefined })) + }; +} diff --git a/src/alva-util/index.ts b/src/alva-util/index.ts index d6dc0d33d..a830cd2ab 100644 --- a/src/alva-util/index.ts +++ b/src/alva-util/index.ts @@ -1,3 +1,4 @@ +export * from './compute-difference'; export * from './ensure-array'; export * from './guess-name'; export * from './noop'; diff --git a/src/container/app.tsx b/src/container/app.tsx index 3115b4b1b..f1e702186 100644 --- a/src/container/app.tsx +++ b/src/container/app.tsx @@ -3,7 +3,7 @@ import * as Sender from '../sender/client'; import * as Components from '../components'; import { ConnectPaneContainer } from './connect-pane-container'; import { ElementList } from './element-list'; -import { ServerMessageType } from '../message'; +import { MessageType } from '../message'; import * as MobxReact from 'mobx-react'; import { PageListContainer } from './page-list/page-list-container'; import { PatternListContainer } from './pattern-list'; @@ -46,14 +46,14 @@ export class App extends React.Component { { Sender.send({ - type: ServerMessageType.CreateNewFileRequest, + type: MessageType.CreateNewFileRequest, id: uuid.v4(), payload: undefined }); }} onSecondaryButtonClick={() => { Sender.send({ - type: ServerMessageType.OpenFileRequest, + type: MessageType.OpenFileRequest, id: uuid.v4(), payload: undefined }); @@ -137,7 +137,7 @@ export class App extends React.Component { onPrimaryButtonClick={() => props.store.connectPatternLibrary()} onSecondaryButtonClick={() => Sender.send({ - type: ServerMessageType.OpenExternalURL, + type: MessageType.OpenExternalURL, id: uuid.v4(), payload: 'http://meetalva.github.io/example/example.alva' }) diff --git a/src/container/chrome/chrome-container.tsx b/src/container/chrome/chrome-container.tsx index e253eab58..bdcfd3ee7 100644 --- a/src/container/chrome/chrome-container.tsx +++ b/src/container/chrome/chrome-container.tsx @@ -1,7 +1,7 @@ import * as AlvaUtil from '../../alva-util'; import { ChromeSwitch } from './chrome-switch'; import { BugReport, Chrome, CopySize, ViewSwitch } from '../../components'; -import { ServerMessageType } from '../../message'; +import { MessageType } from '../../message'; import * as MobxReact from 'mobx-react'; import { Page } from '../../model'; import * as React from 'react'; @@ -44,7 +44,7 @@ export const ChromeContainer = MobxReact.inject('store')( { Sender.send({ - type: ServerMessageType.Maximize, + type: MessageType.Maximize, id: uuid.v4(), payload: undefined }); @@ -69,7 +69,7 @@ export const ChromeContainer = MobxReact.inject('store')( title="Found a bug?" onClick={() => { Sender.send({ - type: ServerMessageType.OpenExternalURL, + type: MessageType.OpenExternalURL, id: uuid.v4(), payload: 'https://github.com/meetalva/alva/labels/type%3A%20bug' }); diff --git a/src/container/page-list/page-list-container.tsx b/src/container/page-list/page-list-container.tsx index 8f3c4d9a8..8be65660b 100644 --- a/src/container/page-list/page-list-container.tsx +++ b/src/container/page-list/page-list-container.tsx @@ -1,6 +1,6 @@ import * as Sender from '../../sender/client'; import { AddPageButton, Layout, LayoutWrap } from '../../components'; -import { ServerMessageType } from '../../message'; +import { MessageType } from '../../message'; import * as MobxReact from 'mobx-react'; import { PageTileContainer } from './page-tile-container'; import * as React from 'react'; @@ -35,7 +35,7 @@ export const PageListContainer: React.StatelessComponent = MobxReact.inject('sto Sender.send({ id: uuid.v4(), payload: undefined, - type: ServerMessageType.CreateNewPage + type: MessageType.CreateNewPage }) } /> diff --git a/src/container/property-list/event-handler-property-view.tsx b/src/container/property-list/event-handler-property-view.tsx index 073192520..4b63b54e8 100644 --- a/src/container/property-list/event-handler-property-view.tsx +++ b/src/container/property-list/event-handler-property-view.tsx @@ -28,12 +28,17 @@ export class EventHandlerPropertyView extends React.Component { Sender.receive(message => { if ( - message.type === ServerMessageType.AssetReadResponse && + message.type === MessageType.AssetReadResponse && message.id === transactionId ) { property.setValue(message.payload); @@ -111,7 +111,7 @@ export class PropertyListItem extends React.Component { Sender.send({ id: transactionId, payload: undefined, - type: ServerMessageType.AssetReadRequest + type: MessageType.AssetReadRequest }); }} placeholder="Or enter URL" diff --git a/src/electron/create-client-message-handler.ts b/src/electron/create-client-message-handler.ts deleted file mode 100644 index fe9141809..000000000 --- a/src/electron/create-client-message-handler.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as Message from '../message'; -import { Sender } from '../sender/server'; - -export type ClientMessageHandler = (message: Message.PreviewMessage) => Promise; - -export interface ClientMessageInjection { - sender: Sender; -} - -export const createClientMessageHandler = async ({ - sender -}: ClientMessageInjection): Promise => - async function clientMessageHandler(message: Message.PreviewMessage): Promise { - switch (message.type) { - case Message.PreviewMessageType.ActivatePage: { - return sender.send({ - id: message.id, - payload: message.payload, - type: Message.ServerMessageType.ActivatePage - }); - } - case Message.PreviewMessageType.ContentResponse: { - return sender.send({ - id: message.id, - payload: message.payload, - type: Message.ServerMessageType.ContentResponse - }); - } - case Message.PreviewMessageType.SketchExportResponse: { - return sender.send({ - id: message.id, - payload: message.payload, - type: Message.ServerMessageType.SketchExportResponse - }); - } - case Message.PreviewMessageType.SelectElement: { - return sender.send({ - id: message.id, - payload: message.payload, - type: Message.ServerMessageType.SelectElement - }); - } - case Message.PreviewMessageType.UnselectElement: { - return sender.send({ - id: message.id, - payload: undefined, - type: Message.ServerMessageType.UnselectElement - }); - } - case Message.PreviewMessageType.HighlightElement: { - return sender.send({ - id: message.id, - payload: message.payload, - type: Message.ServerMessageType.HighlightElement - }); - } - case Message.PreviewMessageType.UnHighlightElement: { - return sender.send({ - id: message.id, - payload: message.payload, - type: Message.ServerMessageType.UnHighlightElement - }); - } - case Message.PreviewMessageType.KeyboardChange: { - return sender.send({ - id: message.id, - payload: message.payload, - type: Message.ServerMessageType.KeyboardChange - }); - } - default: { - return; - } - } - }; diff --git a/src/electron/create-main-menu/create-app-menu.ts b/src/electron/create-main-menu/create-app-menu.ts index 906fc8571..722861df1 100644 --- a/src/electron/create-main-menu/create-app-menu.ts +++ b/src/electron/create-main-menu/create-app-menu.ts @@ -1,6 +1,6 @@ import * as Electron from 'electron'; import { Sender } from '../../sender/server'; -import { ServerMessageType } from '../../message'; +import { MessageType } from '../../message'; import * as Types from '../../types'; import * as uuid from 'uuid'; @@ -29,7 +29,7 @@ export function createAppMenu( injecton.sender.send({ id: uuid.v4(), payload: undefined, - type: ServerMessageType.CheckForUpdatesRequest + type: MessageType.CheckForUpdatesRequest }); } }, diff --git a/src/electron/create-main-menu/create-edit-menu.ts b/src/electron/create-main-menu/create-edit-menu.ts index 9f2428c55..91307f01b 100644 --- a/src/electron/create-main-menu/create-edit-menu.ts +++ b/src/electron/create-main-menu/create-edit-menu.ts @@ -1,5 +1,5 @@ import * as Electron from 'electron'; -import { ServerMessageType } from '../../message'; +import { MessageType } from '../../message'; import * as Model from '../../model'; import { Sender } from '../../sender/server'; import * as Types from '../../types'; @@ -27,7 +27,7 @@ export function createEditMenu( click: () => injection.sender.send({ id: uuid.v4(), - type: ServerMessageType.Undo, + type: MessageType.Undo, payload: undefined }) }, @@ -39,7 +39,7 @@ export function createEditMenu( injection.sender.send({ id: uuid.v4(), payload: undefined, - type: ServerMessageType.Redo + type: MessageType.Redo }) }, { @@ -53,7 +53,7 @@ export function createEditMenu( injection.sender.send({ id: uuid.v4(), payload: undefined, - type: ServerMessageType.Cut + type: MessageType.Cut }); Electron.Menu.sendActionToFirstResponder('cut:'); } @@ -66,7 +66,7 @@ export function createEditMenu( injection.sender.send({ id: uuid.v4(), payload: undefined, - type: ServerMessageType.Copy + type: MessageType.Copy }); Electron.Menu.sendActionToFirstResponder('copy:'); } @@ -79,7 +79,7 @@ export function createEditMenu( injection.sender.send({ id: uuid.v4(), payload: undefined, - type: ServerMessageType.Paste + type: MessageType.Paste }); Electron.Menu.sendActionToFirstResponder('paste:'); } @@ -95,7 +95,7 @@ export function createEditMenu( injection.sender.send({ id: uuid.v4(), payload: undefined, - type: ServerMessageType.Duplicate + type: MessageType.Duplicate }); } }, @@ -118,7 +118,7 @@ export function createEditMenu( injection.sender.send({ id: uuid.v4(), payload: undefined, - type: ServerMessageType.Delete + type: MessageType.Delete }); Electron.Menu.sendActionToFirstResponder('delete:'); } diff --git a/src/electron/create-main-menu/create-file-menu.ts b/src/electron/create-main-menu/create-file-menu.ts index 8905946e2..4bae17a1c 100644 --- a/src/electron/create-main-menu/create-file-menu.ts +++ b/src/electron/create-main-menu/create-file-menu.ts @@ -1,5 +1,5 @@ import * as Electron from 'electron'; -import { ServerMessageType } from '../../message'; +import { MessageType } from '../../message'; import * as Model from '../../model'; import { Sender } from '../../sender/server'; import * as Types from '../../types'; @@ -24,7 +24,7 @@ export function createFileMenu( accelerator: 'CmdOrCtrl+N', click: () => { injection.sender.send({ - type: ServerMessageType.CreateNewFileRequest, + type: MessageType.CreateNewFileRequest, id: uuid.v4(), payload: undefined }); @@ -35,7 +35,7 @@ export function createFileMenu( accelerator: 'CmdOrCtrl+O', click: () => { injection.sender.send({ - type: ServerMessageType.OpenFileRequest, + type: MessageType.OpenFileRequest, id: uuid.v4(), payload: undefined }); @@ -50,7 +50,7 @@ export function createFileMenu( accelerator: 'CmdOrCtrl+Shift+N', click: () => { injection.sender.send({ - type: ServerMessageType.CreateNewPage, + type: MessageType.CreateNewPage, id: uuid.v4(), payload: undefined }); @@ -69,7 +69,7 @@ export function createFileMenu( return; } injection.sender.send({ - type: ServerMessageType.Save, + type: MessageType.Save, id: uuid.v4(), payload: { path: project.getPath(), @@ -94,7 +94,7 @@ export function createFileMenu( injection.sender.send({ id: uuid.v4(), - type: ServerMessageType.ExportSketchPage, + type: MessageType.ExportSketchPage, payload: { path: undefined } }); } @@ -109,7 +109,7 @@ export function createFileMenu( injection.sender.send({ id: uuid.v4(), - type: ServerMessageType.ExportPngPage, + type: MessageType.ExportPngPage, payload: { path: undefined } }); } @@ -128,7 +128,7 @@ export function createFileMenu( injection.sender.send({ id: uuid.v4(), - type: ServerMessageType.ExportHtmlProject, + type: MessageType.ExportHtmlProject, payload: { path: undefined } }); } diff --git a/src/electron/create-main-menu/create-library-menu.ts b/src/electron/create-main-menu/create-library-menu.ts index c4faf8b9f..eeafb7c59 100644 --- a/src/electron/create-main-menu/create-library-menu.ts +++ b/src/electron/create-main-menu/create-library-menu.ts @@ -31,7 +31,7 @@ export function createLibraryMenu( injection.sender.send({ id: uuid.v4(), payload: { library: undefined }, - type: Message.ServerMessageType.ConnectPatternLibraryRequest + type: Message.MessageType.ConnectPatternLibraryRequest }); } }, @@ -54,7 +54,7 @@ export function createLibraryMenu( .forEach(library => { injection.sender.send({ id: uuid.v4(), - type: Message.ServerMessageType.UpdatePatternLibraryRequest, + type: Message.MessageType.UpdatePatternLibraryRequest, payload: { id: library.getId() } diff --git a/src/electron/create-main-menu/create-view-menu.ts b/src/electron/create-main-menu/create-view-menu.ts index 66c1a6982..7f09aa720 100644 --- a/src/electron/create-main-menu/create-view-menu.ts +++ b/src/electron/create-main-menu/create-view-menu.ts @@ -25,7 +25,7 @@ export function createViewMenu( click: (item: Electron.MenuItem, focusedWindow: Electron.BrowserWindow) => { injection.sender.send({ id: uuid.v4(), - type: Message.ServerMessageType.Reload, + type: Message.MessageType.Reload, payload: undefined }); } @@ -36,7 +36,7 @@ export function createViewMenu( click: (item: Electron.MenuItem, focusedWindow: Electron.BrowserWindow) => { injection.sender.send({ id: uuid.v4(), - type: Message.ServerMessageType.Reload, + type: Message.MessageType.Reload, payload: { forced: true } @@ -68,7 +68,7 @@ export function createViewMenu( payload: { id: previousPage.getId() }, - type: Message.ServerMessageType.ActivatePage + type: Message.MessageType.ActivatePage }); } } @@ -86,7 +86,7 @@ export function createViewMenu( payload: { id: nextPage.getId() }, - type: Message.ServerMessageType.ActivatePage + type: Message.MessageType.ActivatePage }); } } @@ -107,7 +107,7 @@ export function createViewMenu( pane: Types.AppPane.PagesPane, visible: !app.getPanes().has(Types.AppPane.PagesPane) }, - type: Message.ServerMessageType.SetPane + type: Message.MessageType.SetPane }); } }, @@ -124,7 +124,7 @@ export function createViewMenu( pane: Types.AppPane.ElementsPane, visible: !app.getPanes().has(Types.AppPane.ElementsPane) }, - type: Message.ServerMessageType.SetPane + type: Message.MessageType.SetPane }); } }, @@ -141,7 +141,7 @@ export function createViewMenu( pane: Types.AppPane.PropertiesPane, visible: !app.getPanes().has(Types.AppPane.PropertiesPane) }, - type: Message.ServerMessageType.SetPane + type: Message.MessageType.SetPane }); } }, diff --git a/src/electron/create-server-message-handler.ts b/src/electron/create-server-message-handler.ts index 4d168d044..21fa28033 100644 --- a/src/electron/create-server-message-handler.ts +++ b/src/electron/create-server-message-handler.ts @@ -41,26 +41,26 @@ export interface ServerMessageHandlerInjection { export async function createServerMessageHandler( ctx: ServerMessageHandlerContext, injection: ServerMessageHandlerInjection -): Promise<(message: Message.ServerMessage) => Promise> { - return async function serverMessageHandler(message: Message.ServerMessage): Promise { +): Promise<(message: Message.Message) => Promise> { + return async function serverMessageHandler(message: Message.Message): Promise { injection.server.emit('message', message); // Handle messages that require // access to system / fs // tslint:disable-next-line:cyclomatic-complexity switch (message.type) { - case Message.ServerMessageType.CheckForUpdatesRequest: { + case Message.MessageType.CheckForUpdatesRequest: { if (ctx.win) { checkForUpdates(ctx.win, true); } break; } - case Message.ServerMessageType.AppLoaded: { + case Message.MessageType.AppLoaded: { const pathToOpen = await injection.ephemeralStore.getProjectPath(); injection.sender.send({ id: uuid.v4(), - type: Message.ServerMessageType.StartApp, + type: Message.MessageType.StartApp, payload: { app: await injection.ephemeralStore.getAppState(), port: ctx.port as number @@ -70,18 +70,18 @@ export async function createServerMessageHandler( if (electronIsDev && pathToOpen) { injection.sender.send({ id: uuid.v4(), - type: Message.ServerMessageType.OpenFileRequest, + type: Message.MessageType.OpenFileRequest, payload: { path: pathToOpen } }); } break; } - case Message.ServerMessageType.Reload: { + case Message.MessageType.Reload: { injection.emitter.emit('reload', message.payload || {}); break; } - case Message.ServerMessageType.CreateNewFileRequest: { + case Message.MessageType.CreateNewFileRequest: { const path = await showSaveDialog({ title: 'Create New Alva File', defaultPath: 'Untitled Project.alva', @@ -103,7 +103,7 @@ export async function createServerMessageHandler( injection.ephemeralStore.setProjectPath(path); injection.sender.send({ - type: Message.ServerMessageType.CreateNewFileResponse, + type: Message.MessageType.CreateNewFileResponse, id: message.id, payload: { path, @@ -113,7 +113,7 @@ export async function createServerMessageHandler( } break; } - case Message.ServerMessageType.OpenFileRequest: { + case Message.MessageType.OpenFileRequest: { const path = await getPath(message.payload); if (!path) { @@ -132,7 +132,7 @@ export async function createServerMessageHandler( } injection.sender.send({ - type: Message.ServerMessageType.OpenFileResponse, + type: Message.MessageType.OpenFileResponse, id: message.id, payload: { path, contents: project } }); @@ -141,7 +141,7 @@ export async function createServerMessageHandler( break; } - case Message.ServerMessageType.AssetReadRequest: { + case Message.MessageType.AssetReadRequest: { const paths = await showOpenDialog({ title: 'Select an image', properties: ['openFile'] @@ -162,14 +162,14 @@ export async function createServerMessageHandler( const mimeType = MimeTypes.lookup(path) || 'application/octet-stream'; injection.sender.send({ - type: Message.ServerMessageType.AssetReadResponse, + type: Message.MessageType.AssetReadResponse, id: message.id, payload: `data:${mimeType};base64,${content.toString('base64')}` }); break; } - case Message.ServerMessageType.Save: { + case Message.MessageType.Save: { const project = Model.Project.from(message.payload.project); project.setPath(message.payload.path); @@ -178,7 +178,7 @@ export async function createServerMessageHandler( await Persistence.persist(project.getPath(), project); break; } - case Message.ServerMessageType.CreateScriptBundleRequest: { + case Message.MessageType.CreateScriptBundleRequest: { // TODO: Come up with a proper id const compiler = createCompiler([], { cwd: process.cwd(), @@ -195,7 +195,7 @@ export async function createServerMessageHandler( const outputFileSystem = compiler.outputFileSystem; injection.sender.send({ - type: Message.ServerMessageType.CreateScriptBundleResponse, + type: Message.MessageType.CreateScriptBundleResponse, id: message.id, payload: ['renderer', 'preview'] .map(name => ({ name, path: Path.posix.join('/', `${name}.js`) })) @@ -209,7 +209,7 @@ export async function createServerMessageHandler( break; } - case Message.ServerMessageType.ConnectedPatternLibraryNotification: { + case Message.MessageType.ConnectedPatternLibraryNotification: { const project = await requestProject(injection.sender); await injection.ephemeralStore.addConnection({ @@ -220,7 +220,7 @@ export async function createServerMessageHandler( break; } - case Message.ServerMessageType.ConnectPatternLibraryRequest: { + case Message.MessageType.ConnectPatternLibraryRequest: { const project = await requestProject(injection.sender); const paths = await showOpenDialog({ @@ -261,7 +261,7 @@ export async function createServerMessageHandler( if (!previousLibrary) { injection.sender.send({ - type: Message.ServerMessageType.ConnectPatternLibraryResponse, + type: Message.MessageType.ConnectPatternLibraryResponse, id: message.id, payload: { analysis: analysisResult.result, @@ -271,7 +271,7 @@ export async function createServerMessageHandler( }); } else { injection.sender.send({ - type: Message.ServerMessageType.UpdatePatternLibraryResponse, + type: Message.MessageType.UpdatePatternLibraryResponse, id: message.id, payload: { analysis: analysisResult.result, @@ -283,7 +283,7 @@ export async function createServerMessageHandler( break; } - case Message.ServerMessageType.UpdatePatternLibraryRequest: { + case Message.MessageType.UpdatePatternLibraryRequest: { const project = await requestProject(injection.sender); const library = project.getPatternLibraryById(message.payload.id); @@ -308,7 +308,7 @@ export async function createServerMessageHandler( } injection.sender.send({ - type: Message.ServerMessageType.UpdatePatternLibraryResponse, + type: Message.MessageType.UpdatePatternLibraryResponse, id: message.id, payload: { analysis: analysisResult.result, @@ -319,12 +319,12 @@ export async function createServerMessageHandler( break; } - case Message.ServerMessageType.CheckLibraryRequest: { + case Message.MessageType.CheckLibraryRequest: { const connections = await injection.ephemeralStore.getConnections(); injection.sender.send({ id: message.id, - type: Message.ServerMessageType.CheckLibraryResponse, + type: Message.MessageType.CheckLibraryResponse, payload: message.payload.libraries.map(lib => { const connection = connections.find(c => c.libraryId === lib); @@ -337,28 +337,28 @@ export async function createServerMessageHandler( }); break; } - case Message.ServerMessageType.OpenExternalURL: { + case Message.MessageType.OpenExternalURL: { Electron.shell.openExternal(message.payload); break; } - case Message.ServerMessageType.Maximize: { + case Message.MessageType.Maximize: { if (ctx.win) { ctx.win.isMaximized() ? ctx.win.unmaximize() : ctx.win.maximize(); } break; } - case Message.ServerMessageType.ShowError: { + case Message.MessageType.ShowError: { const error = new Error(message.payload.message); error.stack = message.payload.stack; showError(error); break; } - case Message.ServerMessageType.ContextMenuRequest: { + case Message.MessageType.ContextMenuRequest: { showContextMenu(message.payload, { sender: injection.sender }); break; } - case Message.ServerMessageType.ChangeApp: { + case Message.MessageType.ChangeApp: { injection.ephemeralStore.setAppState(message.payload.app); const project = await requestProjectSafely(injection.sender); @@ -369,7 +369,7 @@ export async function createServerMessageHandler( break; } - case Message.ServerMessageType.ExportHtmlProject: { + case Message.MessageType.ExportHtmlProject: { const project = await requestProject(injection.sender); const path = await showSaveDialog({ @@ -402,7 +402,7 @@ export async function createServerMessageHandler( break; } - case Message.ServerMessageType.ExportPngPage: { + case Message.MessageType.ExportPngPage: { const project = await requestProject(injection.sender); const activePage = project.getPages().find(p => p.getActive()); @@ -438,7 +438,7 @@ export async function createServerMessageHandler( } break; } - case Message.ServerMessageType.ExportSketchPage: { + case Message.MessageType.ExportSketchPage: { const project = await requestProject(injection.sender); const activePage = project.getPages().find(p => p.getActive()); @@ -528,10 +528,10 @@ async function requestProject(sender: Sender): Promise { const projectResponse = await sender.request( { id: uuid.v4(), - type: Message.ServerMessageType.ProjectRequest, + type: Message.MessageType.ProjectRequest, payload: undefined }, - Message.ServerMessageType.ProjectResponse + Message.MessageType.ProjectResponse ); if (projectResponse.payload.status === Types.ProjectStatus.None) { diff --git a/src/electron/show-context-menu.ts b/src/electron/show-context-menu.ts index feb4cfb40..07c35f764 100644 --- a/src/electron/show-context-menu.ts +++ b/src/electron/show-context-menu.ts @@ -1,6 +1,6 @@ import { Sender } from '../sender/server'; import * as Electron from 'electron'; -import { ServerMessageType } from '../message'; +import { MessageType } from '../message'; import * as Model from '../model'; import * as Types from '../types'; import * as uuid from 'uuid'; @@ -43,7 +43,7 @@ function createElementMenu( click: () => { context.sender.send({ id: uuid.v4(), - type: ServerMessageType.PasteElementBelow, + type: MessageType.PasteElementBelow, payload: element.getId() }); } @@ -54,7 +54,7 @@ function createElementMenu( click: () => { context.sender.send({ id: uuid.v4(), - type: ServerMessageType.PasteElementInside, + type: MessageType.PasteElementInside, payload: element.getId() }); } @@ -66,7 +66,7 @@ function createElementMenu( click: () => { context.sender.send({ id: uuid.v4(), - type: ServerMessageType.DuplicateElement, + type: MessageType.DuplicateElement, payload: element.getId() }); } @@ -81,7 +81,7 @@ function createElementMenu( click: () => { context.sender.send({ id: uuid.v4(), - type: ServerMessageType.CutElement, + type: MessageType.CutElement, payload: element.getId() }); Electron.Menu.sendActionToFirstResponder('cut:'); @@ -94,7 +94,7 @@ function createElementMenu( click: () => { context.sender.send({ id: uuid.v4(), - type: ServerMessageType.CopyElement, + type: MessageType.CopyElement, payload: element.getId() }); Electron.Menu.sendActionToFirstResponder('copy:'); @@ -107,7 +107,7 @@ function createElementMenu( click: () => { context.sender.send({ id: uuid.v4(), - type: ServerMessageType.DeleteElement, + type: MessageType.DeleteElement, payload: element.getId() }); Electron.Menu.sendActionToFirstResponder('delete:'); @@ -149,7 +149,7 @@ function createLayoutMenu( pane: Types.AppPane.PagesPane, visible: !app.getPanes().has(Types.AppPane.PagesPane) }, - type: ServerMessageType.SetPane + type: MessageType.SetPane }) }, { @@ -164,7 +164,7 @@ function createLayoutMenu( pane: Types.AppPane.ElementsPane, visible: !app.getPanes().has(Types.AppPane.ElementsPane) }, - type: ServerMessageType.SetPane + type: MessageType.SetPane }) }, { @@ -179,7 +179,7 @@ function createLayoutMenu( pane: Types.AppPane.PropertiesPane, visible: !app.getPanes().has(Types.AppPane.PropertiesPane) }, - type: ServerMessageType.SetPane + type: MessageType.SetPane }) } ]; diff --git a/src/electron/start-app.ts b/src/electron/start-app.ts index 6dbf9aad4..c173085a7 100644 --- a/src/electron/start-app.ts +++ b/src/electron/start-app.ts @@ -1,6 +1,5 @@ import { startUpdater, stopUpdater } from './auto-updater'; import { createServerMessageHandler } from './create-server-message-handler'; -import { createClientMessageHandler } from './create-client-message-handler'; import * as Electron from 'electron'; import * as Ephemeral from './ephemeral-store'; import * as Events from 'events'; @@ -39,12 +38,10 @@ export async function startApp(ctx: AppContext): Promise<{ emitter: Events.Event sender }); - const clientMessageHandler = await createClientMessageHandler({ sender }); - sender.use(message => server.emit('message', message)); sender.receive(serverMessageHandler); - server.on('client-message', clientMessageHandler); + server.on('client-message', e => sender.send(e)); Electron.app.on('will-finish-launching', () => { Electron.app.on('open-file', async (event, path) => { @@ -56,7 +53,7 @@ export async function startApp(ctx: AppContext): Promise<{ emitter: Events.Event sender.send({ id: uuid.v4(), - type: Message.ServerMessageType.OpenFileRequest, + type: Message.MessageType.OpenFileRequest, payload: { path } }); }); diff --git a/src/message/index.ts b/src/message/index.ts index 85b2bc68d..2181bd1df 100644 --- a/src/message/index.ts +++ b/src/message/index.ts @@ -1,3 +1,2 @@ -export * from './preview-message'; -export * from './server-message'; -export * from './server-request-response'; +export * from './message'; +export * from './request-response'; diff --git a/src/message/server-message.ts b/src/message/message.ts similarity index 54% rename from src/message/server-message.ts rename to src/message/message.ts index 1e8811232..d05253671 100644 --- a/src/message/server-message.ts +++ b/src/message/message.ts @@ -1,7 +1,7 @@ import { Envelope, EmptyEnvelope } from './envelope'; import * as Types from '../types'; -export enum ServerMessageType { +export enum MessageType { ActivatePage = 'activate-page', AppLoaded = 'app-loaded', AppRequest = 'app-request', @@ -34,6 +34,8 @@ export enum ServerMessageType { ChangeActivePage = 'change-active-page', ChangeApp = 'change-app', ChangeElements = 'change-elements', + ChangeElementActions = 'change-element-actions', + ChangeElementContents = 'change-element-contents', ChangeHighlightedElement = 'change-highlighted-element', ChangeHighlightedElementContent = 'change-highlighted-element-content', ChangePages = 'change-pages', @@ -71,10 +73,11 @@ export enum ServerMessageType { UnselectElement = 'unselect-element', UpdatePatternLibraryRequest = 'update-pattern-library-request', UpdatePatternLibraryResponse = 'update-pattern-library-response', - UnHighlightElement = 'unhighlight-element' + UnHighlightElement = 'unhighlight-element', + UserStoreChange = 'user-store-change' } -export type ServerMessage = +export type Message = | ActivatePage | AppRequest | AppResponse @@ -97,6 +100,8 @@ export type ServerMessage = | ChangeActivePage | ChangeApp | ChangeElements + | ChangeElementActions + | ChangeElementContents | ChangeHighlightedElement | ChangeSelectedElement | ChangeHighlightedElementContent @@ -143,65 +148,71 @@ export type ServerMessage = | UnselectElement | UpdatePatternLibraryRequest | UpdatePatternLibraryResponse - | UnHighlightElement; + | UnHighlightElement + | ChangeUserStore; -export type ActivatePage = Envelope; -export type AppLoaded = EmptyEnvelope; -export type AppRequest = EmptyEnvelope; -export type AppResponse = Envelope; -export type AssetReadRequest = EmptyEnvelope; -export type AssetReadResponse = Envelope; -export type ChangeActivePage = Envelope; -export type ChangeApp = Envelope; +export type ActivatePage = Envelope; +export type AppLoaded = EmptyEnvelope; +export type AppRequest = EmptyEnvelope; +export type AppResponse = Envelope; +export type AssetReadRequest = EmptyEnvelope; +export type AssetReadResponse = Envelope; +export type ChangeActivePage = Envelope; +export type ChangeApp = Envelope; export type ChangeElements = Envelope< - ServerMessageType.ChangeElements, - { elements: Types.SerializedElement[]; elementContents: Types.SerializedElementContent[] } + MessageType.ChangeElements, + { elements: Types.SerializedElement[] } >; -export type ChangeHighlightedElement = Envelope< - ServerMessageType.ChangeHighlightedElement, - string | undefined +export type ChangeElementContents = Envelope< + MessageType.ChangeElementContents, + { elementContents: Types.SerializedElementContent[] } +>; +export type ChangeElementActions = Envelope< + MessageType.ChangeElementActions, + { elementActions: Types.SerializedElementAction[] } >; -export type ChangeSelectedElement = Envelope< - ServerMessageType.ChangeSelectedElement, +export type ChangeHighlightedElement = Envelope< + MessageType.ChangeHighlightedElement, string | undefined >; +export type ChangeSelectedElement = Envelope; export type ChangeHighlightedElementContent = Envelope< - ServerMessageType.ChangeHighlightedElementContent, + MessageType.ChangeHighlightedElementContent, string | undefined >; export type ChangeSelectedElementContent = Envelope< - ServerMessageType.ChangeSelectedElementContent, + MessageType.ChangeSelectedElementContent, string | undefined >; export type ChangePages = Envelope< - ServerMessageType.ChangePages, + MessageType.ChangePages, { pages: Types.SerializedPage[]; } >; -export type CheckForUpdatesRequest = EmptyEnvelope; +export type CheckForUpdatesRequest = EmptyEnvelope; export type CheckLibraryRequest = Envelope< - ServerMessageType.CheckLibraryRequest, + MessageType.CheckLibraryRequest, { libraries: string[]; } >; export type CheckLibraryResponse = Envelope< - ServerMessageType.CheckLibraryResponse, + MessageType.CheckLibraryResponse, Types.LibraryCheckPayload[] >; export type ConnectedPatternLibraryNotification = Envelope< - ServerMessageType.ConnectedPatternLibraryNotification, + MessageType.ConnectedPatternLibraryNotification, Types.LibraryNotificationPayload >; export type ConnectPatternLibraryRequest = Envelope< - ServerMessageType.ConnectPatternLibraryRequest, + MessageType.ConnectPatternLibraryRequest, { library: string | undefined; } >; export type ConnectPatternLibraryResponse = Envelope< - ServerMessageType.ConnectPatternLibraryResponse, + MessageType.ConnectPatternLibraryResponse, { analysis: Types.LibraryAnalysis; path: string; @@ -209,110 +220,103 @@ export type ConnectPatternLibraryResponse = Envelope< } >; export type ContextMenuRequst = Envelope< - ServerMessageType.ContextMenuRequest, + MessageType.ContextMenuRequest, Types.ContextMenuRequestPayload >; -export type ContentRequest = EmptyEnvelope; -export type ContentResponse = Envelope; -export type Copy = EmptyEnvelope; -export type CopyPageElement = Envelope; -export type CreateNewPage = Envelope; +export type ContentRequest = EmptyEnvelope; +export type ContentResponse = Envelope; +export type Copy = EmptyEnvelope; +export type CopyPageElement = Envelope; +export type CreateNewPage = Envelope; export type CreateScriptBundleRequest = Envelope< - ServerMessageType.CreateScriptBundleRequest, + MessageType.CreateScriptBundleRequest, Types.SerializedProject >; export type CreateScriptBundleResponse = Envelope< - ServerMessageType.CreateScriptBundleResponse, + MessageType.CreateScriptBundleResponse, Types.FilePayload[] >; -export type Cut = EmptyEnvelope; -export type Delete = EmptyEnvelope; -export type HighlightElement = Envelope; -export type KeyboardChange = Envelope; -export type NewFileRequest = EmptyEnvelope; -export type NewFileResponse = Envelope< - ServerMessageType.CreateNewFileResponse, - Types.ProjectPayload ->; -export type CutPageElement = Envelope; -export type DeletePageElement = Envelope; -export type Duplicate = EmptyEnvelope; -export type DuplicatePageElement = Envelope; +export type Cut = EmptyEnvelope; +export type Delete = EmptyEnvelope; +export type HighlightElement = Envelope; +export type KeyboardChange = Envelope; +export type NewFileRequest = EmptyEnvelope; +export type NewFileResponse = Envelope; +export type CutPageElement = Envelope; +export type DeletePageElement = Envelope; +export type Duplicate = EmptyEnvelope; +export type DuplicatePageElement = Envelope; // tslint:disable-next-line:no-any -export type Log = Envelope; -export type Maximize = EmptyEnvelope; -export type OpenExternalURL = Envelope; -export type OpenFileRequest = Envelope< - ServerMessageType.OpenFileRequest, - { path: string } | undefined ->; -export type OpenFileResponse = Envelope; -export type PageChange = Envelope; -export type ProjectChange = Envelope; -export type Paste = EmptyEnvelope; -export type PastePageElementBelow = Envelope; -export type PastePageElementInside = Envelope; -export type ProjectRequest = EmptyEnvelope; +export type Log = Envelope; +export type Maximize = EmptyEnvelope; +export type OpenExternalURL = Envelope; +export type OpenFileRequest = Envelope; +export type OpenFileResponse = Envelope; +export type PageChange = Envelope; +export type ProjectChange = Envelope; +export type Paste = EmptyEnvelope; +export type PastePageElementBelow = Envelope; +export type PastePageElementInside = Envelope; +export type ProjectRequest = EmptyEnvelope; export type ProjectResponse = Envelope< - ServerMessageType.ProjectResponse, + MessageType.ProjectResponse, { data: Types.SerializedProject | undefined; status: Types.ProjectStatus } >; -export type Redo = EmptyEnvelope; -export type Reload = Envelope; -export type Save = Envelope; -export type SelectElement = Envelope; -export type SetPane = Envelope< - ServerMessageType.SetPane, - { pane: Types.AppPane; visible: boolean } ->; -export type ShowError = Envelope; +export type Redo = EmptyEnvelope; +export type Reload = Envelope; +export type Save = Envelope; +export type SelectElement = Envelope; +export type SetPane = Envelope; +export type ShowError = Envelope; export type SketchExportRequest = Envelope< - ServerMessageType.SketchExportRequest, + MessageType.SketchExportRequest, Types.SketchExportPayload >; -export type SketchExportResponse = Envelope; +export type SketchExportResponse = Envelope; export type StartAppMessage = Envelope< - ServerMessageType.StartApp, + MessageType.StartApp, { app: Types.SerializedAlvaApp | undefined; port: number; } >; export type ChangePatternLibraries = Envelope< - ServerMessageType.ChangePatternLibraries, + MessageType.ChangePatternLibraries, { patternLibraries: Types.SerializedPatternLibrary[]; } >; -export type Undo = EmptyEnvelope; -export type UnselectElement = EmptyEnvelope; +export type Undo = EmptyEnvelope; +export type UnselectElement = EmptyEnvelope; export type UpdatePatternLibraryRequest = Envelope< - ServerMessageType.UpdatePatternLibraryRequest, + MessageType.UpdatePatternLibraryRequest, { id: string; } >; export type UpdatePatternLibraryResponse = Envelope< - ServerMessageType.UpdatePatternLibraryResponse, + MessageType.UpdatePatternLibraryResponse, { analysis: Types.LibraryAnalysis; path: string; previousLibraryId: string; } >; -export type UnHighlightElement = EmptyEnvelope; +export type UnHighlightElement = EmptyEnvelope; export type ExportHtmlProject = Envelope< - ServerMessageType.ExportHtmlProject, - { path: string | undefined } ->; -export type ExportPngPage = Envelope; -export type ExportSketchPage = Envelope< - ServerMessageType.ExportSketchPage, + MessageType.ExportHtmlProject, { path: string | undefined } >; +export type ExportPngPage = Envelope; +export type ExportSketchPage = Envelope; export type ChangeProject = Envelope< - ServerMessageType.ChangeProject, + MessageType.ChangeProject, { project: Types.SerializedProject } >; + +export type ChangeUserStore = Envelope< + MessageType.UserStoreChange, + { userStore: Types.SerializedUserStore } +>; diff --git a/src/message/preview-message.ts b/src/message/preview-message.ts deleted file mode 100644 index 382995a24..000000000 --- a/src/message/preview-message.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Envelope, EmptyEnvelope } from './envelope'; - -export enum PreviewMessageType { - ActivatePage = 'activate-page', - ClickElement = 'click-element', - ContentResponse = 'content-response', - HighlightElement = 'highlight-element', - KeyboardChange = 'keyboard-change', - Reload = 'reload', - SelectElement = 'select-element', - SketchExportRequest = 'sketch-export-request', - SketchExportResponse = 'sketch-export-response', - State = 'state', - UnselectElement = 'unselect-element', - UnHighlightElement = 'unhighlight-element' -} - -export type PreviewMessage = - | PreviewActivatePage - | PreviewHighlightElement - | PreviewSelectElement - | PreviewClickElement - | PreviewContentResponse - | PreviewSelectElement - | PreviewSketchExportResponse - | PreviewUnselectElement - | PreviewUnHighlightElement - | PreviewKeyboardChange; - -export type PreviewActivatePage = Envelope; -export type PreviewClickElement = Envelope; -export type PreviewContentResponse = Envelope; -export type PreviewHighlightElement = Envelope; -export type PreviewSelectElement = Envelope; -export type PreviewSketchExportResponse = Envelope; -export type PreviewUnselectElement = EmptyEnvelope; -export type PreviewKeyboardChange = Envelope< - PreviewMessageType.KeyboardChange, - { metaDown: boolean } ->; -export type PreviewUnHighlightElement = EmptyEnvelope; diff --git a/src/message/request-response.ts b/src/message/request-response.ts new file mode 100644 index 000000000..abe9b10cd --- /dev/null +++ b/src/message/request-response.ts @@ -0,0 +1,13 @@ +import * as Message from './message'; + +export type RequestResponsePair = AppRequestResponsePair | ProjectRequestResponsePair; + +export interface AppRequestResponsePair { + request: Message.AppRequest; + response: Message.AppResponse; +} + +export interface ProjectRequestResponsePair { + request: Message.ProjectRequest; + response: Message.ProjectResponse; +} diff --git a/src/message/server-request-response.ts b/src/message/server-request-response.ts deleted file mode 100644 index 39e9de48a..000000000 --- a/src/message/server-request-response.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as ServerMessage from './server-message'; - -export type RequestResponsePair = AppRequestResponsePair | ProjectRequestResponsePair; - -export interface AppRequestResponsePair { - request: ServerMessage.AppRequest; - response: ServerMessage.AppResponse; -} - -export interface ProjectRequestResponsePair { - request: ServerMessage.ProjectRequest; - response: ServerMessage.ProjectResponse; -} diff --git a/src/model/element-action.ts b/src/model/element-action.ts index fea3beb1e..b3d597edb 100644 --- a/src/model/element-action.ts +++ b/src/model/element-action.ts @@ -1,5 +1,7 @@ import * as Mobx from 'mobx'; +import * as _ from 'lodash'; import * as Types from '../types'; +import { UserStore } from './user-store'; import * as uuid from 'uuid'; export interface ElementActionInit { @@ -11,40 +13,76 @@ export interface ElementActionInit { export class ElementAction { private id: string; + private userStore: UserStore; @Mobx.observable private payload: string; @Mobx.observable private storeActionId: string; @Mobx.observable private storePropertyId: string; - public constructor(init: ElementActionInit) { + public constructor(init: ElementActionInit, ctx: { userStore: UserStore }) { this.id = init.id; this.payload = init.payload; this.storeActionId = init.storeActionId; this.storePropertyId = init.storePropertyId; + this.userStore = ctx.userStore; } - public static from(serialized: Types.SerializedElementAction): ElementAction { - return new ElementAction({ - id: serialized.id, - payload: serialized.payload || '', - storeActionId: serialized.storeActionId, - storePropertyId: serialized.storePropertyId - }); + public static from( + serialized: Types.SerializedElementAction, + ctx: { userStore: UserStore } + ): ElementAction { + return new ElementAction( + { + id: serialized.id, + payload: serialized.payload || '', + storeActionId: serialized.storeActionId, + storePropertyId: serialized.storePropertyId + }, + ctx + ); } public clone(): ElementAction { - return new ElementAction({ - id: uuid.v4(), - payload: this.payload, - storeActionId: this.storeActionId, - storePropertyId: this.storePropertyId - }); + return new ElementAction( + { + id: uuid.v4(), + payload: this.payload, + storeActionId: this.storeActionId, + storePropertyId: this.storePropertyId + }, + { userStore: this.userStore } + ); + } + + public equals(b: this): boolean { + return _.isEqual(this.toJSON(), b.toJSON()); + } + + public execute(): void { + const storeAction = this.userStore.getActionById(this.storeActionId); + + if (!storeAction) { + return; + } + + const storeProperty = this.userStore.getPropertyById(this.storePropertyId); + + if (!storeProperty) { + return; + } + + switch (storeAction.getType()) { + case Types.UserStoreActionType.Noop: + return; + case Types.UserStoreActionType.Set: + storeProperty.setPayload(this.payload); + } } public getId(): string { return this.id; } - public getPayload(): string | undefined { + public getPayload(): string { return this.payload; } @@ -79,4 +117,12 @@ export class ElementAction { public unsetStorePropertyId(): void { this.storePropertyId = ''; } + + @Mobx.action + public update(after: this): void { + this.id = after.id; + this.payload = after.payload; + this.storeActionId = after.storeActionId; + this.storePropertyId = after.storePropertyId; + } } diff --git a/src/model/page/page.ts b/src/model/page/page.ts index e1547ba4a..6e07b2d7c 100644 --- a/src/model/page/page.ts +++ b/src/model/page/page.ts @@ -23,44 +23,22 @@ export interface PageContext { } export class Page { - @Mobx.observable private active: boolean; - @Mobx.observable private focused: boolean; - - /** - * Intermediary edited name - */ @Mobx.observable private editedName: string = ''; - - /** - * The technical unique identifier of this page - */ @Mobx.observable private id: string; - - /** - * The human-friendly name of the page. - * In the frontend, to be displayed instead of the ID. - */ @Mobx.observable private name: string = 'Page'; - - /** - * Wether the name may be edited - */ @Mobx.observable public nameState: Types.EditableTitleState = Types.EditableTitleState.Editable; private project: Project; - - /** - * The root element of the page, the first pattern element of the content tree. - */ private rootId: string; public constructor(init: PageInit, context: PageContext) { - this.active = init.active; + this.project = context.project; this.id = init.id; + + this.active = init.active; this.rootId = init.rootId; this.name = init.name; - this.project = context.project; const rootElement = this.getRoot(); @@ -69,6 +47,25 @@ export class Page { } } + @Mobx.computed + private get active(): boolean { + return ( + this.project + .getUserStore() + .getPageProperty() + .getPayload() === this.id + ); + } + + private set active(active: boolean) { + if (active) { + this.project + .getUserStore() + .getPageProperty() + .setPayload(this.id); + } + } + @Mobx.action public static create(init: PageCreateInit, context: PageContext): Page { const patternLibrary = context.project.getBuiltinPatternLibrary(); @@ -127,12 +124,6 @@ export class Page { ); } - /** - * Loads and returns a page from a given JSON object. - * @param jsonObject The JSON object to load from. - * @param id The ID of the resulting page - * @return A new page object containing the loaded data. - */ public static from(serializedPage: Types.SerializedPage, context: PageContext): Page { const page = new Page( { @@ -189,19 +180,10 @@ export class Page { return rootElement.getElementById(id); } - /** - * Returns the technical (internal) ID of the page. - * @return The technical (internal) ID of the page. - */ public getId(): string { return this.id; } - /** - * Returns the human-friendly name of the page. - * In the frontend, to be displayed instead of the ID. - * @return The human-friendly name of the page. - */ public getName(options?: { unedited: boolean }): string { if ((!options || !options.unedited) && this.nameState === Types.EditableTitleState.Editing) { return this.editedName; @@ -209,18 +191,10 @@ export class Page { return this.name; } - /** - * Get the editable state of the page name - * @param state - */ public getNameState(): Types.EditableTitleState { return this.nameState; } - /** - * Returns the root element of the page, the first pattern element of the content tree. - * @return The root element of the page. - */ public getRoot(): Element | undefined { return this.project.getElementById(this.rootId); } diff --git a/src/model/project.ts b/src/model/project.ts index 9c7f2d112..763831e0b 100644 --- a/src/model/project.ts +++ b/src/model/project.ts @@ -1,7 +1,7 @@ import { Element, ElementContent } from './element'; import { ElementAction } from './element-action'; -import { isEqual } from 'lodash'; import * as Mobx from 'mobx'; +import * as _ from 'lodash'; import { Page } from './page'; import { Pattern, PatternSlot } from './pattern'; import { PatternLibrary } from './pattern-library'; @@ -152,19 +152,16 @@ export class Project { ); serialized.elementActions.forEach(elementAction => { - project.addElementAction(ElementAction.from(elementAction)); + project.addElementAction(ElementAction.from(elementAction, { userStore })); }); return project; } - public static isEqual(a: Types.SavedProject, b: Types.SavedProject): boolean; - public static isEqual( - a: Types.SavedProject | Project, - b: Types.SavedProject | Project - ): boolean { + public static equals(a: Types.SavedProject, b: Types.SavedProject): boolean; + public static equals(a: Types.SavedProject | Project, b: Types.SavedProject | Project): boolean { const toData = input => (input instanceof Project ? input.toDisk() : input); - return isEqual(toData(a), toData(b)); + return _.isEqual(toData(a), toData(b)); } @Mobx.action @@ -351,6 +348,11 @@ export class Project { }); } + @Mobx.action + public removeElementAction(elementAction: ElementAction): void { + this.elementActions.delete(elementAction.getId()); + } + @Mobx.action public removeElementContent(elementContent: ElementContent): void { elementContent.getElements().forEach(element => { diff --git a/src/model/user-store-action/user-store-action.ts b/src/model/user-store-action/user-store-action.ts index 3ff2ae510..8689b8513 100644 --- a/src/model/user-store-action/user-store-action.ts +++ b/src/model/user-store-action/user-store-action.ts @@ -1,4 +1,5 @@ import * as Types from '../../types'; +import * as _ from 'lodash'; import * as Mobx from 'mobx'; import { UserStore } from '../user-store'; import { UserStoreProperty } from '../user-store-property'; @@ -48,6 +49,10 @@ export class UserStoreAction { }); } + public equals(b: UserStoreAction): boolean { + return _.isEqual(this.toJSON(), b.toJSON()); + } + public getAcceptsProperty(): boolean { return this.acceptsProperty; } @@ -95,6 +100,15 @@ export class UserStoreAction { public unsetUserStoreProperty(): void { this.userStorePropertyId = undefined; } + + @Mobx.action + public update(b: this): void { + this.acceptsProperty = b.acceptsProperty; + this.id = b.id; + this.name = b.name; + this.userStorePropertyId = b.userStorePropertyId; + this.type = b.type; + } } function deserializeType(type: Types.SerializedUserStoreActionType): Types.UserStoreActionType { diff --git a/src/model/user-store-property/user-store-property.ts b/src/model/user-store-property/user-store-property.ts index d6f773e38..acd4bc9af 100644 --- a/src/model/user-store-property/user-store-property.ts +++ b/src/model/user-store-property/user-store-property.ts @@ -1,3 +1,4 @@ +import * as _ from 'lodash'; import * as Mobx from 'mobx'; import * as Types from '../../types'; @@ -35,6 +36,10 @@ export class UserStoreProperty { }); } + public equals(b: UserStoreProperty): boolean { + return _.isEqual(this.toJSON(), b.toJSON()); + } + public getId(): string { return this.id; } @@ -69,6 +74,14 @@ export class UserStoreProperty { type: serializeType(this.type) }; } + + @Mobx.action + public update(b: UserStoreProperty): void { + this.id = b.id; + this.name = b.name; + this.payload = b.payload; + this.type = b.type; + } } function deserializeType(type: Types.SerializedUserStorePropertyType): Types.UserStorePropertyType { diff --git a/src/model/user-store.ts b/src/model/user-store.ts index ae8c6c5e7..65af7555c 100644 --- a/src/model/user-store.ts +++ b/src/model/user-store.ts @@ -1,4 +1,6 @@ +import { computeDifference } from '../alva-util'; import * as Mobx from 'mobx'; +import * as Message from '../message'; import { Project } from './project'; import * as Types from '../types'; import { UserStoreAction } from './user-store-action'; @@ -15,14 +17,14 @@ export interface UserStoreContext { } export class UserStore { - @Mobx.observable private actions: UserStoreAction[] = []; private id: string; - @Mobx.observable private properties: UserStoreProperty[] = []; + @Mobx.observable private actions: Map = new Map(); + @Mobx.observable private properties: Map = new Map(); public constructor(init: UserStoreInit) { this.id = init.id; - this.properties = init.properties; - this.actions = init.actions; + init.properties.forEach(prop => this.addProperty(prop)); + init.actions.forEach(action => this.addAction(action)); } public static from(serialized: Types.SerializedUserStore): UserStore { @@ -33,38 +35,82 @@ export class UserStore { }); } + @Mobx.action + public addAction(action: UserStoreAction): void { + this.actions.set(action.getId(), action); + } + @Mobx.action public addProperty(property: UserStoreProperty): void { - this.properties.push(property); + this.properties.set(property.getId(), property); } public getActionById(id: string): UserStoreAction | undefined { - return this.actions.find(a => a.getId() === id); + return this.actions.get(id); } public getActions(): UserStoreAction[] { - return this.actions; + return [...this.actions.values()]; } public getNoopAction(): UserStoreAction { - return this.actions.find( + return this.getActions().find( a => a.getType() === Types.UserStoreActionType.Noop ) as UserStoreAction; } + public getPageProperty(): UserStoreProperty { + return this.getProperties().find( + p => p.getType() === Types.UserStorePropertyType.Page + ) as UserStoreProperty; + } + public getProperties(): UserStoreProperty[] { - return this.properties; + return [...this.properties.values()]; } public getPropertyById(id: string): UserStoreProperty | undefined { - return this.properties.find(p => p.getId() === id); + return this.properties.get(id); + } + + @Mobx.action + public removeAction(property: UserStoreAction): void { + this.actions.delete(property.getId()); + } + + @Mobx.action + public removeProperty(property: UserStoreProperty): void { + this.properties.delete(property.getId()); + } + + @Mobx.action + public sync(message: Message.ChangeUserStore): void { + const userStore = UserStore.from(message.payload.userStore); + + const propertyChanges = computeDifference({ + before: this.getProperties(), + after: userStore.getProperties() + }); + + propertyChanges.added.forEach(change => this.addProperty(change.after)); + propertyChanges.changed.forEach(change => change.before.update(change.after)); + propertyChanges.removed.forEach(change => this.removeProperty(change.before)); + + const actionChanges = computeDifference({ + before: this.getActions(), + after: userStore.getActions() + }); + + actionChanges.added.forEach(change => this.addAction(change.after)); + actionChanges.changed.forEach(change => change.before.update(change.after)); + actionChanges.removed.forEach(change => this.removeAction(change.before)); } public toJSON(): Types.SerializedUserStore { return { - actions: this.actions.map(a => a.toJSON()), + actions: this.getActions().map(a => a.toJSON()), id: this.id, - properties: this.properties.map(p => p.toJSON()) + properties: this.getProperties().map(p => p.toJSON()) }; } } diff --git a/src/preview/compute-difference.ts b/src/preview/compute-difference.ts deleted file mode 100644 index 02354bc9f..000000000 --- a/src/preview/compute-difference.ts +++ /dev/null @@ -1,66 +0,0 @@ -// import * as _ from 'lodash'; - -export interface Difference { - added: AddItem[]; - changed: ChangeItem[]; - removed: RemoveItem[]; -} - -export interface AddItem { - before: undefined; - after: T; -} - -export interface ChangeItem { - before: T; - after: T; -} - -export interface RemoveItem { - before: T; - after: undefined; -} - -export type Diffable = Identifyable & Equalifyable; - -export interface Identifyable { - getId(): string; -} - -export interface Equalifyable { - equals(this: T, a: T): boolean; -} - -export function computeDifference>(a: T[], b: T[]): Difference { - const added = a.filter(ai => !b.find(bi => bi.getId() === ai.getId())); - - const removed = b - .filter(bi => !added.includes(bi)) - .filter(bi => !b.find(ai => ai.getId() === ai.getId())); - - const changed = a - .filter(item => !added.includes(item)) - .filter(el => !removed.includes(el)) - .filter(ai => { - const aid = ai.getId(); - const bi = b.find(i => i.getId() === aid); - return bi && !ai.equals(bi); - }); - - return { - added: added.map(item => ({ before: undefined, after: item })), - changed: changed.map(after => ({ - after, - before: b.find(bc => after.getId() === bc.getId()) as T - })), - removed: removed.map(item => ({ before: item, after: undefined })) - }; -} - -/* function withEquals>(a: T, b: T): boolean { - return a.equals(b); -} - -function withId>(a: T, b: T): boolean { - return a.getId() === b.getId(); -} */ diff --git a/src/preview/preview-store.ts b/src/preview/preview-store.ts index 2655bef3a..9cd2e952d 100644 --- a/src/preview/preview-store.ts +++ b/src/preview/preview-store.ts @@ -145,8 +145,27 @@ export class PreviewStore { return renderProperties; } - // TODO: Restore event handler things here - renderProperties[patternProperty.getPropertyName()] = elementProperty.getValue(); + if (patternProperty.getType() === Types.PatternPropertyType.EventHandler) { + renderProperties[patternProperty.getPropertyName()] = e => { + if (this.mode !== Types.PreviewDocumentMode.Static && !this.getMetaDown()) { + return; + } + + const elementAction = this.project.getElementActionById( + elementProperty.getValue() as string + ); + + if (!elementAction) { + return; + } + + e.preventDefault(); + elementAction.execute(); + }; + } else { + renderProperties[patternProperty.getPropertyName()] = elementProperty.getValue(); + } + return renderProperties; }, {}); } @@ -192,7 +211,9 @@ export class PreviewStore { } public onElementClick(e: MouseEvent, element: Model.Element): void { - console.log('onElementClick!'); + if (!this.getMetaDown()) { + e.preventDefault(); + } } public onElementMouseOver(e: MouseEvent, element: Model.Element): void { @@ -211,7 +232,7 @@ export class PreviewStore { this.sender.send({ id: uuid.v4(), payload: undefined, - type: Message.PreviewMessageType.UnHighlightElement + type: Message.MessageType.UnHighlightElement }); return; } @@ -221,7 +242,7 @@ export class PreviewStore { payload: { id: element.getId() }, - type: Message.PreviewMessageType.HighlightElement + type: Message.MessageType.HighlightElement }); }); } @@ -247,7 +268,7 @@ export class PreviewStore { payload: { id: element.getId() }, - type: Message.PreviewMessageType.SelectElement + type: Message.MessageType.SelectElement }); }); } @@ -268,13 +289,13 @@ export class PreviewStore { this.sender.send({ id: uuid.v4(), payload: undefined, - type: Message.PreviewMessageType.UnselectElement + type: Message.MessageType.UnselectElement }); this.sender.send({ id: uuid.v4(), payload: undefined, - type: Message.PreviewMessageType.UnHighlightElement + type: Message.MessageType.UnHighlightElement }); } diff --git a/src/preview/preview.ts b/src/preview/preview.ts index 29094ee5b..6ee49f1af 100644 --- a/src/preview/preview.ts +++ b/src/preview/preview.ts @@ -1,4 +1,4 @@ -import { computeDifference } from './compute-difference'; +import { computeDifference } from '../alva-util'; import { ElementArea } from './element-area'; import exportToSketchData from './export-to-sketch-data'; import { getComponents } from './get-components'; @@ -76,45 +76,33 @@ function main(): void { sender.receive(message => { Mobx.transaction(() => { switch (message.type) { - case Message.ServerMessageType.KeyboardChange: { + case Message.MessageType.KeyboardChange: { store.setMetaDown(message.payload.metaDown); break; } - case Message.ServerMessageType.ChangePages: { - const pages = message.payload.pages.map(p => Model.Page.from(p, { project })); - const previousPages = project.getPages(); - const pageChanges = computeDifference(pages, previousPages); + case Message.MessageType.ChangePages: { + const changes = computeDifference({ + after: message.payload.pages.map(p => Model.Page.from(p, { project })), + before: project.getPages() + }); - pageChanges.added.forEach(change => project.addPage(change.after)); - pageChanges.changed.forEach(change => change.before.update(change.after)); - pageChanges.removed.forEach(change => project.removePage(change.before)); + changes.added.forEach(change => project.addPage(change.after)); + changes.changed.forEach(change => change.before.update(change.after)); + changes.removed.forEach(change => project.removePage(change.before)); break; } - case Message.ServerMessageType.ChangeElements: { - const contents = message.payload.elementContents.map(e => - Model.ElementContent.from(e, { project }) - ); - const previousContents = project.getElementContents(); - const contentChanges = computeDifference( - contents, - previousContents - ); - + case Message.MessageType.ChangeElements: { const els = message.payload.elements.map(e => Model.Element.from(e, { project })); - const previousElements = project.getElements(); - const elementChanges = computeDifference(els, previousElements); - - 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)); + const changes = computeDifference({ + before: project.getElements(), + after: els + }); - elementChanges.added.forEach(change => project.addElement(change.after)); - elementChanges.removed.forEach(change => project.removeElement(change.before)); - elementChanges.changed.forEach(change => change.before.update(change.after)); + 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)); // TODO: The explicit highlighting / selecting below should be done by the diffing, // appears to be broken by a bug, though @@ -137,15 +125,37 @@ function main(): void { break; } - case Message.ServerMessageType.ChangePatternLibraries: { - const libraries = message.payload.patternLibraries.map(e => - Model.PatternLibrary.from(e) - ); - - const libraryChanges = computeDifference( - libraries, - project.getPatternLibraries() + case Message.MessageType.ChangeElementContents: { + 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)); + break; + } + case Message.MessageType.ChangeElementActions: { + 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)); + break; + } + case Message.MessageType.ChangePatternLibraries: { + const libraryChanges = computeDifference({ + before: project.getPatternLibraries(), + after: message.payload.patternLibraries.map(e => Model.PatternLibrary.from(e)) + }); libraryChanges.added.forEach(change => { const script = document.createElement('script'); @@ -180,20 +190,20 @@ function main(): void { }); // (5) Maintain selection area - Mobx.autorun(() => { - const selectedElement = store.getSelectedElement(); - - const selectionNode = document.getElementById('preview-selection') as HTMLElement; - const selectionArea = store.getSelectionArea(); + Mobx.reaction( + () => store.getSelectedElement(), + selectedElement => { + const selectionArea = store.getSelectionArea(); + selectedElement ? selectionArea.show() : selectionArea.hide(); - store.hasSelectedItem() ? selectionArea.show() : selectionArea.hide(); + if (selectedElement && selectedElement.getRole() === Types.ElementRole.Root) { + selectionArea.hide(); + } - if (selectedElement && selectedElement.getRole() === Types.ElementRole.Root) { - selectionArea.hide(); + const selectionNode = document.getElementById('preview-selection') as HTMLElement; + selectionArea.write(selectionNode); } - - selectionArea.write(selectionNode); - }); + ); // (6) Maintain highlight area Mobx.autorun(() => { @@ -217,10 +227,20 @@ function main(): void { payload: { metaDown }, - type: Message.PreviewMessageType.KeyboardChange + type: Message.MessageType.KeyboardChange }); } ); + + Mobx.autorun(() => { + const userStore = store.getProject().getUserStore(); + + sender.send({ + id: uuid.v4(), + payload: { userStore: userStore.toJSON() }, + type: Message.MessageType.UserStoreChange + }); + }); } } diff --git a/src/renderer/create-change-notifiers.ts b/src/renderer/create-change-notifiers.ts index 58cab05f0..1f1891c37 100644 --- a/src/renderer/create-change-notifiers.ts +++ b/src/renderer/create-change-notifiers.ts @@ -16,72 +16,87 @@ export function createChangeNotifiers({ app, store }: NotifierContext): void { scheduler: window.requestIdleCallback }; - const send = ({ type, payload }) => { + Mobx.autorun(() => { Sender.send({ id: uuid.v4(), - payload, - type - }); - }; - - // Notifiy preview about page changes - Mobx.autorun(() => { - send({ payload: { pages: store.getPages().map(p => p.toJSON()) }, - type: Message.ServerMessageType.ChangePages + type: Message.MessageType.ChangePages }); }); - // Notify preview about element / element content changes Mobx.autorun(() => { const elements = store.getElements().map(e => e.toJSON()); + + Sender.send({ + id: uuid.v4(), + payload: { elements }, + type: Message.MessageType.ChangeElements + }); + }); + + Mobx.autorun(() => { const elementContents = store.getElementContents().map(e => e.toJSON()); - send({ - payload: { elements, elementContents }, - type: Message.ServerMessageType.ChangeElements + Sender.send({ + id: uuid.v4(), + payload: { elementContents }, + type: Message.MessageType.ChangeElementContents + }); + }); + + Mobx.autorun(() => { + const elementActions = store.getElementActions().map(e => e.toJSON()); + + Sender.send({ + id: uuid.v4(), + payload: { elementActions }, + type: Message.MessageType.ChangeElementActions }); }); Mobx.autorun(() => { const metaDown = store.getMetaDown(); - send({ + Sender.send({ + id: uuid.v4(), payload: { metaDown }, - type: Message.ServerMessageType.KeyboardChange + type: Message.MessageType.KeyboardChange }); }); Mobx.autorun(() => { const patternLibraries = store.getPatternLibraries(); - send({ + Sender.send({ + id: uuid.v4(), payload: { patternLibraries: patternLibraries.map(l => l.toJSON()) }, - type: Message.ServerMessageType.ChangePatternLibraries + type: Message.MessageType.ChangePatternLibraries }); - send({ + Sender.send({ + id: uuid.v4(), payload: { libraries: patternLibraries .filter(l => l.getOrigin() === Types.PatternLibraryOrigin.UserProvided) .map(l => l.getId()) }, - type: Message.ServerMessageType.CheckLibraryRequest + type: Message.MessageType.CheckLibraryRequest }); }, opts); Mobx.autorun(() => { - send({ + Sender.send({ + id: uuid.v4(), payload: { app: store.getApp().toJSON() }, - type: Message.ServerMessageType.ChangeApp + type: Message.MessageType.ChangeApp }); }, opts); } diff --git a/src/renderer/create-edit-message-handler.ts b/src/renderer/create-edit-message-handler.ts index 1fdc07216..779cca91b 100644 --- a/src/renderer/create-edit-message-handler.ts +++ b/src/renderer/create-edit-message-handler.ts @@ -3,7 +3,7 @@ import * as Model from '../model'; import { ViewStore } from '../store'; import * as Types from '../types'; -export type EditMessageHandler = (message: Message.ServerMessage) => void; +export type EditMessageHandler = (message: Message.Message) => void; export function createEditMessageHandler({ app, @@ -13,22 +13,22 @@ export function createEditMessageHandler({ app: Model.AlvaApp; }): EditMessageHandler { // tslint:disable-next-line:cyclomatic-complexity - return function editMessageHandler(message: Message.ServerMessage): void { + return function editMessageHandler(message: Message.Message): void { // Do not perform custom operations when an input is selected if (document.activeElement.tagName.toLowerCase() === 'input') { return; } switch (message.type) { - case Message.ServerMessageType.Undo: { + case Message.MessageType.Undo: { store.undo(); break; } - case Message.ServerMessageType.Redo: { + case Message.MessageType.Redo: { store.redo(); break; } - case Message.ServerMessageType.Cut: { + case Message.MessageType.Cut: { /*if (app.getActiveView() === Types.AlvaView.Pages) { // TODO: implement this // store.cutSelectedPage(); @@ -38,11 +38,11 @@ export function createEditMessageHandler({ } break; } - case Message.ServerMessageType.CutElement: { + case Message.MessageType.CutElement: { store.executeElementCutById(message.payload); break; } - case Message.ServerMessageType.Delete: { + case Message.MessageType.Delete: { if ( app.getActiveView() === Types.AlvaView.PageDetail && store.getProject().getFocusedItemType() === Types.FocusedItemType.Page @@ -58,11 +58,11 @@ export function createEditMessageHandler({ } break; } - case Message.ServerMessageType.DeleteElement: { + case Message.MessageType.DeleteElement: { store.executeElementRemoveById(message.payload); break; } - case Message.ServerMessageType.Copy: { + case Message.MessageType.Copy: { /*if (app.getActiveView() === Types.AlvaView.Pages) { // TODO: implement this // store.copySelectedPage(); @@ -72,11 +72,11 @@ export function createEditMessageHandler({ } break; } - case Message.ServerMessageType.CopyElement: { + case Message.MessageType.CopyElement: { store.copyElementById(message.payload); break; } - case Message.ServerMessageType.Paste: { + case Message.MessageType.Paste: { /*if (app.getActiveView() === Types.AlvaView.Pages) { // TODO: implement this // store.pasteAfterSelectedPage(); @@ -86,21 +86,21 @@ export function createEditMessageHandler({ } break; } - case Message.ServerMessageType.PasteElementBelow: { + case Message.MessageType.PasteElementBelow: { store.executeElementPasteAfterById(message.payload); break; } - case Message.ServerMessageType.PasteElementInside: { + case Message.MessageType.PasteElementInside: { store.executeElementPasteInsideById(message.payload); break; } - case Message.ServerMessageType.Duplicate: { + case Message.MessageType.Duplicate: { if (app.getActiveView() === Types.AlvaView.PageDetail) { store.executeElementDuplicateSelected(); } break; } - case Message.ServerMessageType.DuplicateElement: { + case Message.MessageType.DuplicateElement: { if (app.getActiveView() === Types.AlvaView.PageDetail) { store.executeElementDuplicateById(message.payload); } diff --git a/src/renderer/create-init-message-handler.ts b/src/renderer/create-init-message-handler.ts index 1718162fc..19057ffbe 100644 --- a/src/renderer/create-init-message-handler.ts +++ b/src/renderer/create-init-message-handler.ts @@ -6,7 +6,7 @@ import { ViewStore } from '../store'; import * as Types from '../types'; import * as uuid from 'uuid'; -export type InitMessageHandler = (message: Message.ServerMessage) => void; +export type InitMessageHandler = (message: Message.Message) => void; export function createInitMessageHandler({ app, @@ -17,9 +17,9 @@ export function createInitMessageHandler({ history: Model.EditHistory; store: ViewStore; }): InitMessageHandler { - return function initMessageHandler(message: Message.ServerMessage): void { + return function initMessageHandler(message: Message.Message): void { switch (message.type) { - case Message.ServerMessageType.StartApp: { + case Message.MessageType.StartApp: { store.setServerPort(Number(message.payload.port)); try { @@ -36,7 +36,7 @@ export function createInitMessageHandler({ break; } - case Message.ServerMessageType.OpenFileResponse: { + case Message.MessageType.OpenFileResponse: { history.clear(); try { @@ -56,7 +56,7 @@ export function createInitMessageHandler({ .getPatternLibraries() .map(lib => lib.getId()) }, - type: Message.ServerMessageType.CheckLibraryRequest + type: Message.MessageType.CheckLibraryRequest }); } catch (err) { Sender.send({ @@ -67,13 +67,13 @@ export function createInitMessageHandler({ )}".\n Parsing the project failed with: ${err.message}`, stack: err.stack }, - type: Message.ServerMessageType.ShowError + type: Message.MessageType.ShowError }); } break; } - case Message.ServerMessageType.CreateNewFileResponse: { + case Message.MessageType.CreateNewFileResponse: { history.clear(); const newProject = Model.Project.from(message.payload.contents); store.setProject(newProject); @@ -81,7 +81,7 @@ export function createInitMessageHandler({ store.commit(); break; } - case Message.ServerMessageType.Log: { + case Message.MessageType.Log: { if (Array.isArray(message.payload)) { console.log(...message.payload); } else { @@ -89,7 +89,7 @@ export function createInitMessageHandler({ } break; } - case Message.ServerMessageType.KeyboardChange: { + case Message.MessageType.KeyboardChange: { store.setMetaDown(message.payload.metaDown); } } diff --git a/src/renderer/create-project-message-handler.ts b/src/renderer/create-project-message-handler.ts index 110eaafa9..30ce8406a 100644 --- a/src/renderer/create-project-message-handler.ts +++ b/src/renderer/create-project-message-handler.ts @@ -5,7 +5,7 @@ import { ViewStore } from '../store'; import * as Types from '../types'; import * as uuid from 'uuid'; -export type ProjectMessageHandler = (message: Message.ServerMessage) => void; +export type ProjectMessageHandler = (message: Message.Message) => void; export function createProjectMessageHandler({ app, @@ -17,7 +17,7 @@ export function createProjectMessageHandler({ store: ViewStore; }): ProjectMessageHandler { // tslint:disable-next-line:cyclomatic-complexity - return async function projectMessagehandler(message: Message.ServerMessage): Promise { + return async function projectMessagehandler(message: Message.Message): Promise { const project = store.getProject(); if (!project) { @@ -25,7 +25,7 @@ export function createProjectMessageHandler({ } switch (message.type) { - case Message.ServerMessageType.CreateNewPage: { + case Message.MessageType.CreateNewPage: { const page = store.executePageAddNew(); if (!page) { @@ -37,7 +37,7 @@ export function createProjectMessageHandler({ break; } - case Message.ServerMessageType.ConnectPatternLibraryResponse: { + case Message.MessageType.ConnectPatternLibraryResponse: { const library = Model.PatternLibrary.import(message.payload.analysis); project.addPatternLibrary(library); store.getApp().setRightSidebarTab(Types.RightSidebarTab.ProjectSettings); @@ -49,12 +49,12 @@ export function createProjectMessageHandler({ id: library.getId(), path: message.payload.path }, - type: Message.ServerMessageType.ConnectedPatternLibraryNotification + type: Message.MessageType.ConnectedPatternLibraryNotification }); break; } - case Message.ServerMessageType.UpdatePatternLibraryResponse: { + case Message.MessageType.UpdatePatternLibraryResponse: { const previousLibrary = project.getPatternLibraryById( message.payload.previousLibraryId ); @@ -74,12 +74,12 @@ export function createProjectMessageHandler({ id: library.getId(), path: message.payload.path }, - type: Message.ServerMessageType.ConnectedPatternLibraryNotification + type: Message.MessageType.ConnectedPatternLibraryNotification }); break; } - case Message.ServerMessageType.CheckLibraryResponse: { + case Message.MessageType.CheckLibraryResponse: { message.payload .map(check => ({ library: project.getPatternLibraryById(check.id), check })) .forEach(({ library, check }) => { @@ -94,38 +94,42 @@ export function createProjectMessageHandler({ }); break; } - case Message.ServerMessageType.SelectElement: { + case Message.MessageType.SelectElement: { const element = store.getElementById(message.payload.id); if (element) { store.setSelectedElement(element); } break; } - case Message.ServerMessageType.UnselectElement: { + case Message.MessageType.UnselectElement: { store.getProject().unsetSelectedElement(); break; } - case Message.ServerMessageType.HighlightElement: { + case Message.MessageType.HighlightElement: { const element = store.getElementById(message.payload.id); if (element) { store.setHighlightedElement(element, { flat: !store.getMetaDown() }); } break; } - case Message.ServerMessageType.ActivatePage: { + case Message.MessageType.ActivatePage: { const page = project.getPageById(message.payload.id); if (page) { store.getProject().setActivePage(page); } break; } - case Message.ServerMessageType.SetPane: { + case Message.MessageType.SetPane: { app.setPane(message.payload.pane, message.payload.visible); break; } - case Message.ServerMessageType.UnHighlightElement: { + case Message.MessageType.UnHighlightElement: { store.unsetDraggedElement(); store.unsetHighlightedElementContent(); + break; + } + case Message.MessageType.UserStoreChange: { + project.getUserStore().sync(message); } } }; diff --git a/src/renderer/create-request-message-handler.ts b/src/renderer/create-request-message-handler.ts index 1eba8df6e..2b8736a87 100644 --- a/src/renderer/create-request-message-handler.ts +++ b/src/renderer/create-request-message-handler.ts @@ -4,7 +4,7 @@ import * as Sender from '../sender/client'; import { ViewStore } from '../store'; import * as Types from '../types'; -export type RequestMessageHandler = (message: Message.ServerMessage) => void; +export type RequestMessageHandler = (message: Message.Message) => void; export function createRequestMessageHandler({ app, @@ -15,15 +15,15 @@ export function createRequestMessageHandler({ history: Model.EditHistory; store: ViewStore; }): RequestMessageHandler { - return function requestMessageHandler(message: Message.ServerMessage): void { + return function requestMessageHandler(message: Message.Message): void { switch (message.type) { - case Message.ServerMessageType.ProjectRequest: { + case Message.MessageType.ProjectRequest: { const data = store.getProject(); if (!data) { return Sender.send({ id: message.id, - type: Message.ServerMessageType.ProjectResponse, + type: Message.MessageType.ProjectResponse, payload: { data: undefined, status: Types.ProjectStatus.None @@ -33,7 +33,7 @@ export function createRequestMessageHandler({ return Sender.send({ id: message.id, - type: Message.ServerMessageType.ProjectResponse, + type: Message.MessageType.ProjectResponse, payload: { data: data.toJSON(), status: Types.ProjectStatus.Ok diff --git a/src/renderer/create-server-message-handler.ts b/src/renderer/create-server-message-handler.ts index 727ca16f0..047cee589 100644 --- a/src/renderer/create-server-message-handler.ts +++ b/src/renderer/create-server-message-handler.ts @@ -12,7 +12,7 @@ export interface ServerMessageHandlerContext { store: ViewStore; } -export type ServerMessageHandler = (message: Message.ServerMessage) => void; +export type ServerMessageHandler = (message: Message.Message) => void; export function createServerMessageHandler({ app, @@ -24,7 +24,7 @@ export function createServerMessageHandler({ const handleProjectMessages = createProjectMessageHandler({ app, history, store }); const handleRequestMessages = createRequestMessageHandler({ app, history, store }); - return function serverMessageHandler(message: Message.ServerMessage): void { + return function serverMessageHandler(message: Message.Message): void { handleInitMessages(message); handleProjectMessages(message); handleRequestMessages(message); diff --git a/src/renderer/renderer.tsx b/src/renderer/renderer.tsx index 12d75c2df..2ce65749e 100644 --- a/src/renderer/renderer.tsx +++ b/src/renderer/renderer.tsx @@ -2,7 +2,7 @@ import { App } from '../container/app'; import { createChangeNotifiers } from './create-change-notifiers'; import { createServerMessageHandler } from './create-server-message-handler'; import * as Sender from '../sender/client'; -import { ServerMessageType } from '../message'; +import { MessageType } from '../message'; import * as MobxReact from 'mobx-react'; import * as Model from '../model'; import * as React from 'react'; @@ -16,7 +16,7 @@ export function startRenderer(): void { Sender.send({ id: uuid.v4(), - type: ServerMessageType.AppLoaded, + type: MessageType.AppLoaded, payload: undefined }); diff --git a/src/sender/client.ts b/src/sender/client.ts index 5c72a8141..e17948177 100644 --- a/src/sender/client.ts +++ b/src/sender/client.ts @@ -1,9 +1,9 @@ import * as Electron from 'electron'; -import { isServerMessage } from './is-server-message'; +import { isMessage } from './is-message'; import * as Message from '../message'; -export function send(message: Message.ServerMessage): void { - if (!isServerMessage(message)) { +export function send(message: Message.Message): void { + if (!isMessage(message)) { console.warn(`Client tried to send invalid message: ${JSON.stringify(message)}`); return; } @@ -11,9 +11,9 @@ export function send(message: Message.ServerMessage): void { Electron.ipcRenderer.send('message', message); } -export function receive(handler: (message: Message.ServerMessage) => void): void { +export function receive(handler: (message: Message.Message) => void): void { Electron.ipcRenderer.on('message', (e: Electron.Event, message) => { - if (!isServerMessage(message)) { + if (!isMessage(message)) { return; } handler(message); diff --git a/src/sender/is-server-message.ts b/src/sender/is-message.ts similarity index 59% rename from src/sender/is-server-message.ts rename to src/sender/is-message.ts index 78d1d270a..0e6e2eeb7 100644 --- a/src/sender/is-server-message.ts +++ b/src/sender/is-message.ts @@ -1,9 +1,9 @@ -import { ServerMessage, ServerMessageType } from '../message'; +import { Message, MessageType } from '../message'; -const TYPES = Object.values(ServerMessageType); +const TYPES = Object.values(MessageType); // tslint:disable-next-line:no-any -export function isServerMessage(input: any): input is ServerMessage { +export function isMessage(input: any): input is Message { if (typeof input !== 'object') { return false; } diff --git a/src/sender/is-preview-message.ts b/src/sender/is-preview-message.ts deleted file mode 100644 index 2f220d8b0..000000000 --- a/src/sender/is-preview-message.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { PreviewMessage, PreviewMessageType } from '../message'; - -const TYPES = Object.values(PreviewMessageType); - -// tslint:disable-next-line:no-any -export function isPreviewMessage(input: any): input is PreviewMessage { - if (typeof input !== 'object') { - return false; - } - - const type = input.type; - - if (typeof type !== 'string' || typeof input.id !== 'string') { - return false; - } - - if (!TYPES.includes(type)) { - return false; - } - - return true; -} diff --git a/src/sender/preview.ts b/src/sender/preview.ts index 2ba533da8..d6b34a7ad 100644 --- a/src/sender/preview.ts +++ b/src/sender/preview.ts @@ -1,7 +1,6 @@ import * as AlvaUtil from '../alva-util'; import * as Message from '../message'; -import { isServerMessage } from './is-server-message'; -import { isPreviewMessage } from './is-preview-message'; +import { isMessage } from './is-message'; export interface SenderInit { endpoint: string; @@ -16,8 +15,8 @@ export class Sender { this.connection = new WebSocket(this.endpoint); } - public async send(message: Message.PreviewMessage): Promise { - if (!isPreviewMessage) { + public async send(message: Message.Message): Promise { + if (!isMessage(message)) { return; } @@ -27,7 +26,7 @@ export class Sender { } // tslint:disable-next-line:no-any - public async receive(handler: (message: Message.ServerMessage) => void): Promise { + public async receive(handler: (message: Message.Message) => void): Promise { await onReady(this.connection); this.connection.addEventListener('message', e => { @@ -38,7 +37,7 @@ export class Sender { return; } - if (!isServerMessage(parseResult.result)) { + if (!isMessage(parseResult.result)) { return; } diff --git a/src/sender/server.ts b/src/sender/server.ts index 4494dc2ea..f245c6414 100644 --- a/src/sender/server.ts +++ b/src/sender/server.ts @@ -1,24 +1,24 @@ import { BrowserWindow, ipcMain } from 'electron'; -import { isServerMessage } from './is-server-message'; +import { isMessage } from './is-message'; import * as uuid from 'uuid'; import * as Message from '../message'; -export type ServerHandler = (message: Message.ServerMessage) => void; +export type ServerHandler = (message: Message.Message) => void; // tslint:disable-next-line:no-any export type IpcHandler = (e: any, message: any) => void; export class Sender { - private received: Map = new Map(); + private received: Map = new Map(); private sendHandlers: ServerHandler[] = []; // tslint:disable-next-line:no-any private receiveIpcHandlers: IpcHandler[] = []; - public async receive(handler: (message: Message.ServerMessage) => void): Promise { + public async receive(handler: (message: Message.Message) => void): Promise { // tslint:disable-next-line:no-any const receiveHandler = (e: any, message: any) => { - if (!isServerMessage(message)) { + if (!isMessage(message)) { return; } this.received.set(message.type, message); @@ -36,7 +36,7 @@ export class Sender { return new Promise((resolve, reject) => { // tslint:disable-next-line:no-any function messageHandler(e: any, responseMessage: any): void { - if (!isServerMessage(message)) { + if (!isMessage(message)) { return; } @@ -57,8 +57,8 @@ export class Sender { }); } - public async send(message: Message.ServerMessage): Promise { - if (!isServerMessage(message)) { + public async send(message: Message.Message): Promise { + if (!isMessage(message)) { console.warn(`Server tried to send invalid message: ${JSON.stringify(message)}`); return; } @@ -80,14 +80,14 @@ export class Sender { this.sendHandlers.push(handler); } - public last(type: T['type']): T | undefined { + public last(type: T['type']): T | undefined { return this.received.get(type) as T; } // tslint:disable-next-line:no-any public async log(...args: any[]): Promise { this.send({ - type: Message.ServerMessageType.Log, + type: Message.MessageType.Log, id: uuid.v4(), payload: args }); diff --git a/src/server/create-libraries-route.ts b/src/server/create-libraries-route.ts index c18df4ba5..5291fbea7 100644 --- a/src/server/create-libraries-route.ts +++ b/src/server/create-libraries-route.ts @@ -1,5 +1,5 @@ import * as Express from 'express'; -import { ProjectRequestResponsePair, ServerMessageType } from '../message'; +import { ProjectRequestResponsePair, MessageType } from '../message'; import { Sender } from '../sender/server'; import * as Model from '../model'; import * as Path from 'path'; @@ -18,10 +18,10 @@ export function createLibrariesRoute(options: LibrariesRouteOptions): Express.Re const projectResponse = await options.sender.request( { id: uuid.v4(), - type: ServerMessageType.ProjectRequest, + type: MessageType.ProjectRequest, payload: undefined }, - ServerMessageType.ProjectResponse + MessageType.ProjectResponse ); if (projectResponse.payload.status === Types.ProjectStatus.None) { diff --git a/src/server/create-preview-route.ts b/src/server/create-preview-route.ts index 1c81829c8..f8ff7265f 100644 --- a/src/server/create-preview-route.ts +++ b/src/server/create-preview-route.ts @@ -1,6 +1,6 @@ import * as Express from 'express'; import * as PreviewDocument from '../preview-document'; -import { ProjectRequestResponsePair, ServerMessageType } from '../message'; +import { ProjectRequestResponsePair, MessageType } from '../message'; import { Sender } from '../sender/server'; import * as Model from '../model'; import * as Types from '../types'; @@ -17,10 +17,10 @@ export function createPreviewRoute(options: PreviewRouteOptions): Express.Reques const projectResponse = await options.sender.request( { id: uuid.v4(), - type: ServerMessageType.ProjectRequest, + type: MessageType.ProjectRequest, payload: undefined }, - ServerMessageType.ProjectResponse + MessageType.ProjectResponse ); if (projectResponse.payload.status === Types.ProjectStatus.None) { diff --git a/src/server/create-screenshot-route.ts b/src/server/create-screenshot-route.ts index 1e8e14c8c..a336d21ad 100644 --- a/src/server/create-screenshot-route.ts +++ b/src/server/create-screenshot-route.ts @@ -1,7 +1,7 @@ import * as Electron from 'electron'; import * as Express from 'express'; import * as Path from 'path'; -import { ProjectRequestResponsePair, ServerMessageType } from '../message'; +import { ProjectRequestResponsePair, MessageType } from '../message'; import { Sender } from '../sender/server'; import * as Model from '../model'; import { sizedBrowser } from './sized-browser'; @@ -38,10 +38,10 @@ export function createScreenshotRoute(options: ScreenshotRouteOptions): Express. const projectResponse = await options.sender.request( { id: uuid.v4(), - type: ServerMessageType.ProjectRequest, + type: MessageType.ProjectRequest, payload: undefined }, - ServerMessageType.ProjectResponse + MessageType.ProjectResponse ); if (projectResponse.payload.status === Types.ProjectStatus.None) { diff --git a/src/server/create-server-message-handler.ts b/src/server/create-server-message-handler.ts index 638562e43..af8c9d5cf 100644 --- a/src/server/create-server-message-handler.ts +++ b/src/server/create-server-message-handler.ts @@ -1,7 +1,7 @@ import * as Message from '../message'; import * as WS from 'ws'; -export type ServerMessageHandler = (message: Message.ServerMessage) => void; +export type ServerMessageHandler = (message: Message.Message) => void; export interface ServerMessageHandlerContext { webSocketServer: WS.Server; @@ -10,7 +10,7 @@ export interface ServerMessageHandlerContext { export function createServerMessageHandler( context: ServerMessageHandlerContext ): ServerMessageHandler { - return function serverMessageHandler(message: Message.ServerMessage): void { + return function serverMessageHandler(message: Message.Message): void { context.webSocketServer.clients.forEach(client => { if (client.readyState === WS.OPEN) { client.send(JSON.stringify(message)); diff --git a/src/server/create-sketch-route.ts b/src/server/create-sketch-route.ts index ce9c09990..8db1d3641 100644 --- a/src/server/create-sketch-route.ts +++ b/src/server/create-sketch-route.ts @@ -1,7 +1,7 @@ import * as Express from 'express'; import * as Path from 'path'; import * as PreviewDocument from '../preview-document'; -import { ProjectRequestResponsePair, ServerMessageType } from '../message'; +import { ProjectRequestResponsePair, MessageType } from '../message'; import { Sender } from '../sender/server'; import { sizedBrowser } from './sized-browser'; import * as Model from '../model'; @@ -35,10 +35,10 @@ export function createSketchRoute(options: ScreenshotRouteOptions): Express.Requ const projectResponse = await options.sender.request( { id: uuid.v4(), - type: ServerMessageType.ProjectRequest, + type: MessageType.ProjectRequest, payload: undefined }, - ServerMessageType.ProjectResponse + MessageType.ProjectResponse ); if (projectResponse.payload.status === Types.ProjectStatus.None) { diff --git a/src/server/create-static-route.ts b/src/server/create-static-route.ts index 6a6dff1cf..52419b3a0 100644 --- a/src/server/create-static-route.ts +++ b/src/server/create-static-route.ts @@ -1,7 +1,7 @@ import * as Express from 'express'; import * as Path from 'path'; import * as PreviewDocument from '../preview-document'; -import { ProjectRequestResponsePair, ServerMessageType } from '../message'; +import { ProjectRequestResponsePair, MessageType } from '../message'; import { Sender } from '../sender/server'; import * as Model from '../model'; import * as Types from '../types'; @@ -18,10 +18,10 @@ export function createStaticRoute(options: StaticRouteOptions): Express.RequestH const projectResponse = await options.sender.request( { id: uuid.v4(), - type: ServerMessageType.ProjectRequest, + type: MessageType.ProjectRequest, payload: undefined }, - ServerMessageType.ProjectResponse + MessageType.ProjectResponse ); if (projectResponse.payload.status === Types.ProjectStatus.None) { diff --git a/src/server/server.ts b/src/server/server.ts index 1ab66b514..42e1b4176 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -9,7 +9,9 @@ import { createServerMessageHandler } from './create-server-message-handler'; import { EventEmitter } from 'events'; import * as express from 'express'; import * as Http from 'http'; +import * as Message from '../message'; import * as WS from 'ws'; +import { isMessage } from '../sender/is-message'; import { Sender } from '../sender/server'; export interface ServerOptions { @@ -74,6 +76,25 @@ export class AlvaServer extends EventEmitter { public stop(): Promise { return new Promise(resolve => this.server.close(resolve)); } + + public emit(name: 'client-message' | 'message', message: Message.Message): boolean { + if (!isMessage(message)) { + return false; + } + + return super.emit(name, message); + } + + public on(name: 'client-message' | 'message', handler: (e: Message.Message) => void): this { + // tslint:disable-next-line:no-any + super.on(name, (message: any) => { + if (!isMessage(message)) { + return; + } + handler(message); + }); + return this; + } } export function createServer(options: ServerOptions): AlvaServer { diff --git a/src/store/view-store.ts b/src/store/view-store.ts index e158f4575..85e01a98f 100644 --- a/src/store/view-store.ts +++ b/src/store/view-store.ts @@ -1,6 +1,6 @@ import * as Sender from '../sender/client'; import { debounce, isEqual } from 'lodash'; -import { ServerMessageType } from '../message'; +import { MessageType } from '../message'; import * as Mobx from 'mobx'; import * as Model from '../model'; import * as Types from '../types'; @@ -99,7 +99,7 @@ export class ViewStore { @Mobx.action public connectPatternLibrary(library?: Model.PatternLibrary): void { Sender.send({ - type: ServerMessageType.ConnectPatternLibraryRequest, + type: MessageType.ConnectPatternLibraryRequest, id: uuid.v4(), payload: { library: library ? library.getId() : undefined @@ -567,6 +567,14 @@ export class ViewStore { return this.project.getElements().some(e => e.getDragged()); } + public getElementActions(): Model.ElementAction[] { + if (!this.project) { + return []; + } + + return this.project.getElementActions(); + } + public getElementContents(): Model.ElementContent[] { if (!this.project) { return []; @@ -839,7 +847,7 @@ export class ViewStore { public requestContextMenu(payload: Types.ContextMenuRequestPayload): void { Sender.send({ id: uuid.v4(), - type: ServerMessageType.ContextMenuRequest, + type: MessageType.ContextMenuRequest, payload }); } @@ -849,7 +857,7 @@ export class ViewStore { const savedProjects = this.getSavedProjects(); const savedProject = savedProjects[savedProjects.length - 1]; - if (savedProject && Model.Project.isEqual(savedProject, this.project.toDisk())) { + if (savedProject && Model.Project.equals(savedProject, this.project.toDisk())) { return; } @@ -865,7 +873,7 @@ export class ViewStore { Sender.send({ id: uuid.v4(), payload, - type: ServerMessageType.Save + type: MessageType.Save }); } } @@ -1048,7 +1056,7 @@ export class ViewStore { public updatePatternLibrary(library: Model.PatternLibrary): void { Sender.send({ - type: ServerMessageType.UpdatePatternLibraryRequest, + type: MessageType.UpdatePatternLibraryRequest, payload: { id: library.getId() }, diff --git a/src/types/user-store.ts b/src/types/user-store.ts index 4642fbf12..ca4b08433 100644 --- a/src/types/user-store.ts +++ b/src/types/user-store.ts @@ -32,3 +32,5 @@ export interface SerializedUserStore { id: string; properties: SerializedUserStoreProperty[]; } + +export type UserStoreActionPayload = string;