From 3f6164a7e86d16672b943b43ce78109863b28eca Mon Sep 17 00:00:00 2001 From: Mario Nebl Date: Mon, 21 May 2018 11:32:24 +0200 Subject: [PATCH] fix: ensure library updates work --- src/analyzer/directory.ts | 38 ------------------- .../typescript-react-analyzer.ts | 17 ++++++++- src/container/app.tsx | 8 +++- src/electron/main.ts | 16 ++++---- src/electron/menu.ts | 30 ++++++++++++++- src/message/message.ts | 8 +++- src/model/pattern-library/builtins/box.ts | 18 +++++++-- src/model/pattern-library/builtins/page.ts | 18 +++++++-- .../pattern-library/builtins/placeholder.ts | 8 ++-- src/model/pattern-library/builtins/text.ts | 8 ++-- src/model/pattern-library/pattern-library.ts | 14 +++++++ src/model/pattern/pattern.ts | 26 ++++++------- src/model/project.ts | 18 +++++---- src/preview/preview.tsx | 5 +++ 14 files changed, 144 insertions(+), 88 deletions(-) delete mode 100644 src/analyzer/directory.ts diff --git a/src/analyzer/directory.ts b/src/analyzer/directory.ts deleted file mode 100644 index 77354a764..000000000 --- a/src/analyzer/directory.ts +++ /dev/null @@ -1,38 +0,0 @@ -import * as Fs from 'fs'; -import * as Path from 'path'; - -export class Directory { - private readonly path: string; - - public constructor(path: string) { - this.path = path; - } - - public *getDirectories(): IterableIterator { - for (const childName of Fs.readdirSync(this.path)) { - const childPath = Path.join(this.path, childName); - - if (Fs.lstatSync(childPath).isDirectory()) { - yield new Directory(childPath); - } - } - } - - public *getFiles(): IterableIterator { - for (const childName of Fs.readdirSync(this.path)) { - const childPath = Path.join(this.path, childName); - - if (Fs.lstatSync(childPath).isFile()) { - yield childPath; - } - } - } - - public getName(): string { - return Path.basename(this.path); - } - - public getPath(): string { - return this.path; - } -} diff --git a/src/analyzer/typescript-react-analyzer/typescript-react-analyzer.ts b/src/analyzer/typescript-react-analyzer/typescript-react-analyzer.ts index 3040bb5fc..5cb8963ad 100644 --- a/src/analyzer/typescript-react-analyzer/typescript-react-analyzer.ts +++ b/src/analyzer/typescript-react-analyzer/typescript-react-analyzer.ts @@ -1,6 +1,7 @@ import { createCompiler } from '../../compiler'; import * as Fs from 'fs'; import * as Path from 'path'; +import { PatternLibrary } from '../../model/pattern-library'; import * as PropertyAnalyzer from './property-analyzer'; import * as ReactUtils from '../typescript/react-utils'; import * as SlotAnalyzer from './slot-analzyer'; @@ -29,13 +30,25 @@ interface AnalyzeContext { type PatternAnalyzer = (candidate: PatternCandidate, predicate: PatternAnalyzerPredicate) => Types.PatternAnalysis[]; type PatternAnalyzerPredicate = (ex: TypeScript.TypescriptExport, ctx: AnalyzeContext) => Types.PatternAnalysis | undefined; -export async function analyze(path: string): Promise { +export async function analyze(path: string, previousLibrary: Types.SerializedPatternLibrary): Promise { + const library = PatternLibrary.from(previousLibrary); + const patternCandidates = await findPatternCandidates(path); const sourcePaths = patternCandidates.map(p => p.sourcePath); const analyzePattern = getPatternAnalyzer(ts.createProgram(sourcePaths, {})); const patterns = patternCandidates - .reduce((acc, candidate) => [...acc, ...analyzePattern(candidate, analyzePatternExport)], []); + .reduce((acc, candidate) => [...acc, ...analyzePattern(candidate, analyzePatternExport)], []) + .map(item => { + const previousPattern = library.getPatternByContextId(item.pattern.contextId); + + // Reuse pattern id if it has been in the library previously + if (previousPattern) { + item.pattern.id = previousPattern.getId(); + } + + return item; + }); const compilerPatterns = patterns.map(({path: patternPath, pattern}) => ({ id: pattern.id, diff --git a/src/container/app.tsx b/src/container/app.tsx index 73a4e4eb2..555afd152 100644 --- a/src/container/app.tsx +++ b/src/container/app.tsx @@ -155,10 +155,16 @@ export class App extends React.Component { {store.getPatternLibraryState() === Types.PatternLibraryState.Pristine && ( { + const project = store.getProject(); + + if (!project) { + return; + } + Sender.send({ type: ServerMessageType.ConnectPatternLibraryRequest, id: uuid.v4(), - payload: undefined + payload: project.getPatternLibrary().toJSON() }); }} /> diff --git a/src/electron/main.ts b/src/electron/main.ts index 36869f016..2de72dba2 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -2,7 +2,7 @@ import * as Analyzer from '../analyzer'; import { checkForUpdates } from './auto-updater'; import { colors } from '../components'; import { createCompiler } from '../compiler/create-compiler'; -import { app, BrowserWindow, dialog, ipcMain, screen } from 'electron'; +import { app, BrowserWindow, dialog, screen } from 'electron'; import * as electronIsDev from 'electron-is-dev'; import * as Fs from 'fs'; import * as getPort from 'get-port'; @@ -89,6 +89,12 @@ async function createWindow(): Promise { // access to system / fs // tslint:disable-next-line:cyclomatic-complexity switch (message.type) { + case ServerMessageType.CheckForUpdatesRequest: { + if (win) { + checkForUpdates(win, true); + } + break; + } case ServerMessageType.AppLoaded: { // Load last known file automatically in development if (electronIsDev && projectPath) { @@ -269,7 +275,7 @@ async function createWindow(): Promise { const path = Array.isArray(paths) ? paths[0] : undefined; if (path) { - const analysis = await Analyzer.analyze(path); + const analysis = await Analyzer.analyze(path, message.payload); Sender.send({ type: ServerMessageType.ConnectPatternLibraryResponse, @@ -370,12 +376,6 @@ app.on('activate', async () => { } }); -ipcMain.on('request-check-for-updates', () => { - if (win) { - checkForUpdates(win, true); - } -}); - process.on('unhandledRejection', reason => { console.error(reason); }); diff --git a/src/electron/menu.ts b/src/electron/menu.ts index 5b8c3176f..b0f48d758 100644 --- a/src/electron/menu.ts +++ b/src/electron/menu.ts @@ -1,5 +1,5 @@ import * as Sender from '../message/client'; -import { BrowserWindow, ipcRenderer, MenuItem, MenuItemConstructorOptions, remote } from 'electron'; +import { BrowserWindow, MenuItem, MenuItemConstructorOptions, remote } from 'electron'; import { HtmlExporter } from '../export/html-exporter'; import { ServerMessageType } from '../message'; import { Page, Project } from '../model'; @@ -320,6 +320,28 @@ export function createMenu(): void { } ] }, + { + label: '&Library', + submenu: [ + { + label: '&Connect', + accelerator: 'CmdOrCtrl+Shift+C', + click: () => { + const project = store.getProject(); + + if (!project) { + return; + } + + Sender.send({ + type: ServerMessageType.ConnectPatternLibraryRequest, + id: uuid.v4(), + payload: project.getPatternLibrary().toJSON() + }); + } + } + ] + }, { label: '&View', submenu: [ @@ -410,7 +432,11 @@ export function createMenu(): void { { label: 'Check for Updates', click: () => { - ipcRenderer.send('request-check-for-updates'); + Sender.send({ + id: uuid.v4(), + payload: undefined, + type: ServerMessageType.CheckForUpdatesRequest + }); } }, { diff --git a/src/message/message.ts b/src/message/message.ts index e30f46a9d..78384d062 100644 --- a/src/message/message.ts +++ b/src/message/message.ts @@ -16,6 +16,7 @@ export enum ServerMessageType { AssetReadRequest = 'asset-read-request', AssetReadResponse = 'asset-read-response', BundleChange = 'bundle-change', + CheckForUpdatesRequest = 'check-for-updates-request', ConnectPatternLibraryRequest = 'connect-pattern-library-request', ConnectPatternLibraryResponse = 'connect-pattern-library-response', ContentRequest = 'content-request', @@ -66,6 +67,7 @@ export type ServerMessage = | AppLoaded | AssetReadRequest | AssetReadResponse + | CheckForUpdatesRequest | ConnectPatternLibraryRequest | ConnectPatternLibraryResponse | ContentRequest @@ -106,8 +108,10 @@ export type ServerMessage = export type AppLoaded = EmptyEnvelope; export type AssetReadRequest = EmptyEnvelope; export type AssetReadResponse = Envelope; -export type ConnectPatternLibraryRequest = EmptyEnvelope< - ServerMessageType.ConnectPatternLibraryRequest +export type CheckForUpdatesRequest = EmptyEnvelope; +export type ConnectPatternLibraryRequest = Envelope< + ServerMessageType.ConnectPatternLibraryRequest, + Types.SerializedPatternLibrary >; export type ConnectPatternLibraryResponse = Envelope< ServerMessageType.ConnectPatternLibraryResponse, diff --git a/src/model/pattern-library/builtins/box.ts b/src/model/pattern-library/builtins/box.ts index ac170a4e8..5efa72b4f 100644 --- a/src/model/pattern-library/builtins/box.ts +++ b/src/model/pattern-library/builtins/box.ts @@ -1,4 +1,4 @@ -import { Pattern, SyntheticPatternType } from '../../pattern'; +import { Pattern, PatternSlot, SyntheticPatternType } from '../../pattern'; import { PatternBooleanProperty, PatternEnumProperty, @@ -6,6 +6,7 @@ import { PatternNumberProperty, PatternStringProperty } from '../../pattern-property'; +import * as Types from '../../types'; import * as uuid from 'uuid'; export const Box = context => { @@ -152,10 +153,19 @@ export const Box = context => { return { boxPattern: new Pattern( { + contextId: 'synthetic:box', + exportName: 'default', name: 'Box', - path: '', - type: SyntheticPatternType.SyntheticBox, - propertyIds: boxProperties.map(p => p.getId()) + propertyIds: boxProperties.map(p => p.getId()), + slots: [ + new PatternSlot({ + displayName: 'Children', + propertyName: 'children', + id: uuid.v4(), + type: Types.SlotType.Children + }) + ], + type: SyntheticPatternType.SyntheticBox }, context ), diff --git a/src/model/pattern-library/builtins/page.ts b/src/model/pattern-library/builtins/page.ts index bf3364f12..a1ddd223e 100644 --- a/src/model/pattern-library/builtins/page.ts +++ b/src/model/pattern-library/builtins/page.ts @@ -1,10 +1,11 @@ import { languages } from './languages'; -import { Pattern, SyntheticPatternType } from '../../pattern'; +import { Pattern, PatternSlot, SyntheticPatternType } from '../../pattern'; import { PatternBooleanProperty, PatternEnumProperty, PatternEnumPropertyOption } from '../../pattern-property'; +import * as Types from '../../types'; import * as uuid from 'uuid'; export const Page = context => { @@ -39,10 +40,19 @@ export const Page = context => { const pagePattern = new Pattern( { + contextId: 'synthetic:page', + exportName: 'default', name: 'Page', - path: '', - type: SyntheticPatternType.SyntheticPage, - propertyIds: pageProperties.map(p => p.getId()) + propertyIds: pageProperties.map(p => p.getId()), + slots: [ + new PatternSlot({ + displayName: 'Children', + propertyName: 'children', + id: uuid.v4(), + type: Types.SlotType.Children + }) + ], + type: SyntheticPatternType.SyntheticPage }, context ); diff --git a/src/model/pattern-library/builtins/placeholder.ts b/src/model/pattern-library/builtins/placeholder.ts index 21505e6cf..bbca1f03d 100644 --- a/src/model/pattern-library/builtins/placeholder.ts +++ b/src/model/pattern-library/builtins/placeholder.ts @@ -11,10 +11,12 @@ export const Placeholder = context => { const placeholderPattern = new Pattern( { + contextId: 'synthetic:placeholder', + exportName: 'default', name: 'Placeholder', - path: '', - type: SyntheticPatternType.SyntheticPlaceholder, - propertyIds: placeholderProperties.map(p => p.getId()) + propertyIds: placeholderProperties.map(p => p.getId()), + slots: [], + type: SyntheticPatternType.SyntheticPlaceholder }, context ); diff --git a/src/model/pattern-library/builtins/text.ts b/src/model/pattern-library/builtins/text.ts index 6f5c31d5f..50d6a388c 100644 --- a/src/model/pattern-library/builtins/text.ts +++ b/src/model/pattern-library/builtins/text.ts @@ -13,10 +13,12 @@ export const Text = context => { textProperties, textPattern: new Pattern( { + contextId: 'synthetic:text', + exportName: 'default', name: 'Text', - path: '', - type: SyntheticPatternType.SyntheticText, - propertyIds: textProperties.map(p => p.getId()) + propertyIds: textProperties.map(p => p.getId()), + slots: [], + type: SyntheticPatternType.SyntheticText }, context ) diff --git a/src/model/pattern-library/pattern-library.ts b/src/model/pattern-library/pattern-library.ts index abe0c7f22..84ee100d2 100644 --- a/src/model/pattern-library/pattern-library.ts +++ b/src/model/pattern-library/pattern-library.ts @@ -113,6 +113,10 @@ export class PatternLibrary { return this.bundle; } + public getPatternByContextId(contextId: string): Pattern | undefined { + return this.patterns.find(pattern => pattern.getContextId() === contextId); + } + public getPatternById(id: string): Pattern | undefined { return this.patterns.find(pattern => pattern.getId() === id); } @@ -155,6 +159,16 @@ export class PatternLibrary { .map(match => match.id); } + public removePattern(pattern: Pattern): void { + const index = this.patterns.indexOf(pattern); + + if (index === -1) { + return; + } + + this.patterns.splice(index, 1); + } + public setBundle(bundle: string): void { this.bundle = bundle; } diff --git a/src/model/pattern/pattern.ts b/src/model/pattern/pattern.ts index bd18012ee..55eae9701 100644 --- a/src/model/pattern/pattern.ts +++ b/src/model/pattern/pattern.ts @@ -20,11 +20,12 @@ export enum ConcretePatternType { } export interface PatternInit { - exportName?: string; + contextId: string; + exportName: string; id?: string; name: string; - propertyIds?: string[]; - slots?: PatternSlot[]; + propertyIds: string[]; + slots: PatternSlot[]; type: PatternType; } @@ -53,25 +54,20 @@ export class Pattern { private type: PatternType; public constructor(init: PatternInit, context: PatternContext) { - this.exportName = init.exportName || 'default'; + this.contextId = init.contextId; + this.exportName = init.exportName; this.id = init.id || uuid.v4(); this.name = AlvaUtil.guessName(init.name); this.patternLibrary = context.patternLibrary; - this.propertyIds = init.propertyIds || []; + this.propertyIds = init.propertyIds; + this.slots = init.slots; this.type = init.type; - this.slots = init.slots || [ - new PatternSlot({ - displayName: 'Children', - propertyName: 'children', - id: uuid.v4(), - type: Types.SlotType.Children - }) - ]; } public static from(serialized: Types.SerializedPattern, context: PatternContext): Pattern { return new Pattern( { + contextId: serialized.contextId, exportName: serialized.exportName, id: serialized.id, name: serialized.name, @@ -87,6 +83,10 @@ export class Pattern { this.slots.push(slot); } + public getContextId(): string { + return this.contextId; + } + public getExportName(): string { return this.exportName; } diff --git a/src/model/project.ts b/src/model/project.ts index 2455b6562..66920a42e 100644 --- a/src/model/project.ts +++ b/src/model/project.ts @@ -209,18 +209,20 @@ export class Project { { name: 'Patterns' }, { patternLibrary: this.patternLibrary } ); + this.patternLibrary.getRoot().addChild(folder); - analysis.patterns.forEach(item => { - const pattern = Pattern.from(item.pattern, context); - this.patternLibrary.addPattern(pattern); + analysis.patterns + .forEach(item => { + const pattern = Pattern.from(item.pattern, context); + this.patternLibrary.addPattern(pattern); - item.properties - .map(property => PatternProperty.from(property)) - .forEach(property => this.patternLibrary.addProperty(property)); + item.properties + .map(property => PatternProperty.from(property)) + .forEach(property => this.patternLibrary.addProperty(property)); - folder.addPattern(pattern); - }); + folder.addPattern(pattern); + }); this.patternLibrary.setState(Types.PatternLibraryState.Imported); this.patternLibrary.setBundle(analysis.bundle); diff --git a/src/preview/preview.tsx b/src/preview/preview.tsx index 12acd0f27..b29c8e42f 100644 --- a/src/preview/preview.tsx +++ b/src/preview/preview.tsx @@ -311,6 +311,11 @@ function createComponentGetter(store: PreviewStore): (props: any, synthetics: an case 'pattern': // tslint:disable-next-line:no-any const component = ((window as any).components || {})[pattern.id]; + + if (!component) { + throw new Error(`Could not find component with id "${pattern.id}" for pattern "${pattern.name}".`); + } + return component[pattern.exportName]; case 'synthetic:page': return synthetics.page;