From 7edd4982f4617460e86c1f9e123679b08ac10880 Mon Sep 17 00:00:00 2001 From: Mauricio Palma Date: Tue, 10 Apr 2018 10:37:27 +0200 Subject: [PATCH 01/14] feat: add page overview --- .editorconfig | 5 +- .gitignore | 1 + .vscode/launch.json | 17 +- package-lock.json | 923 ++++--- package.json | 33 +- src/component/chrome/chrome-container.tsx | 52 +- .../chrome/overview-switch-container.tsx | 30 + src/component/container/app.tsx | 44 +- src/component/container/element-list.tsx | 323 ++- src/component/container/element-wrapper.tsx | 108 +- src/component/container/pattern-list.tsx | 11 +- .../container/preview-pane-wrapper.tsx | 39 +- src/component/container/property-list.tsx | 9 +- src/component/container/splash-screen.tsx | 40 + .../page-list/page-list-composite.tsx | 26 - .../page-list/page-list-container.tsx | 62 +- src/component/page-list/page-list-preview.tsx | 6 +- .../page-list/page-tile-container.tsx | 18 +- src/electron/context-menus.ts | 2 +- src/electron/main.ts | 109 +- src/electron/menu.ts | 40 +- src/electron/renderer.ts | 79 + src/electron/renderer/app.html | 8 - src/electron/renderer/app.ts | 73 - src/electron/renderer/preview.ts | 58 - src/electron/server.ts | 315 +++ src/export/exporter.ts | 4 +- src/export/pdf-exporter.ts | 116 +- src/export/png-exporter.ts | 142 +- src/export/sketch-exporter.ts | 115 +- src/lsg/patterns/add-button/index.tsx | 86 + src/lsg/patterns/add-button/pattern.json | 6 + src/lsg/patterns/button/demo.tsx | 6 +- src/lsg/patterns/chrome/demo.tsx | 30 + src/lsg/patterns/chrome/index.tsx | 68 +- src/lsg/patterns/colors/index.tsx | 8 + src/lsg/patterns/copy/index.tsx | 6 +- src/lsg/patterns/dropdown-item/index.tsx | 4 +- src/lsg/patterns/dropdown/index.tsx | 4 +- src/lsg/patterns/element/demo.tsx | 20 +- src/lsg/patterns/element/index.tsx | 2 +- src/lsg/patterns/global-styles/index.tsx | 5 +- src/lsg/patterns/icons/demo.tsx | 12 +- src/lsg/patterns/icons/index.tsx | 18 +- src/lsg/patterns/input/index.tsx | 6 +- src/lsg/patterns/layout/demo.tsx | 2 +- src/lsg/patterns/layout/index.tsx | 39 +- src/lsg/patterns/link/demo.tsx | 34 +- src/lsg/patterns/list/index.tsx | 14 +- src/lsg/patterns/panes/element-pane/index.tsx | 25 +- .../patterns/panes/patterns-pane/index.tsx | 18 +- src/lsg/patterns/panes/preview-pane/index.tsx | 89 +- .../patterns/panes/property-pane/index.tsx | 7 +- src/lsg/patterns/pattern-list/index.tsx | 20 +- src/lsg/patterns/preview-tile/demo.tsx | 23 +- src/lsg/patterns/preview-tile/index.tsx | 15 +- .../property-items/asset-item/index.tsx | 8 +- .../property-items/boolean-item/index.tsx | 6 +- .../property-items/enum-item/index.tsx | 8 +- .../property-items/string-item/index.tsx | 8 +- src/lsg/patterns/space/demo.tsx | 180 +- src/lsg/patterns/space/index.tsx | 18 +- src/lsg/patterns/tab-navigation/index.tsx | 27 +- src/lsg/patterns/tag.tsx | 24 + src/lsg/patterns/view-switch/demo.tsx | 18 + src/lsg/patterns/view-switch/index.tsx | 94 + src/lsg/patterns/view-switch/pattern.json | 6 + src/message/index.ts | 42 + src/preview/components-loader.ts | 27 + src/preview/get-component.ts | 66 + .../renderer => preview}/highlight-area.ts | 3 + src/preview/pattern-id-to-webpack-name.ts | 8 + .../preview-document.ts} | 18 +- src/preview/preview-renderer.tsx | 236 ++ src/preview/preview.tsx | 170 ++ src/store/command/element-location-command.ts | 34 +- src/store/command/property-value-command.ts | 2 + src/store/json.ts | 8 +- src/store/page/page-element.ts | 193 +- src/store/page/page.ts | 22 +- src/store/page/property-value.ts | 4 - src/store/project.ts | 7 +- src/store/store.ts | 129 +- src/store/styleguide/pattern.ts | 29 + .../styleguide/property/asset-property.ts | 8 +- .../styleguide/property/object-property.ts | 34 +- .../styleguide/property/pattern-property.ts | 66 - src/store/styleguide/slot.ts | 53 + src/styleguide/analyzer/directory.ts | 18 +- .../analyzer/styleguide-analyzer.ts | 5 - .../property-analyzer.ts | 30 +- .../slot-analzyer.ts | 47 + .../typescript-react-analyzer.ts | 78 +- .../analyzer/typescript/react-utils.ts | 45 +- .../analyzer/typescript/typescript-utils.ts | 21 + .../renderer/react/error-message.tsx | 29 - .../renderer/react/pattern-component.tsx | 10 - src/styleguide/renderer/react/placeholder.tsx | 13 - src/styleguide/renderer/react/preview.tsx | 333 --- src/test/.gitignore | 1 + src/test/backwards-compatibility-test.ts | 20 +- .../lib/patterns/image/index.d.ts | 1 + .../node_modules/@types/react/LICENSE | 21 + .../node_modules/@types/react/README.md | 16 + .../node_modules/@types/react/global.d.ts | 180 ++ .../node_modules/@types/react/index.d.ts | 2402 +++++++++++++++++ .../node_modules/@types/react/package.json | 114 + .../0.6.0/styleguide/alva/page-testpage.yaml | 4 + .../styleguide/alva/testproject/testpage.yaml | 4 + .../styleguide/alva/testproject/testpage.yaml | 17 +- 110 files changed, 6266 insertions(+), 2074 deletions(-) create mode 100644 src/component/chrome/overview-switch-container.tsx create mode 100644 src/component/container/splash-screen.tsx delete mode 100644 src/component/page-list/page-list-composite.tsx create mode 100644 src/electron/renderer.ts delete mode 100644 src/electron/renderer/app.html delete mode 100644 src/electron/renderer/app.ts delete mode 100644 src/electron/renderer/preview.ts create mode 100644 src/electron/server.ts create mode 100644 src/lsg/patterns/add-button/index.tsx create mode 100644 src/lsg/patterns/add-button/pattern.json create mode 100644 src/lsg/patterns/chrome/demo.tsx create mode 100644 src/lsg/patterns/tag.tsx create mode 100644 src/lsg/patterns/view-switch/demo.tsx create mode 100644 src/lsg/patterns/view-switch/index.tsx create mode 100644 src/lsg/patterns/view-switch/pattern.json create mode 100644 src/message/index.ts create mode 100644 src/preview/components-loader.ts create mode 100644 src/preview/get-component.ts rename src/{styleguide/renderer => preview}/highlight-area.ts (93%) create mode 100644 src/preview/pattern-id-to-webpack-name.ts rename src/{electron/renderer/preview.html => preview/preview-document.ts} (75%) create mode 100644 src/preview/preview-renderer.tsx create mode 100644 src/preview/preview.tsx delete mode 100644 src/store/styleguide/property/pattern-property.ts create mode 100644 src/store/styleguide/slot.ts create mode 100644 src/styleguide/analyzer/typescript-react-analyzer/slot-analzyer.ts delete mode 100644 src/styleguide/renderer/react/error-message.tsx delete mode 100644 src/styleguide/renderer/react/pattern-component.tsx delete mode 100644 src/styleguide/renderer/react/placeholder.tsx delete mode 100644 src/styleguide/renderer/react/preview.tsx create mode 100644 src/test/.gitignore create mode 100644 src/test/dummy-patterns/node_modules/@types/react/LICENSE create mode 100644 src/test/dummy-patterns/node_modules/@types/react/README.md create mode 100644 src/test/dummy-patterns/node_modules/@types/react/global.d.ts create mode 100644 src/test/dummy-patterns/node_modules/@types/react/index.d.ts create mode 100644 src/test/dummy-patterns/node_modules/@types/react/package.json diff --git a/.editorconfig b/.editorconfig index bb66653fd..4865618d2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,5 +10,8 @@ spaces_around_brackets = false spaces_around_operators = true trim_trailing_whitespace = true -[{package.json,.travis.yml}] +[*.{yaml,yml}] +indent_style = space + +[package.json] indent_style = space diff --git a/.gitignore b/.gitignore index 8663a4eb1..f8f280423 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build dist designkit node_modules +*.log diff --git a/.vscode/launch.json b/.vscode/launch.json index fe629ec38..396c874fe 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,6 +10,9 @@ "name": "Attach to main browser instance", "port": 9222, "webRoot": "${workspaceFolder}", + "windows": { + "url": "${workspaceFolder}\\build\\electron\\renderer\\app.html" + }, "url": "${workspaceFolder}/build/electron/renderer/app.html" }, { @@ -18,6 +21,9 @@ "name": "Attach to preview browser instance", "port": 9222, "webRoot": "${workspaceFolder}", + "windows": { + "url": "${workspaceFolder}\\build\\electron\\renderer\\preview.html" + }, "url": "${workspaceFolder}/build/electron/renderer/preview.html" }, { @@ -26,6 +32,15 @@ "request": "attach", "cwd": "${workspaceRoot}", "port": 9223 - } + }, + { + "type": "node", + "request": "launch", + "name": "Jest All", + "program": "${workspaceFolder}/node_modules/jest/bin/jest", + "args": ["--runInBand"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + } ] } diff --git a/package-lock.json b/package-lock.json index 9ea077b8e..87e34ee31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,44 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "7zip": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/7zip/-/7zip-0.0.6.tgz", - "integrity": "sha1-nK+xca+CMpSQNTtIFvAzR6oVCjA=", - "dev": true - }, - "7zip-bin": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-3.1.0.tgz", - "integrity": "sha512-juYJNi8JEpTUWXwz8ssa8Oop4n/kwJ/pIQP22vJAVAe6RTRD+0m+e9LRNnfK2EDaX8uwmUzLNGviFQRD6SxeOw==", - "dev": true, - "requires": { - "7zip-bin-linux": "1.3.1", - "7zip-bin-mac": "1.0.1", - "7zip-bin-win": "2.2.0" - } - }, - "7zip-bin-linux": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/7zip-bin-linux/-/7zip-bin-linux-1.3.1.tgz", - "integrity": "sha512-Wv1uEEeHbTiS1+ycpwUxYNuIcyohU6Y6vEqY3NquBkeqy0YhVdsNUGsj0XKSRciHR6LoJSEUuqYUexmws3zH7Q==", - "dev": true, - "optional": true - }, - "7zip-bin-mac": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/7zip-bin-mac/-/7zip-bin-mac-1.0.1.tgz", - "integrity": "sha1-Pmh3i78JJq3GgVlCcHRQXUdVXAI=", - "dev": true, - "optional": true - }, - "7zip-bin-win": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/7zip-bin-win/-/7zip-bin-win-2.2.0.tgz", - "integrity": "sha512-uPHXapEmUtlUKTBx4asWMlxtFUWXzEY0KVEgU7QKhgO2LJzzM3kYxM6yOyUZTtYE6mhK4dDn3FDut9SCQWHzgg==", - "dev": true, - "optional": true - }, "@babel/code-frame": { "version": "7.0.0-beta.44", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz", @@ -316,8 +278,8 @@ "integrity": "sha512-jaAP61py+ISMF3/n3yIiIuY5h6mJlucOqawu5mLB1HaQADLvg/y5UB3pT7HSucZJan34lp7+7ylQPfbKEGmxrA==", "dev": true, "requires": { - "JSONStream": "1.3.1", "is-text-path": "1.0.1", + "JSONStream": "1.3.1", "lodash": "4.17.5", "meow": "4.0.0", "split2": "2.2.0", @@ -783,36 +745,317 @@ "glob-to-regexp": "0.3.0" } }, - "@patternplate/api": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@patternplate/api/-/api-2.4.0.tgz", - "integrity": "sha1-2vvcNnpyIMQ89i62mUHl2YubrW8=", + "@patternplate/cli": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/cli/-/cli-2.5.2.tgz", + "integrity": "sha512-PitBWawwjmAgWVYq4BKI4qOKhT1mW7wmumFrDfKmLLwjfK6dlkjFACI5U3cfjgwbSP+Rw1U1AaBW5tpyv1MnzQ==", "dev": true, "requires": { + "@babel/runtime": "7.0.0-beta.44", "@marionebl/sander": "0.6.1", - "@patternplate/compiler": "2.4.0", - "@patternplate/load-config": "2.1.6", - "@patternplate/load-docs": "2.1.6", - "@patternplate/load-meta": "2.1.6", - "@patternplate/validate-config": "2.3.0", - "aggregate-error": "1.0.0", + "@patternplate/client": "2.5.2", + "@patternplate/compiler": "2.5.2", + "@patternplate/create-default": "2.5.2", + "@patternplate/load-config": "2.5.2", + "@patternplate/load-docs": "2.5.2", + "@patternplate/load-meta": "2.5.2", + "@patternplate/validate-config": "2.5.2", "arson": "0.2.6", - "chokidar": "1.7.0", - "common-dir": "1.0.1", - "dargs": "5.1.0", + "chalk": "2.4.0", + "command-exists": "1.2.6", + "errorhandler": "1.5.0", + "execa": "0.9.0", "express": "4.16.3", - "glob-parent": "3.1.0", + "express-slash": "2.0.1", + "import-from": "2.1.0", "memory-fs": "0.4.1", - "micromatch": "3.1.9", + "meow": "3.7.0", + "ora": "2.0.0", "require-from-string": "2.0.1", + "resolve-from": "4.0.0", "resolve-pkg": "1.0.0", "string-hash": "1.1.3", - "unindent": "2.0.0", - "ws": "4.1.0", - "yargs-parser": "9.0.2", - "zen-observable": "0.7.1" + "unindent": "2.0.0" }, "dependencies": { + "@patternplate/api": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/api/-/api-2.5.2.tgz", + "integrity": "sha1-ZAKivb5/BqeXKJMBzol6SfAfSqY=", + "dev": true, + "requires": { + "@marionebl/sander": "0.6.1", + "@patternplate/compiler": "2.5.2", + "@patternplate/load-config": "2.5.2", + "@patternplate/load-docs": "2.5.2", + "@patternplate/load-meta": "2.5.2", + "@patternplate/validate-config": "2.5.2", + "aggregate-error": "1.0.0", + "arson": "0.2.6", + "chokidar": "1.7.0", + "common-dir": "1.0.1", + "dargs": "5.1.0", + "express": "4.16.3", + "glob-parent": "3.1.0", + "memory-fs": "0.4.1", + "micromatch": "3.1.9", + "require-from-string": "2.0.1", + "resolve-pkg": "1.0.0", + "string-hash": "1.1.3", + "unindent": "2.0.0", + "ws": "4.1.0", + "yargs-parser": "9.0.2", + "zen-observable": "0.7.1" + } + }, + "@patternplate/client": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/client/-/client-2.5.2.tgz", + "integrity": "sha512-b+vokgfnW7Ait/Adz//wejT/kbndGQrBYs9/LdMFQPKfXKM1nVl3BD2AKAzPpS/XRglrD2b80b/dXHCJWslFbw==", + "dev": true, + "requires": { + "@patternplate/api": "2.5.2", + "@patternplate/load-config": "2.5.2", + "@patternplate/load-docs": "2.5.2", + "@patternplate/load-meta": "2.5.2", + "buble": "0.19.3", + "cors": "2.8.4", + "express": "4.16.3", + "globby": "8.0.1", + "iframe-resizer": "3.6.0", + "isomorphic-fetch": "2.2.1", + "load-json-file": "4.0.0", + "memory-fs": "0.4.1", + "serve-static": "1.13.2" + } + }, + "@patternplate/compiler": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/compiler/-/compiler-2.5.2.tgz", + "integrity": "sha1-I5HS4aGdiNfx/xLO0tcLYuRRnq4=", + "dev": true, + "requires": { + "@patternplate/cover-client": "2.5.2", + "@patternplate/demo-client": "2.5.2", + "@patternplate/load-config": "2.5.2", + "@patternplate/probe-client": "2.5.2", + "@patternplate/webpack-entry": "2.5.2", + "css-loader": "0.28.11", + "html-loader": "0.5.5", + "memory-fs": "0.4.1", + "read-pkg": "3.0.0", + "resolve-from": "4.0.0", + "to-string-loader": "1.1.5", + "webpack": "4.5.0", + "webpack-node-externals": "1.7.2" + } + }, + "@patternplate/cover-client": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/cover-client/-/cover-client-2.5.2.tgz", + "integrity": "sha1-RvSLcJEU20meK0yZ/tqn5lg90BQ=", + "dev": true + }, + "@patternplate/create-default": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/create-default/-/create-default-2.5.2.tgz", + "integrity": "sha1-34P1MQ2NlcFJv5nFjPyJtqACRGQ=", + "dev": true + }, + "@patternplate/demo-client": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/demo-client/-/demo-client-2.5.2.tgz", + "integrity": "sha1-jWshA+Od41Hhwxrx3IbEzBz+ldY=", + "dev": true, + "requires": { + "iframe-resizer": "3.6.0" + } + }, + "@patternplate/load-config": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/load-config/-/load-config-2.5.2.tgz", + "integrity": "sha1-AoXuVCcPYZTH29xBo9avST3fKBU=", + "dev": true, + "requires": { + "@patternplate/render-default": "2.5.2", + "cosmiconfig": "3.1.0", + "resolve-from": "4.0.0" + } + }, + "@patternplate/load-doc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/load-doc/-/load-doc-2.5.2.tgz", + "integrity": "sha1-amW9D9FM/Ot2jYmdeGIPPU1+FSM=", + "dev": true, + "requires": { + "@marionebl/sander": "0.6.1", + "globby": "6.1.0" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "@patternplate/load-docs": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/load-docs/-/load-docs-2.5.2.tgz", + "integrity": "sha1-EPtB6BOwiPjaS+vcEMxlpk5UOe0=", + "dev": true, + "requires": { + "@marionebl/sander": "0.6.1", + "front-matter": "2.3.0", + "globby": "6.1.0", + "lodash": "4.17.5", + "remark": "8.0.0", + "shortid": "2.2.8", + "unist-util-find": "1.0.1" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "@patternplate/load-manifest": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/load-manifest/-/load-manifest-2.5.2.tgz", + "integrity": "sha1-94ZtfFkS9a5Mx45eFPpm9WvC/Cs=", + "dev": true, + "requires": { + "@marionebl/sander": "0.6.1", + "load-json-file": "4.0.0" + } + }, + "@patternplate/load-meta": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/load-meta/-/load-meta-2.5.2.tgz", + "integrity": "sha1-DWVWcPey9pBFq5mPeqzSwun05Cs=", + "dev": true, + "requires": { + "@marionebl/sander": "0.6.1", + "@patternplate/load-doc": "2.5.2", + "@patternplate/load-manifest": "2.5.2", + "globby": "6.1.0", + "load-source-map": "1.0.0", + "p-filter": "1.0.0" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "@patternplate/probe-client": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/probe-client/-/probe-client-2.5.2.tgz", + "integrity": "sha1-LfhBTqFfXIEHXKXG+Z1CltaVkvU=", + "dev": true, + "requires": { + "@patternplate/websocket-client": "2.5.2", + "arson": "0.2.6", + "iframe-resizer": "3.6.0" + } + }, + "@patternplate/render-default": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/render-default/-/render-default-2.5.2.tgz", + "integrity": "sha1-VhXVQ/Pholfzf0RAyObPGpwKsFk=", + "dev": true, + "requires": { + "lodash": "4.17.5" + } + }, + "@patternplate/validate-config": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/validate-config/-/validate-config-2.5.2.tgz", + "integrity": "sha1-f9l6+cvF1miclvfUuMwid3kr0lI=", + "dev": true, + "requires": { + "@webpack-contrib/schema-utils": "1.0.0-beta.0" + } + }, + "@patternplate/webpack-entry": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/webpack-entry/-/webpack-entry-2.5.2.tgz", + "integrity": "sha1-b8KMiJ5U1cb9bLP5wa0weNHFN5k=", + "dev": true, + "requires": { + "glob-parent": "3.1.0", + "globby": "6.1.0", + "loader-utils": "1.1.0", + "path-exists": "3.0.0", + "raw-loader": "0.5.1", + "require-from-string": "2.0.1", + "resolve-from": "4.0.0" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "@patternplate/websocket-client": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@patternplate/websocket-client/-/websocket-client-2.5.2.tgz", + "integrity": "sha1-fQmLotyPt3YBfuUXjGmLkgTRgmg=", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, "anymatch": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", @@ -878,6 +1121,17 @@ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, + "chalk": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, "chokidar": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", @@ -906,12 +1160,42 @@ } } }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "2.0.0" + } + }, + "cli-spinners": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-1.3.1.tgz", + "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==", + "dev": true + }, "dargs": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-5.1.0.tgz", "integrity": "sha1-7H6lDHhWTNNsnV7Bj2Yyn63ieCk=", "dev": true }, + "execa": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.9.0.tgz", + "integrity": "sha512-BbUMBiX4hqiHZUA5+JujIjNb6TyAlp2D5KLheMjMluwOuzcnylDL4AxZYLLn1n2AGB49eSWwyKvvEQoRpnAtmA==", + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + }, "expand-brackets": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", @@ -930,6 +1214,12 @@ "is-extglob": "1.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", @@ -945,133 +1235,61 @@ "is-extglob": "1.0.0" } }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "dev": true, - "requires": { - "camelcase": "4.1.0" - } - } - } - }, - "@patternplate/cli": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@patternplate/cli/-/cli-2.3.1.tgz", - "integrity": "sha1-+KZ8wFwr+6/QCdpbIbU9XdF817k=", - "dev": true, - "requires": { - "@babel/runtime": "7.0.0-beta.44", - "@marionebl/sander": "0.6.1", - "@patternplate/client": "2.4.0", - "@patternplate/compiler": "2.4.0", - "@patternplate/create-default": "2.1.6", - "@patternplate/load-config": "2.1.6", - "@patternplate/load-docs": "2.1.6", - "@patternplate/load-meta": "2.1.6", - "@patternplate/validate-config": "2.3.0", - "arson": "0.2.6", - "chalk": "2.4.0", - "command-exists": "1.2.6", - "errorhandler": "1.5.0", - "execa": "0.9.0", - "express": "4.16.3", - "express-slash": "2.0.1", - "import-from": "2.1.0", - "memory-fs": "0.4.1", - "meow": "3.7.0", - "ora": "2.0.0", - "require-from-string": "2.0.1", - "resolve-from": "4.0.0", - "resolve-pkg": "1.0.0", - "string-hash": "1.1.3", - "unindent": "2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", - "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" - } - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "restore-cursor": "2.0.0" + "mimic-fn": "1.1.0" } }, - "cli-spinners": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-1.3.1.tgz", - "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==", - "dev": true - }, - "execa": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.9.0.tgz", - "integrity": "sha512-BbUMBiX4hqiHZUA5+JujIjNb6TyAlp2D5KLheMjMluwOuzcnylDL4AxZYLLn1n2AGB49eSWwyKvvEQoRpnAtmA==", + "ora": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-2.0.0.tgz", + "integrity": "sha512-g+IR0nMUXq1k4nE3gkENbN4wkF0XsVZFyxznTF6CdmwQ9qeTGONGpSR9LM5//1l0TVvJoJF3MkMtJp6slUsWFg==", "dev": true, "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" + "chalk": "2.4.0", + "cli-cursor": "2.1.0", + "cli-spinners": "1.3.1", + "log-symbols": "2.2.0", + "strip-ansi": "4.0.0", + "wcwidth": "1.0.1" } }, - "has-flag": { + "path-exists": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "mimic-fn": "1.1.0" + "pify": "3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, - "ora": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-2.0.0.tgz", - "integrity": "sha512-g+IR0nMUXq1k4nE3gkENbN4wkF0XsVZFyxznTF6CdmwQ9qeTGONGpSR9LM5//1l0TVvJoJF3MkMtJp6slUsWFg==", + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "chalk": "2.4.0", - "cli-cursor": "2.1.0", - "cli-spinners": "1.3.1", - "log-symbols": "2.2.0", - "strip-ansi": "4.0.0", - "wcwidth": "1.0.1" + "load-json-file": "4.0.0", + "normalize-package-data": "2.4.0", + "path-type": "3.0.0" } }, "restore-cursor": { @@ -1101,215 +1319,18 @@ "requires": { "has-flag": "3.0.0" } - } - } - }, - "@patternplate/client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@patternplate/client/-/client-2.4.0.tgz", - "integrity": "sha1-b79RA/VUgiiwljwDuwDlFN+GUy4=", - "dev": true, - "requires": { - "@patternplate/api": "2.4.0", - "@patternplate/load-config": "2.1.6", - "@patternplate/load-docs": "2.1.6", - "@patternplate/load-meta": "2.1.6", - "buble": "0.19.3", - "express": "4.16.3", - "globby": "8.0.1", - "iframe-resizer": "3.6.0", - "isomorphic-fetch": "2.2.1", - "load-json-file": "4.0.0", - "memory-fs": "0.4.1", - "serve-static": "1.13.2" - } - }, - "@patternplate/compiler": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@patternplate/compiler/-/compiler-2.4.0.tgz", - "integrity": "sha1-AsY3wgBjnW68nU0ZcAMAsHK0MWk=", - "dev": true, - "requires": { - "@patternplate/cover-client": "2.1.6", - "@patternplate/demo-client": "2.4.0", - "@patternplate/load-config": "2.1.6", - "@patternplate/probe-client": "2.1.6", - "@patternplate/webpack-entry": "2.1.6", - "css-loader": "0.28.11", - "html-loader": "0.5.5", - "memory-fs": "0.4.1", - "read-pkg": "3.0.0", - "resolve-from": "4.0.0", - "to-string-loader": "1.1.5", - "webpack": "4.5.0", - "webpack-node-externals": "1.7.2" - }, - "dependencies": { - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "4.0.0", - "normalize-package-data": "2.4.0", - "path-type": "3.0.0" - } - } - } - }, - "@patternplate/cover-client": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@patternplate/cover-client/-/cover-client-2.1.6.tgz", - "integrity": "sha1-IQZ+ZXwz02W2UNuWeKMyAm5+w5A=", - "dev": true - }, - "@patternplate/create-default": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@patternplate/create-default/-/create-default-2.1.6.tgz", - "integrity": "sha1-yMEExN1euKQ+VPRlK3DxaAWF/jE=", - "dev": true - }, - "@patternplate/demo-client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@patternplate/demo-client/-/demo-client-2.4.0.tgz", - "integrity": "sha1-YvspugjWiFDiBD1HwRfnuNV+xrU=", - "dev": true, - "requires": { - "iframe-resizer": "3.6.0" - } - }, - "@patternplate/load-config": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@patternplate/load-config/-/load-config-2.1.6.tgz", - "integrity": "sha1-xU/1uIudbxL3Zrb4iEwCtV8A0ZU=", - "dev": true, - "requires": { - "@patternplate/render-default": "2.1.6", - "cosmiconfig": "3.1.0", - "resolve-from": "4.0.0" - } - }, - "@patternplate/load-doc": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@patternplate/load-doc/-/load-doc-2.1.6.tgz", - "integrity": "sha1-aQck8B261MexttXeWAufd+UAS6o=", - "dev": true, - "requires": { - "@marionebl/sander": "0.6.1", - "globby": "6.1.0" - }, - "dependencies": { - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - } - } - }, - "@patternplate/load-docs": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@patternplate/load-docs/-/load-docs-2.1.6.tgz", - "integrity": "sha1-Hs4/Schx6+B6smLFgD9VhzUsG7c=", - "dev": true, - "requires": { - "@marionebl/sander": "0.6.1", - "front-matter": "2.3.0", - "globby": "6.1.0", - "lodash": "4.17.5", - "remark": "8.0.0", - "shortid": "2.2.8", - "unist-util-find": "1.0.1" - }, - "dependencies": { - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - } - } - }, - "@patternplate/load-manifest": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@patternplate/load-manifest/-/load-manifest-2.1.6.tgz", - "integrity": "sha1-egZ1V2wZ02AZMVUD6ajVjU39kM4=", - "dev": true, - "requires": { - "@marionebl/sander": "0.6.1", - "load-json-file": "4.0.0" - } - }, - "@patternplate/load-meta": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@patternplate/load-meta/-/load-meta-2.1.6.tgz", - "integrity": "sha1-XZJdmkBaEMFCkrR2pCHojh2SJQg=", - "dev": true, - "requires": { - "@marionebl/sander": "0.6.1", - "@patternplate/load-doc": "2.1.6", - "@patternplate/load-manifest": "2.1.6", - "globby": "6.1.0", - "load-source-map": "1.0.0", - "p-filter": "1.0.0" - }, - "dependencies": { - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", "dev": true, "requires": { - "array-union": "1.0.2", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "camelcase": "4.1.0" } } } }, - "@patternplate/probe-client": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@patternplate/probe-client/-/probe-client-2.1.6.tgz", - "integrity": "sha1-E8tLNiGhFV6CnHZO7AI81UJURyQ=", - "dev": true, - "requires": { - "@patternplate/websocket-client": "2.1.6", - "arson": "0.2.6", - "iframe-resizer": "3.6.0" - } - }, "@patternplate/render-default": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/@patternplate/render-default/-/render-default-2.1.6.tgz", @@ -1344,57 +1365,6 @@ "styled-components": "3.2.5" } }, - "@patternplate/validate-config": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@patternplate/validate-config/-/validate-config-2.3.0.tgz", - "integrity": "sha1-SA3b15Q+T31X0UGwnR+HZjGgnLM=", - "dev": true, - "requires": { - "@webpack-contrib/schema-utils": "1.0.0-beta.0" - } - }, - "@patternplate/webpack-entry": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@patternplate/webpack-entry/-/webpack-entry-2.1.6.tgz", - "integrity": "sha1-rp+erGfHcmSZQKVL3LSPjMJhWvA=", - "dev": true, - "requires": { - "glob-parent": "3.1.0", - "globby": "6.1.0", - "loader-utils": "1.1.0", - "path-exists": "3.0.0", - "raw-loader": "0.5.1", - "require-from-string": "2.0.1", - "resolve-from": "4.0.0" - }, - "dependencies": { - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, - "@patternplate/websocket-client": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@patternplate/websocket-client/-/websocket-client-2.1.6.tgz", - "integrity": "sha1-zEgH4uK84ziPKdi2muvM0Kbbkd8=", - "dev": true - }, "@types/chokidar": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/@types/chokidar/-/chokidar-1.7.5.tgz", @@ -1646,16 +1616,28 @@ } } }, - "JSONStream": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", - "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "7zip": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/7zip/-/7zip-0.0.6.tgz", + "integrity": "sha1-nK+xca+CMpSQNTtIFvAzR6oVCjA=", + "dev": true + }, + "7zip-bin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-3.1.0.tgz", + "integrity": "sha512-juYJNi8JEpTUWXwz8ssa8Oop4n/kwJ/pIQP22vJAVAe6RTRD+0m+e9LRNnfK2EDaX8uwmUzLNGviFQRD6SxeOw==", "dev": true, "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" + "7zip-bin-mac": "1.0.1" } }, + "7zip-bin-mac": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/7zip-bin-mac/-/7zip-bin-mac-1.0.1.tgz", + "integrity": "sha1-Pmh3i78JJq3GgVlCcHRQXUdVXAI=", + "dev": true, + "optional": true + }, "abab": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", @@ -1812,18 +1794,9 @@ "integrity": "sha512-x0XQKDOXnJIO0BWUMdzDK76S2luBWfxjfDHXOecoXuKbHIoKNZ7xN+jwECm5TzrybfnPMSehzKz5clTwUVbDEQ==", "dev": true, "requires": { - "app-builder-bin-linux": "1.8.3", - "app-builder-bin-mac": "1.8.3", - "app-builder-bin-win": "1.8.3" + "app-builder-bin-mac": "1.8.3" } }, - "app-builder-bin-linux": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/app-builder-bin-linux/-/app-builder-bin-linux-1.8.3.tgz", - "integrity": "sha512-9SyskqOlydRS4vXDkc2DlwZK937heoQdvJ70BUEIE090J09seG1n8Xx4dDjvirc6nu+Nob4n4zEhKFVu/GgXbw==", - "dev": true, - "optional": true - }, "app-builder-bin-mac": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/app-builder-bin-mac/-/app-builder-bin-mac-1.8.3.tgz", @@ -1831,13 +1804,6 @@ "dev": true, "optional": true }, - "app-builder-bin-win": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/app-builder-bin-win/-/app-builder-bin-win-1.8.3.tgz", - "integrity": "sha512-7jfqRgqQqWMNRnT3Et5ZA3Ju666J1ygS86V94ebgOWoVQWkMmPKFm7vJeTVJR1XcPSeDB+7dD6cHZ0bTlJPdPQ==", - "dev": true, - "optional": true - }, "app-root-path": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz", @@ -3714,8 +3680,8 @@ "integrity": "sha512-8MD05yN0Zb6aRsZnFX1ET+8rHWfWJk+my7ANCJZBU2mhz7TSB1fk2vZhkrwVy/PCllcTYAP/1T1NiWQ7Z01mKw==", "dev": true, "requires": { - "JSONStream": "1.3.1", "is-text-path": "1.0.1", + "JSONStream": "1.3.1", "lodash": "4.17.5", "meow": "3.7.0", "split2": "2.2.0", @@ -3957,8 +3923,8 @@ "integrity": "sha512-8MD05yN0Zb6aRsZnFX1ET+8rHWfWJk+my7ANCJZBU2mhz7TSB1fk2vZhkrwVy/PCllcTYAP/1T1NiWQ7Z01mKw==", "dev": true, "requires": { - "JSONStream": "1.3.1", "is-text-path": "1.0.1", + "JSONStream": "1.3.1", "lodash": "4.17.5", "meow": "3.7.0", "split2": "2.2.0", @@ -4066,6 +4032,16 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", + "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "dev": true, + "requires": { + "object-assign": "4.1.1", + "vary": "1.1.2" + } + }, "cosmiconfig": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-3.1.0.tgz", @@ -4765,17 +4741,13 @@ "integrity": "sha512-2uJICLdVnkDqizLZa4HclhBsAWiSf1sEPeKS5+GhuxGaDdWnabXZ4ed9hYQ5u81P3hW3lB+xvxDw2TTinDB9Tw==", "dev": true, "requires": { - "app-builder-bin-linux": "1.7.2", - "app-builder-bin-mac": "1.7.2", - "app-builder-bin-win": "1.7.2" + "app-builder-bin-mac": "1.7.2" } }, "app-builder-bin-linux": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/app-builder-bin-linux/-/app-builder-bin-linux-1.7.2.tgz", - "integrity": "sha512-spoW8f6sqo5aKpoZx+scIPMonSTrh8JtKWM3MuDqBJiXiUCtpVIPez5c4AycGwQnmh167KFjK4pn129o3k+aHQ==", - "dev": true, - "optional": true + "integrity": "sha512-spoW8f6sqo5aKpoZx+scIPMonSTrh8JtKWM3MuDqBJiXiUCtpVIPez5c4AycGwQnmh167KFjK4pn129o3k+aHQ==" }, "app-builder-bin-mac": { "version": "1.7.2", @@ -4787,9 +4759,7 @@ "app-builder-bin-win": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/app-builder-bin-win/-/app-builder-bin-win-1.7.2.tgz", - "integrity": "sha512-/7tvJZas9T5TBM3QUV0xQkRQAyUlsXdtUsqtOg48mgp1ogPqDjs4W2Jr31YhhiUHDdNgamZc655PzWqAEnbZfQ==", - "dev": true, - "optional": true + "integrity": "sha512-/7tvJZas9T5TBM3QUV0xQkRQAyUlsXdtUsqtOg48mgp1ogPqDjs4W2Jr31YhhiUHDdNgamZc655PzWqAEnbZfQ==" }, "builder-util-runtime": { "version": "4.1.0", @@ -6775,7 +6745,8 @@ }, "jsbn": { "version": "0.1.1", - "bundled": true + "bundled": true, + "optional": true }, "json-schema": { "version": "0.2.3", @@ -7069,6 +7040,13 @@ } } }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, "string-width": { "version": "1.0.2", "bundled": true, @@ -7078,13 +7056,6 @@ "strip-ansi": "3.0.1" } }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, "stringstream": { "version": "0.0.5", "bundled": true, @@ -9457,6 +9428,16 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, + "JSONStream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "dev": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -13682,6 +13663,14 @@ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "dev": true }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, "string-argv": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz", @@ -13754,14 +13743,6 @@ } } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, "stringify-entities": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.1.tgz", diff --git a/package.json b/package.json index 0329f2ae3..10f279872 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Alva is a radically new design tool that enables cross-functional teams to design digital products.", "main": "./build/electron/main.js", "scripts": { - "build": "tslint --project . -c tslint.json 'src/**/*.ts' && tsc --project . && cp src/electron/renderer/*.html build/electron/renderer", + "build": "npm run lint && tsc --project .", "build:clean": "npm run clean && npm run build", "build:electron": "electron-builder -mwl && node ./scripts/dist-postprocess.js", "build:lsg": "tsc --preserveWatchOutput --project src/lsg --outDir build/lsg/patterns", @@ -13,11 +13,13 @@ "commitmsg": "commitlint -e $GIT_PARAMS", "dist": "node ./scripts/dist-preprocess.js", "docs": "typedoc src", - "lint": "tslint --project .", - "start": "npm run build && concurrently \"electron build/electron/main.js --remote-debugging-port=9222 --inspect=9223\" \"tsc --project . --watch\"", + "lint": "tslint --project . -c tslint.json 'src/**/*.ts'", + "start": "npm run build && electron build/electron/main.js --remote-debugging-port=9222 --inspect=9223", "start:clean": "npm run clean && npm run start", "start:lsg": "npm run build:lsg && concurrently \"npm run build:lsg -- -w\" \"patternplate start\"", "precommit": "lint-staged", + "watch": "npm run build && concurrently \"electron build/electron/main.js --remote-debugging-port=9222 --inspect=9223\" \"tsc --project . --watch\"", + "watch:clean": "npm run clean && npm run watch", "watch:tests": "jest --watch", "test": "jest" }, @@ -91,25 +93,31 @@ "@commitlint/config-conventional": "6.1.3", "@commitlint/prompt-cli": "6.1.3", "@commitlint/travis-cli": "6.1.3", - "@patternplate/cli": "2.3.1", + "@patternplate/cli": "2.5.2", "@patternplate/render-styled-components": "2.1.6", "@types/chokidar": "1.7.5", "@types/deep-assign": "0.1.1", "@types/electron-devtools-installer": "2.0.3", + "@types/express": "4.11.1", "@types/fs-extra": "5.0.2", + "@types/get-port": "3.2.0", "@types/isomorphic-fetch": "0.0.34", "@types/jest": "22.2.2", "@types/js-yaml": "3.11.1", "@types/lodash": "4.14.107", + "@types/memory-fs": "0.3.0", "@types/mime-types": "2.1.0", - "@types/node": "9.6.5", "@types/mock-fs": "3.6.30", + "@types/node": "9.6.5", "@types/object-path": "0.9.29", "@types/react": "16.3.10", "@types/react-dom": "16.0.5", "@types/smoothscroll-polyfill": "0.3.0", "@types/username": "3.0.0", "@types/uuid": "3.4.3", + "@types/webpack": "4.1.3", + "@types/webpack-dev-middleware": "2.0.1", + "@types/ws": "4.0.2", "concurrently": "3.5.1", "electron": "1.8.4", "electron-builder": "20.8.1", @@ -120,28 +128,35 @@ "jest-util": "22.4.3", "lint-staged": "7.0.4", "mobx-react-devtools": "4.2.15", - "prettier": "1.12.0", "mock-fs": "4.4.2", + "prettier": "1.12.0", "standard-version": "4.3.0", - "ts-config": "17.0.0", "ts-jest": "22.4.2", "tslint": "5.9.1", "typedoc": "0.11.1" }, "dependencies": { "@brainly/html-sketchapp": "3.0.2", + "@types/loader-utils": "1.1.3", "chokidar": "2.0.3", "cli": "1.0.1", + "commondir": "1.0.1", "deep-assign": "2.0.0", "electron-log": "2.2.14", "electron-updater": "2.21.4", + "express": "4.16.3", "fs-extra": "5.0.0", + "get-port": "3.2.0", + "js-string-escape": "1.0.1", "js-yaml": "3.11.0", + "loader-utils": "1.1.0", "lodash": "4.17.5", + "memory-fs": "0.4.1", "mime-types": "2.1.18", "mobx": "3.6.2", "mobx-react": "4.4.3", "object-path": "0.11.4", + "query-string": "6.0.0", "react": "16.3.1", "react-dom": "16.3.1", "react-router": "4.2.0", @@ -151,7 +166,9 @@ "tslib": "1.9.0", "typescript": "2.8.1", "username": "3.0.0", - "uuid": "3.2.1" + "uuid": "3.2.1", + "webpack": "4.6.0", + "ws": "5.1.1" }, "homepage": "https://meetalva.github.io/" } diff --git a/src/component/chrome/chrome-container.tsx b/src/component/chrome/chrome-container.tsx index bf4910975..1d0851300 100644 --- a/src/component/chrome/chrome-container.tsx +++ b/src/component/chrome/chrome-container.tsx @@ -1,19 +1,36 @@ import Chrome from '../../lsg/patterns/chrome'; +import { CopySize as FontSize } from '../../lsg/patterns/copy'; import { observer } from 'mobx-react'; +import { OverviewSwitchContainer } from './overview-switch-container'; +import { Page } from '../../store/page/page'; import { PageRef } from '../../store/page/page-ref'; import * as React from 'react'; import { Store } from '../../store/store'; +import { ViewSwitch, ViewTitle } from '../../lsg/patterns/view-switch'; @observer export class ChromeContainer extends React.Component { + protected store = Store.getInstance(); + + protected getCurrentPage(): Page | undefined { + return this.store.getCurrentPage(); + } + + protected openPage(page: PageRef | undefined): void { + if (page) { + this.store.openPage(page.getId()); + } + return; + } + public render(): JSX.Element { let nextPage: PageRef | undefined; let previousPage: PageRef | undefined; - const store = Store.getInstance(); - const page = store.getCurrentPage(); - const pages: PageRef[] = page ? page.getProject().getPages() : []; - const currentIndex = page ? pages.indexOf(page.getPageRef()) : 0; + const currentPage = this.getCurrentPage(); + const project = currentPage ? currentPage.getProject() : undefined; + const pages = project ? project.getPages() : []; + const currentIndex = currentPage ? pages.indexOf(currentPage.getPageRef()) : 0; if (currentIndex > 0) { previousPage = pages[currentIndex - 1]; @@ -24,13 +41,26 @@ export class ChromeContainer extends React.Component { } return ( - (previousPage ? store.openPage(previousPage.getId()) : undefined)} - onRightClick={() => (nextPage ? store.openPage(nextPage.getId()) : undefined)} - title={page ? page.getName() : undefined} - > + + + {!this.store.pageOverviewIsOpened && ( + this.openPage(previousPage)} + onRightClick={() => this.openPage(nextPage)} + title={currentPage ? currentPage.getName() : ''} + /> + )} + {this.store.pageOverviewIsOpened && ( + + )} {this.props.children} ); diff --git a/src/component/chrome/overview-switch-container.tsx b/src/component/chrome/overview-switch-container.tsx new file mode 100644 index 000000000..f6c264742 --- /dev/null +++ b/src/component/chrome/overview-switch-container.tsx @@ -0,0 +1,30 @@ +import { observer } from 'mobx-react'; +import { Project } from '../../store/project'; +import * as React from 'react'; +import { Store } from '../../store/store'; +import { ViewSwitch } from '../../lsg/patterns/view-switch'; + +@observer +export class OverviewSwitchContainer extends React.Component { + protected getName(): string { + const store = Store.getInstance(); + const project: Project | undefined = store.getCurrentProject(); + if (store.pageOverviewIsOpened) { + return 'Pages'; + } else { + return project ? project.getName() : 'Unnamed Project'; + } + } + + public render(): JSX.Element { + const store = Store.getInstance(); + return ( + store.togglePageOverview()} + leftVisible={true} + rightVisible={false} + title={`${this.getName()} Overview`} + /> + ); + } +} diff --git a/src/component/container/app.tsx b/src/component/container/app.tsx index 252bac6bd..f61201376 100644 --- a/src/component/container/app.tsx +++ b/src/component/container/app.tsx @@ -14,6 +14,8 @@ import Link from '../../lsg/patterns/link'; import { createMenu } from '../../electron/menu'; import * as MobX from 'mobx'; import { observer } from 'mobx-react'; +import { PageListContainer } from '../page-list/page-list-container'; +import { PageListPreview } from '../page-list/page-list-preview'; import * as PathUtils from 'path'; import { PatternListContainer } from '../../component/container/pattern-list'; import PatternsPane from '../../lsg/patterns/panes/patterns-pane'; @@ -34,9 +36,9 @@ const store = Store.getInstance(); export class App extends React.Component { private static PATTERN_LIST_ID = 'patternlist'; private static PROPERTIES_LIST_ID = 'propertieslist'; - @MobX.observable protected activeTab: string = App.PATTERN_LIST_ID; private ctrlDown: boolean = false; + private shiftDown: boolean = false; public constructor(props: {}) { @@ -147,25 +149,29 @@ export class App extends React.Component { return ( - - {project && [ - - - - - - - - , - , - - - - - - ]} - + {project && + !store.pageOverviewIsOpened && [ + + + + + + + + , + , + + + + + + ]} + {store.pageOverviewIsOpened && ( + + + + )} {!project && ( diff --git a/src/component/container/element-list.tsx b/src/component/container/element-list.tsx index db1e65050..edd8f9ae3 100644 --- a/src/component/container/element-list.tsx +++ b/src/component/container/element-list.tsx @@ -1,3 +1,4 @@ +import { colors } from '../../lsg/patterns/colors'; import { elementMenu } from '../../electron/context-menus'; import { ElementLocationCommand } from '../../store/command/element-location-command'; import { ElementWrapper } from './element-wrapper'; @@ -7,12 +8,34 @@ import { observer } from 'mobx-react'; import { Page } from '../../store/page/page'; import { PageElement } from '../../store/page/page-element'; import { Pattern } from '../../store/styleguide/pattern'; -import { PropertyValue } from '../../store/page/property-value'; import * as React from 'react'; +import { Slot } from '../../store/styleguide/slot'; import { Store } from '../../store/store'; +import * as uuid from 'uuid'; + +export interface ElementListState { + dragging: boolean; +} + +const DRAG_IMG_STYLE = ` + position: fixed; + top: 100vh; + background-color: ${colors.white.toString()}; + color: ${colors.black.toString()}; + padding: 6px 18px; + border-radius: 3px; + font-size: 12px; + opacity: 1; +`; @observer -export class ElementList extends React.Component { +export class ElementList extends React.Component<{}, ElementListState> { + private dragImg?: HTMLElement; + + public state = { + dragging: true + }; + public componentDidMount(): void { createMenu(); } @@ -25,49 +48,45 @@ export class ElementList extends React.Component { key: string, element: PageElement, selectedElement?: PageElement - ): ListItemProps { + ): ElementNodeProps { + const store = Store.getInstance(); const pattern: Pattern | undefined = element.getPattern(); + if (!pattern) { return { label: key, - value: '(invalid)', - children: [] + title: '(invalid)', + id: uuid.v4(), + children: [], + dragging: this.state.dragging }; } - const items: ListItemProps[] = []; - const children: PageElement[] = element.getChildren() || []; - children.forEach((value: PageElement, index: number) => { - items.push( - this.createItemFromProperty( - children.length > 1 ? `Child ${index + 1}` : 'Child', - value, - selectedElement - ) - ); - }); + let defaultSlotItems: ElementNodeProps[] | undefined = []; + const slots: ElementNodeProps[] = []; - const updatePageElement: React.MouseEventHandler = event => { - event.stopPropagation(); - Store.getInstance().setSelectedElement(element); - Store.getInstance().setElementFocussed(true); - }; + pattern.getSlots().forEach(slot => { + const listItem = this.createItemFromSlot(slot, element, selectedElement); + + if (slot.getId() === Pattern.DEFAULT_SLOT_ID) { + defaultSlotItems = listItem.children; + } else { + slots.push(listItem); + } + }); return { label: key, - value: element.getName(), - onClick: updatePageElement, - onContextMenu: () => elementMenu(element), - handleDragStart: (e: React.DragEvent) => { - Store.getInstance().setDraggedElement(element); - }, - handleDragDropForChild: (e: React.DragEvent) => { + title: element.getName(), + dragging: this.state.dragging, + id: element.getId(), + onDragDropForChild: (e: React.DragEvent) => { + this.handleDragEnd(e); const patternId = e.dataTransfer.getData('patternId'); const newParent = element.getParent(); let draggedElement: PageElement | undefined; - const store = Store.getInstance(); if (!patternId) { draggedElement = store.getDraggedElement(); } else { @@ -97,15 +116,22 @@ export class ElementList extends React.Component { } } - store.execute(ElementLocationCommand.addChild(newParent, draggedElement, newIndex)); + store.execute( + ElementLocationCommand.addChild( + newParent, + draggedElement, + element.getParentSlotId(), + newIndex + ) + ); store.setSelectedElement(draggedElement); }, - handleDragDrop: (e: React.DragEvent) => { + onDragDrop: (e: React.DragEvent) => { + this.handleDragEnd(e); const patternId = e.dataTransfer.getData('patternId'); let draggedElement: PageElement | undefined; - const store = Store.getInstance(); if (!patternId) { draggedElement = store.getDraggedElement(); } else { @@ -128,74 +154,215 @@ export class ElementList extends React.Component { store.execute(ElementLocationCommand.addChild(element, draggedElement)); store.setSelectedElement(draggedElement); }, - children: items, - active: element === selectedElement + children: [...slots, ...defaultSlotItems], + active: element === selectedElement && !store.getSelectedSlotId() }; } - public createItemFromProperty( - key: string, - value: PropertyValue, + public createItemFromSlot( + slot: Slot, + element: PageElement, selectedElement?: PageElement - ): ListItemProps { - if (value instanceof Array) { - const items: ListItemProps[] = []; - (value as (string | number)[]).forEach((child, index: number) => { - items.push(this.createItemFromProperty(String(index + 1), child)); - }); - return { value: key, children: items }; + ): ElementNodeProps { + const store = Store.getInstance(); + const slotId = slot.getId(); + const slotContents: PageElement[] = element.getSlotContents(slotId); + const childItems: ElementNodeProps[] = []; + const selectedSlot = store.getSelectedSlotId(); + + slotContents.forEach((value: PageElement, index: number) => { + childItems.push( + this.createItemFromElement( + slotContents.length > 1 ? `Child ${index + 1}` : 'Child', + value, + selectedElement + ) + ); + }); + + const updateSelectedSlot: React.MouseEventHandler = event => { + event.stopPropagation(); + store.setSelectedElement(element); + store.setSelectedSlot(slotId); + store.setElementFocussed(false); + }; + + const slotListItem: ElementNodeProps = { + id: slot.getId(), + title: `\uD83D\uDD18 ${slot.getName()}`, + draggable: false, + dragging: this.state.dragging, + children: childItems, + label: slotId, + onClick: updateSelectedSlot, + onDragDrop: (e: React.DragEvent) => { + const patternId = e.dataTransfer.getData('patternId'); + + let draggedElement: PageElement | undefined; + + if (!patternId) { + draggedElement = store.getDraggedElement(); + } else { + const styleguide = store.getStyleguide(); + + if (!styleguide) { + return; + } + + draggedElement = new PageElement({ + pattern: styleguide.getPattern(patternId), + setDefaults: true + }); + } + + if (!draggedElement) { + return; + } + + store.execute(ElementLocationCommand.addChild(element, draggedElement, slotId)); + store.setSelectedElement(draggedElement); + }, + active: element === selectedElement && selectedSlot === slotId + }; + + return slotListItem; + } + + private handleClick(e: React.MouseEvent): void { + const element = elementFromTarget(e.target); + e.stopPropagation(); + Store.getInstance().setSelectedElement(element); + Store.getInstance().setElementFocussed(true); + } + + private handleContextMenu(e: React.MouseEvent): void { + const element = elementFromTarget(e.target); + if (element) { + elementMenu(element); } + } + + private handleDragEnd(e: React.DragEvent): void { + this.setState({ dragging: false }); - if (value === undefined || value === null || typeof value !== 'object') { - return { label: key, value: String(value) }; + if (this.dragImg && this.dragImg.parentNode) { + this.dragImg.parentNode.removeChild(this.dragImg); } + } - if (value instanceof PageElement) { - return this.createItemFromElement(key, value, selectedElement); - } else { - const items: ListItemProps[] = []; - Object.keys(value).forEach((childKey: string) => { - // tslint:disable-next-line:no-any - items.push(this.createItemFromProperty(childKey, (value as any)[childKey])); - }); - return { value: key, children: items }; + private handleDragStart(e: React.DragEvent): void { + this.setState({ dragging: true }); + const element = elementFromTarget(e.target); + + if (element) { + Store.getInstance().setDraggedElement(element); + const dragImg = document.createElement('div'); + dragImg.textContent = element.getName(); + dragImg.setAttribute('style', DRAG_IMG_STYLE); + document.body.appendChild(dragImg); + e.dataTransfer.setDragImage(dragImg, 75, 15); + this.dragImg = dragImg; } } + private handleMouseLeave(e: React.MouseEvent): void { + this.setState({ dragging: true }); + } + + private handleMouseOver(e: React.MouseEvent): void { + this.setState({ dragging: false }); + } + public render(): JSX.Element | null { const store = Store.getInstance(); const page: Page | undefined = store.getCurrentPage(); - if (page) { - const rootElement = page.getRoot(); - if (!rootElement) { - return null; - } + if (!page) { + return null; + } - const selectedElement = store.getSelectedElement(); + const rootElement = page.getRoot(); - return this.renderList(this.createItemFromElement('Root', rootElement, selectedElement)); - } else { + if (!rootElement) { return null; } - } - public renderList(item: ListItemProps, key?: number): JSX.Element { + const selectedElement = store.getSelectedElement(); + const item = this.createItemFromElement('Root', rootElement, selectedElement); + return ( - this.handleClick(e)} + onContextMenu={e => this.handleContextMenu(e)} + onDragStart={e => this.handleDragStart(e)} + onDragEnd={e => this.handleDragEnd(e)} + onMouseOver={e => this.handleMouseOver(e)} + onMouseLeave={e => this.handleMouseLeave(e)} > - {item.children && - item.children.length > 0 && - item.children.map((child, index) => this.renderList(child, index))} - + + ); } } + +export interface ElementNodeProps extends ListItemProps { + children?: ElementNodeProps[]; + dragging: boolean; + id: string; +} + +function ElementTree(props: ElementNodeProps): JSX.Element { + const children = Array.isArray(props.children) ? props.children : []; + + return ( + + {children.map(child => ( + + ))} + + ); +} + +function above(node: EventTarget, selector: string): HTMLElement | null { + let el = node as HTMLElement; + let ended = false; + + while (el && !ended) { + if (el.matches(selector)) { + break; + } + + if (el.parentElement !== null) { + el = el.parentElement; + } else { + ended = true; + break; + } + } + + return ended ? null : el; +} + +function elementFromTarget(target: EventTarget): PageElement | undefined { + const el = above(target, '[data-id]'); + + if (!el) { + return; + } + + const id = el.getAttribute('data-id'); + + if (typeof id !== 'string') { + return; + } + + const store = Store.getInstance(); + const page = store.getCurrentPage(); + + if (!page) { + return; + } + + return page.getElementById(id); +} diff --git a/src/component/container/element-wrapper.tsx b/src/component/container/element-wrapper.tsx index eb0a6096b..8aca40fc1 100644 --- a/src/component/container/element-wrapper.tsx +++ b/src/component/container/element-wrapper.tsx @@ -9,40 +9,42 @@ export interface ElementWrapperState { export interface ElementWrapperProps { active?: boolean; - handleClick?: React.MouseEventHandler; - handleContextMenu?: React.MouseEventHandler; - handleDragDrop?: React.DragEventHandler; - handleDragDropForChild?: React.DragEventHandler; - handleDragStart?: React.DragEventHandler; + dragging: boolean; + id: string; + onClick?: React.MouseEventHandler; + onContextMenu?: React.MouseEventHandler; + onDragDrop?: React.DragEventHandler; + onDragDropForChild?: React.DragEventHandler; + onDragStart?: React.DragEventHandler; open?: boolean; title: string; } export class ElementWrapper extends React.Component { - public constructor(props: ElementWrapperProps) { - super(props); + public state = { + open: this.props.open, + highlightPlaceholder: false, + highlight: false + }; - this.state = { - open: this.props.open, - highlight: false - }; - - this.handleIconClick = this.handleIconClick.bind(this); - this.handleDragStart = this.handleDragStart.bind(this); - this.handleDragEnter = this.handleDragEnter.bind(this); - this.handleDragLeave = this.handleDragLeave.bind(this); - this.handleDragDrop = this.handleDragDrop.bind(this); - this.handleDragEnterForChild = this.handleDragEnterForChild.bind(this); - this.handleDragLeaveForChild = this.handleDragLeaveForChild.bind(this); - this.handleDragDropForChild = this.handleDragDropForChild.bind(this); + private handleClick(e: React.MouseEvent): void { + const target = e.target as HTMLElement; + const icon = above(target, 'svg[data-icon]'); + + if (icon) { + e.stopPropagation(); + this.setState({ + open: !this.state.open + }); + } } private handleDragDrop(e: React.DragEvent): void { this.setState({ highlight: false }); - if (typeof this.props.handleDragDrop === 'function') { - this.props.handleDragDrop(e); + if (typeof this.props.onDragDrop === 'function') { + this.props.onDragDrop(e); } } @@ -50,8 +52,8 @@ export class ElementWrapper extends React.Component this.handleClick(e)} + onDragDrop={e => this.handleDragDrop(e)} + onDragDropForChild={e => this.handleDragDropForChild(e)} + onDragEnter={e => this.handleDragEnter(e)} + onDragEnterForChild={e => this.handleDragEnterForChild(e)} + onDragLeave={e => this.handleDragLeave(e)} + onDragLeaveForChild={e => this.handleDragLeaveForChild(e)} + onDragStart={e => this.handleDragStart(e)} highlight={this.state.highlight} highlightPlaceholder={this.state.highlightPlaceholder} - handleClick={handleClick} - handleContextMenu={handleContextMenu} - draggable - handleIconClick={this.handleIconClick} - handleDragStart={this.handleDragStart} - handleDragEnter={this.handleDragEnter} - handleDragLeave={this.handleDragLeave} - handleDragDrop={this.handleDragDrop} - handleDragEnterForChild={this.handleDragEnterForChild} - handleDragLeaveForChild={this.handleDragLeaveForChild} - handleDragDropForChild={this.handleDragDropForChild} + id={this.props.id} + open={!this.state.open} + title={title} > {children} ); } } + +function above(node: EventTarget, selector: string): HTMLElement | null { + let el = node as HTMLElement; + let ended = false; + + while (el && !ended) { + if (el.matches(selector)) { + break; + } + + if (el.parentElement !== null) { + el = el.parentElement; + } else { + ended = true; + break; + } + } + + return ended ? null : el; +} diff --git a/src/component/container/pattern-list.tsx b/src/component/container/pattern-list.tsx index 69d5c2328..bdacd35f9 100644 --- a/src/component/container/pattern-list.tsx +++ b/src/component/container/pattern-list.tsx @@ -10,7 +10,7 @@ import PatternList, { PatternListItemProps } from '../../lsg/patterns/pattern-list'; import * as React from 'react'; -import Space, { Size } from '../../lsg/patterns/space'; +import Space, { SpaceSize } from '../../lsg/patterns/space'; import { Store } from '../../store/store'; export interface PatternListContainerItemProps { @@ -106,12 +106,15 @@ export class PatternListContainer extends React.Component { protected handlePatternClick(pattern: Pattern): void { const store: Store = Store.getInstance(); const selectedElement: PageElement | undefined = store.getSelectedElement(); + const selectedSlot = store.getSelectedSlotId(); if (selectedElement) { const newPageElement = new PageElement({ pattern, setDefaults: true }); - store.execute(ElementLocationCommand.addSibling(selectedElement, newPageElement)); + store.execute( + ElementLocationCommand.addChild(selectedElement, newPageElement, selectedSlot) + ); store.setSelectedElement(newPageElement); } } @@ -133,10 +136,10 @@ export class PatternListContainer extends React.Component { const list = this.createList(this.items); return (
- + - {list} + {list}
); } diff --git a/src/component/container/preview-pane-wrapper.tsx b/src/component/container/preview-pane-wrapper.tsx index fefef2463..922bd0759 100644 --- a/src/component/container/preview-pane-wrapper.tsx +++ b/src/component/container/preview-pane-wrapper.tsx @@ -10,22 +10,13 @@ export interface ElementWrapperState { } export class PreviewPaneWrapper extends React.Component { - public constructor(props: {}) { - super(props); - - this.state = { - isResizing: false, - direction: 1, - width: 0, - maxWidth: 0 - }; - - this.handleMouseDownRight = this.handleMouseDownRight.bind(this); - this.handleMouseDownLeft = this.handleMouseDownLeft.bind(this); - this.handleMouseUp = this.handleMouseUp.bind(this); - this.handleMouseMove = this.handleMouseMove.bind(this); - this.handlePreviewWidthUpdate = this.handlePreviewWidthUpdate.bind(this); - } + public state = { + isResizing: false, + direction: 1, + width: 0, + maxWidth: 0, + mousePosition: undefined + }; private handleMouseDownLeft(e: React.MouseEvent): void { this.setState({ @@ -46,7 +37,7 @@ export class PreviewPaneWrapper extends React.Component): void { const { maxWidth, mousePosition, width, direction } = this.state; - if (!mousePosition) { + if (typeof mousePosition !== 'number' || Number.isNaN(mousePosition)) { return; } @@ -83,15 +74,17 @@ export class PreviewPaneWrapper extends React.Component this.handleMouseDownLeft(e)} + onMouseDownRight={e => this.handleMouseDownRight(e)} + onMouseMove={e => this.handleMouseMove(e)} + onMouseUp={() => this.handleMouseUp()} + onPreviewWidthUpdate={e => this.handlePreviewWidthUpdate(e)} previewFrame={this.props.previewFrame} + width={this.state.width} /> ); } diff --git a/src/component/container/property-list.tsx b/src/component/container/property-list.tsx index fcc86c5e0..61fa50786 100644 --- a/src/component/container/property-list.tsx +++ b/src/component/container/property-list.tsx @@ -98,8 +98,13 @@ class PropertyTree extends React.Component { const { property } = context; return ( - - {this.renderItems()} + + {this.isOpen ? this.renderItems() : 'hidden'} ); } diff --git a/src/component/container/splash-screen.tsx b/src/component/container/splash-screen.tsx new file mode 100644 index 000000000..5de382ae8 --- /dev/null +++ b/src/component/container/splash-screen.tsx @@ -0,0 +1,40 @@ +import Button, { Order } from '../../lsg/patterns/button'; +import { colors } from '../../lsg/patterns/colors'; +import Copy, { CopySize } from '../../lsg/patterns/copy'; +import { Headline } from '../../lsg/patterns/headline'; +import Link from '../../lsg/patterns/link'; +import * as React from 'react'; +import Space, { SpaceSize } from '../../lsg/patterns/space'; +// tslint:disable-next-line +import SplashScreenContainer from '../../lsg/patterns/splash-screen'; + +export interface SplashScreenProps { + onPrimaryButtonClick?: React.MouseEventHandler; + onSecondaryButtonClick?: React.MouseEventHandler; +} + +export function SplashScreen(props: SplashScreenProps): JSX.Element { + return ( + + + + Getting started with Alva + + + + + You can open an existing Alva space or create a new one based on our designkit + including some basic components to kickstart your project. + + + + + + + or open existing Alva space + + + ); +} diff --git a/src/component/page-list/page-list-composite.tsx b/src/component/page-list/page-list-composite.tsx deleted file mode 100644 index 4a1fa211a..000000000 --- a/src/component/page-list/page-list-composite.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import Layout from '../../lsg/patterns/layout'; -import { observer } from 'mobx-react'; -import { PageRef } from '../../store/page/page-ref'; -import { PageTileContainer } from './page-tile-container'; -import * as React from 'react'; - -export interface PageListProps { - focusStates: boolean[]; - pages: PageRef[]; - onClick(event: React.MouseEvent, index: number): void; -} - -export const PageListComposite: React.StatelessComponent = observer( - (props): JSX.Element => ( - - {props.pages.map((page: PageRef, i: number) => ( - props.onClick(e, i)} - page={page} - /> - ))} - - ) -); diff --git a/src/component/page-list/page-list-container.tsx b/src/component/page-list/page-list-container.tsx index 76597f4a2..78814f7bf 100644 --- a/src/component/page-list/page-list-container.tsx +++ b/src/component/page-list/page-list-container.tsx @@ -1,47 +1,31 @@ -import * as MobX from 'mobx'; +import Layout from '../../lsg/patterns/layout'; import { observer } from 'mobx-react'; -import { PageListComposite } from './page-list-composite'; import { PageRef } from '../../store/page/page-ref'; -import { Project } from '../../store/project'; +import { PageTileContainer } from './page-tile-container'; import * as React from 'react'; import { Store } from '../../store/store'; -@observer -export class PageListContainer extends React.Component { - @MobX.observable public focusStates: boolean[] = this.generateInitialFocusList(false); +export const PageListContainer: React.StatelessComponent = observer((): JSX.Element | null => { + const store = Store.getInstance(); + const project = store.getCurrentProject(); + const currentPage = store.getCurrentPageRef(); + const currentPageId = currentPage ? currentPage.getId() : undefined; - @MobX.action - protected generateInitialFocusList(bool: boolean): boolean[] { - const pages = this.getPages(); - const states: boolean[] = []; - pages.forEach((page: PageRef) => { - states.push(bool); - }); - return states; - } - protected getPages(): PageRef[] { - const project: Project | undefined = Store.getInstance().getCurrentProject(); - return project ? project.getPages() : []; - } - - @MobX.action - protected handleClick(e: React.MouseEvent, i: number): void { - if (this.focusStates[i]) { - return; - } - this.focusStates.forEach((state, index) => { - this.focusStates[index] = false; - }); - this.focusStates[i] = !this.focusStates[i]; + if (!project) { + return null; } - public render(): JSX.Element { - return ( - - ); - } -} + return ( + + {project + .getPages() + .map((pageRef: PageRef, i: number) => ( + + ))} + + ); +}); diff --git a/src/component/page-list/page-list-preview.tsx b/src/component/page-list/page-list-preview.tsx index 061748e11..5e8f8c1a4 100644 --- a/src/component/page-list/page-list-preview.tsx +++ b/src/component/page-list/page-list-preview.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { colors } from '../../lsg/patterns/colors'; import Copy from '../../lsg/patterns/copy'; import { Headline } from '../../lsg/patterns/headline'; -import Space, { Size } from '../../lsg/patterns/space'; +import Space, { SpaceSize } from '../../lsg/patterns/space'; import { Store } from '../../store/store'; export const PageListPreview: React.StatelessComponent = props => { @@ -13,8 +13,8 @@ export const PageListPreview: React.StatelessComponent = props => { } const dateString = new Intl.DateTimeFormat().format(project.getLastChangedDate()); return ( - - + + {project.getName()} diff --git a/src/component/page-list/page-tile-container.tsx b/src/component/page-list/page-tile-container.tsx index 700f2c165..f3acaf4bd 100644 --- a/src/component/page-list/page-tile-container.tsx +++ b/src/component/page-list/page-tile-container.tsx @@ -1,5 +1,5 @@ import { PreviewTile } from '../../lsg/patterns/preview-tile/index'; -import Space, { Size } from '../../lsg/patterns/space/index'; +import Space, { SpaceSize } from '../../lsg/patterns/space/index'; import * as MobX from 'mobx'; import { observer } from 'mobx-react'; import { PageRef } from '../../store/page/page-ref'; @@ -8,7 +8,6 @@ import { Store } from '../../store/store'; export interface PageTileContainerProps { focused: boolean; - onClick: React.MouseEventHandler; page: PageRef; } @@ -16,11 +15,11 @@ export interface PageTileContainerProps { export class PageTileContainer extends React.Component { @MobX.observable public editable: boolean = false; @MobX.observable public inputValue: string = ''; + @MobX.observable public namedPage: boolean = Boolean(this.props.page.getName()); public constructor(props: PageTileContainerProps) { super(props); - this.inputValue = this.inputValue || this.props.page.getName(); - + this.inputValue = this.inputValue || (this.props.page.getName() || 'Unnamed Page'); this.handleBlur = this.handleBlur.bind(this); this.handleChange = this.handleChange.bind(this); this.handleClick = this.handleClick.bind(this); @@ -41,14 +40,18 @@ export class PageTileContainer extends React.Component { @MobX.action protected handleClick(e: React.MouseEvent): void { - this.props.onClick(e); + const store = Store.getInstance(); + store.openPage(this.props.page.getId()); + if (this.props.focused) { this.editable = true; } } protected handleDoubleClick(e: React.MouseEvent): void { - Store.getInstance().openPage(this.props.page.getId()); + const store = Store.getInstance(); + store.togglePageOverview(); + store.openPage(this.props.page.getId()); } @MobX.action @@ -82,7 +85,7 @@ export class PageTileContainer extends React.Component { public render(): JSX.Element { return ( - + { onClick={this.handleClick} onDoubleClick={this.handleDoubleClick} onKeyDown={this.handleKeyDown} + named={this.namedPage} value={this.inputValue} /> diff --git a/src/electron/context-menus.ts b/src/electron/context-menus.ts index 84c052289..dd311393b 100644 --- a/src/electron/context-menus.ts +++ b/src/electron/context-menus.ts @@ -37,7 +37,7 @@ export function elementMenu(element: PageElement): void { const newPageElement = clipboardElement && clipboardElement.clone(); if (newPageElement) { - store.execute(ElementLocationCommand.addSibling(element, newPageElement)); + store.execute(ElementLocationCommand.addSibling(newPageElement, element)); } } }, diff --git a/src/electron/main.ts b/src/electron/main.ts index 39d9d97b5..78bad96a8 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -1,13 +1,32 @@ import { checkForUpdates } from './auto-updater'; +import { colors } from '../lsg/patterns/colors'; import { app, BrowserWindow, ipcMain, screen } from 'electron'; -import * as PathUtils from 'path'; -import * as url from 'url'; +import * as Fs from 'fs'; +import * as getPort from 'get-port'; +import * as stringEscape from 'js-string-escape'; +import { PreviewMessageType, ServerMessage, ServerMessageType } from '../message'; +import * as Path from 'path'; +import { createServer } from './server'; +import * as Url from 'url'; +import * as uuid from 'uuid'; + +const APP_ENTRY = require.resolve('./renderer'); + +const RENDERER_DOCUMENT = ` + + +
+ + +`; + +Fs.writeFileSync(Path.join(__dirname, 'app.html'), RENDERER_DOCUMENT); // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let win: BrowserWindow | undefined; -function createWindow(): void { +async function createWindow(): Promise { const { width = 1280, height = 800 } = screen.getPrimaryDisplay().workAreaSize; // Create the browser window. @@ -17,18 +36,77 @@ function createWindow(): void { minWidth: 780, minHeight: 380, titleBarStyle: 'hiddenInset', + backgroundColor: colors.grey97.toString('hex'), title: 'Alva' }); // and load the index.html of the app. win.loadURL( - url.format({ - pathname: PathUtils.join(__dirname, 'renderer', 'app.html'), + Url.format({ + pathname: Path.join(__dirname, 'app.html'), protocol: 'file:', slashes: true }) ); + // Cast getPort return type from PromiseLike to Promise + // to avoid async-promise tslint rule to produce errors here + const port = await (getPort({ port: 1879 }) as Promise); + const server = await createServer({ port }); + + // tslint:disable-next-line:no-any + const send = (message: ServerMessage) => { + if (win) { + win.webContents.send('message', message); + } + }; + + // tslint:disable-next-line:no-any + ipcMain.on('message', (e: Electron.Event, payload: any) => { + if (!payload) { + return; + } + + server.emit('message', payload); + + switch (payload.type) { + case ServerMessageType.AppLoaded: { + send({ + id: uuid.v4(), + type: ServerMessageType.StartApp, + payload: String(port) + }); + } + } + }); + + server.on('client-message', (envelope: string) => { + try { + const message = JSON.parse(envelope); + + switch (message.type) { + case PreviewMessageType.ContentResponse: { + send({ + id: message.id, + payload: message.payload, + type: ServerMessageType.ContentResponse + }); + break; + } + case PreviewMessageType.SketchExportResponse: { + send({ + id: message.id, + payload: message.payload, + type: ServerMessageType.SketchExportResponse + }); + } + } + } catch (err) { + console.error('Error while receiving client message'); + console.error(err); + } + }); + // Open the DevTools. // win.webContents.openDevTools(); @@ -69,8 +147,8 @@ log.info('App starting...'); // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. -app.on('ready', () => { - createWindow(); +app.on('ready', async () => { + await createWindow(); }); // Quit when all windows are closed. @@ -82,11 +160,11 @@ app.on('window-all-closed', () => { } }); -app.on('activate', () => { +app.on('activate', async () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (!win) { - createWindow(); + await createWindow(); } }); @@ -95,16 +173,3 @@ ipcMain.on('request-check-for-updates', () => { checkForUpdates(win, true); } }); - -ipcMain.on('preview-ready', () => { - BrowserWindow.getAllWindows().forEach(window => { - window.webContents.send('preview-ready'); - }); -}); - -// tslint:disable-next-line:no-any -ipcMain.on('export-as-sketch-done', (_: Event, payload: any) => { - BrowserWindow.getAllWindows().forEach(window => { - window.webContents.send('export-as-sketch-done', payload); - }); -}); diff --git a/src/electron/menu.ts b/src/electron/menu.ts index e057ef34a..481b0bef1 100644 --- a/src/electron/menu.ts +++ b/src/electron/menu.ts @@ -1,19 +1,12 @@ -import { - BrowserWindow, - ipcRenderer, - MenuItem, - MenuItemConstructorOptions, - remote, - WebviewTag -} from 'electron'; +import { BrowserWindow, ipcRenderer, MenuItem, MenuItemConstructorOptions, remote } from 'electron'; import { ElementLocationCommand } from '../store/command/element-location-command'; -import * as FileExtraUtils from 'fs-extra'; +import * as FsExtra from 'fs-extra'; import { Page } from '../store/page/page'; import { PageElement } from '../store/page/page-element'; -import * as PathUtils from 'path'; +import * as Path from 'path'; import { PdfExporter } from '../export/pdf-exporter'; import { PngExporter } from '../export/png-exporter'; -import * as ProcessUtils from 'process'; +import * as Process from 'process'; import { SketchExporter } from '../export/sketch-exporter'; import { Store } from '../store/store'; const { Menu, shell, app, dialog } = remote; @@ -55,10 +48,10 @@ export function createMenu(): void { click: () => { let appPath: string = app.getAppPath().replace('.asar', '.asar.unpacked'); if (appPath.indexOf('node_modules') >= 0) { - appPath = ProcessUtils.cwd(); + appPath = Process.cwd(); } - const designkitPath = PathUtils.join(appPath, 'build', 'designkit'); + const designkitPath = Path.join(appPath, 'build', 'designkit'); dialog.showOpenDialog( { properties: ['openDirectory', 'createDirectory'] }, filePaths => { @@ -66,10 +59,7 @@ export function createMenu(): void { return; } - FileExtraUtils.copySync( - designkitPath, - PathUtils.join(filePaths[0], 'designkit') - ); + FsExtra.copySync(designkitPath, Path.join(filePaths[0], 'designkit')); store.openStyleguide(`${filePaths[0]}/designkit`); store.openFirstPage(); } @@ -134,9 +124,8 @@ export function createMenu(): void { }); if (path) { - const webview = document.getElementById('preview') as WebviewTag; const sketchExporter = new SketchExporter(); - await sketchExporter.createExport(webview); + await sketchExporter.createExport(); await sketchExporter.writeToDisk(path); } } @@ -153,9 +142,8 @@ export function createMenu(): void { }); if (path) { - const webview = document.getElementById('preview') as WebviewTag; const pdfExporter = new PdfExporter(); - await pdfExporter.createExport(webview); + await pdfExporter.createExport(); await pdfExporter.writeToDisk(path); } } @@ -172,9 +160,8 @@ export function createMenu(): void { }); if (path) { - const webview = document.getElementById('preview') as WebviewTag; const pngExporter = new PngExporter(); - await pngExporter.createExport(webview); + await pngExporter.createExport(); await pngExporter.writeToDisk(path); } } @@ -278,7 +265,7 @@ export function createMenu(): void { if (selectedElement && store.isElementFocussed()) { const newPageElement = selectedElement.clone(); store.execute( - ElementLocationCommand.addSibling(selectedElement, newPageElement) + ElementLocationCommand.addSibling(newPageElement, selectedElement) ); store.setSelectedElement(newPageElement); } @@ -306,7 +293,12 @@ export function createMenu(): void { } })(), click: () => { + if (store.getSelectedSlotId()) { + return; + } + const selectedElement: PageElement | undefined = store.getSelectedElement(); + if (selectedElement) { store.execute(ElementLocationCommand.remove(selectedElement)); store.setSelectedElement(undefined); diff --git a/src/electron/renderer.ts b/src/electron/renderer.ts new file mode 100644 index 000000000..4e5d963bc --- /dev/null +++ b/src/electron/renderer.ts @@ -0,0 +1,79 @@ +import { App } from '../component/container/app'; +import { ipcRenderer, webFrame } from 'electron'; +import * as MobX from 'mobx'; +import * as React from 'react'; +import * as ReactDom from 'react-dom'; +import { Store } from '../store/store'; + +// prevent app zooming +webFrame.setVisualZoomLevelLimits(1, 1); +webFrame.setLayoutZoomLevelLimits(0, 0); + +const store = Store.getInstance(); +store.openFromPreferences(); + +ipcRenderer.send('message', { type: 'app-loaded' }); + +// tslint:disable-next-line:no-any +ipcRenderer.on('message', (e: Electron.Event, message: any) => { + if (!message) { + return; + } + switch (message.type) { + case 'start-app': { + store.setServerPort(message.payload); + } + } +}); + +MobX.autorun(() => { + const styleguide = store.getStyleguide(); + + if (styleguide) { + ipcRenderer.send('message', { + type: 'styleguide-change', + payload: { + analyzerName: store.getAnalyzerName(), + styleguidePath: styleguide.getPath(), + patternsPath: styleguide.getPatternsPath() + } + }); + } +}); + +MobX.autorun(() => { + const page = store.getCurrentPage(); + + if (page) { + ipcRenderer.send('message', { + type: 'page-change', + payload: page.toJsonObject({ forRendering: true }) + }); + } +}); + +MobX.autorun(() => { + const selectedElement = store.getSelectedElement(); + ipcRenderer.send('message', { + type: 'element-change', + payload: selectedElement ? selectedElement.getId() : undefined + }); +}); + +ReactDom.render(React.createElement(App), document.getElementById('app')); + +// Disable drag and drop from outside the application +document.addEventListener( + 'dragover', + event => { + event.preventDefault(); + }, + false +); +document.addEventListener( + 'drop', + event => { + event.preventDefault(); + }, + false +); diff --git a/src/electron/renderer/app.html b/src/electron/renderer/app.html deleted file mode 100644 index e4519f756..000000000 --- a/src/electron/renderer/app.html +++ /dev/null @@ -1,8 +0,0 @@ - - - -
- - - - diff --git a/src/electron/renderer/app.ts b/src/electron/renderer/app.ts deleted file mode 100644 index c05e4899e..000000000 --- a/src/electron/renderer/app.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { App } from '../../component/container/app'; -import { ipcRenderer, webFrame, WebviewTag } from 'electron'; -import { JsonObject } from '../../store/json'; -import * as MobX from 'mobx'; -import { Page } from '../../store/page/page'; -import * as React from 'react'; -import * as ReactDom from 'react-dom'; -import { Store } from '../../store/store'; - -// prevent app zooming -webFrame.setVisualZoomLevelLimits(1, 1); -webFrame.setLayoutZoomLevelLimits(0, 0); - -const store: Store = Store.getInstance(); -store.openFromPreferences(); - -ipcRenderer.on('preview-ready', (readyEvent: {}, readyMessage: JsonObject) => { - function sendWebViewMessage(message: JsonObject, channel: string): void { - const webviewTag: WebviewTag = document.getElementById('preview') as WebviewTag; - if (webviewTag && webviewTag.send) { - webviewTag.send(channel, message); - } - } - - global.setTimeout(() => { - MobX.autorun(() => { - const styleguide = store.getStyleguide(); - const message: JsonObject = { - analyzerName: store.getAnalyzerName(), - projects: store.getProjects().map(project => project.toJsonObject()), - styleguidePath: styleguide ? styleguide.getPath() : undefined - }; - - sendWebViewMessage(message, 'styleguide-change'); - }); - - MobX.autorun(() => { - const page: Page | undefined = store.getCurrentPage(); - const message: JsonObject = { - page: page ? page.toJsonObject() : undefined, - pageId: page ? page.getId() : undefined - }; - - sendWebViewMessage(message, 'page-change'); - }); - - MobX.autorun(() => { - const selectedElement = store.getSelectedElement(); - const message: JsonObject = { - selectedElementId: selectedElement ? selectedElement.getId() : undefined - }; - sendWebViewMessage(message, 'selectedElement-change'); - }); - }, 3000); -}); - -ReactDom.render(React.createElement(App), document.getElementById('app')); - -// Disable drag and drop from outside the application -document.addEventListener( - 'dragover', - event => { - event.preventDefault(); - }, - false -); -document.addEventListener( - 'drop', - event => { - event.preventDefault(); - }, - false -); diff --git a/src/electron/renderer/preview.ts b/src/electron/renderer/preview.ts deleted file mode 100644 index c8f553633..000000000 --- a/src/electron/renderer/preview.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { ipcRenderer } from 'electron'; -import { JsonObject } from '../../store/json'; -import { Page } from '../../store/page/page'; -import { SketchExporter } from '../../export/sketch-exporter'; -import * as SmoothscrollPolyfill from 'smoothscroll-polyfill'; -import { Store } from '../../store/store'; - -window.onload = () => { - SmoothscrollPolyfill.polyfill(); -}; - -const store = Store.getInstance(); - -ipcRenderer.on('styleguide-change', (event: {}, message: JsonObject) => { - store.setStyleguideFromJsonInternal(message); -}); - -ipcRenderer.on('page-change', (event: {}, message: JsonObject) => { - store.setPageFromJsonInternal(message); - - const styleguide = store.getStyleguide(); - const analyzer = styleguide ? styleguide.getAnalyzer() : undefined; - if (analyzer) { - analyzer.render(); - } -}); - -ipcRenderer.on('selectedElement-change', (event: {}, message: JsonObject) => { - const page = store.getCurrentPage() as Page; - store.setSelectedElement(page.getElementById(message.selectedElementId as string)); -}); - -ipcRenderer.on('export-as-sketch', (event: Electron.Event, id: string) => { - try { - const contents = SketchExporter.createSketchExport(); - ipcRenderer.send('export-as-sketch-done', { id, contents }); - } catch (error) { - ipcRenderer.send('export-as-sketch-done', { id, error }); - } -}); - -ipcRenderer.send('preview-ready'); - -// Disable drag and drop from outside the application -document.addEventListener( - 'dragover', - event => { - event.preventDefault(); - }, - false -); -document.addEventListener( - 'drop', - event => { - event.preventDefault(); - }, - false -); diff --git a/src/electron/server.ts b/src/electron/server.ts new file mode 100644 index 000000000..01a93bce1 --- /dev/null +++ b/src/electron/server.ts @@ -0,0 +1,315 @@ +import { EventEmitter } from 'events'; +import * as express from 'express'; +import * as Http from 'http'; +import { ServerMessageType } from '../message'; +import * as Path from 'path'; +import { patternIdToWebpackName } from '../preview/pattern-id-to-webpack-name'; +import { previewDocument } from '../preview/preview-document'; +import * as QueryString from 'query-string'; +import { Store } from '../store/store'; +import { Styleguide } from '../store/styleguide/styleguide'; +import * as uuid from 'uuid'; +import * as webpack from 'webpack'; +import { OPEN, Server as WebsocketServer } from 'ws'; + +// memory-fs typings on @types are faulty +const MemoryFs = require('memory-fs'); + +const PREVIEW_PATH = require.resolve('../preview/preview'); +const LOADER_PATH = require.resolve('../preview/components-loader'); +const RENDERER_PATH = require.resolve('../preview/preview-renderer'); + +export interface ServerOptions { + port: number; +} + +interface StyleguidePattern { + [key: string]: string; +} + +interface State { + id: string; + payload: { + elementId?: string; + // tslint:disable-next-line:no-any + page?: any; + }; + type: 'state'; +} + +enum WebpackMessageType { + Start = 'start', + Done = 'done', + Error = 'error' +} + +interface WebpackMessage { + id: string; + payload?: object; + type: WebpackMessageType; +} + +// tslint:disable-next-line:no-any +type Queue = WebpackMessage[]; + +export async function createServer(opts: ServerOptions): Promise { + const store = Store.getInstance(); + store.openFromPreferences(); + + const emitter = new EventEmitter(); + const app = express(); + + const server = Http.createServer(app); + const wss = new WebsocketServer({ server }); + + const state: State = { + id: uuid.v4(), + type: 'state', + payload: {} + }; + + // tslint:disable-next-line:no-any + const compilation: any = { + path: '', + queue: [], + listeners: [] + }; + + // Prevent client errors (frequently caused by Chrome disconnecting on reload) + // from bubbling up and making the server fail, ref: https://github.com/websockets/ws/issues/1256 + wss.on('connection', ws => { + ws.on('error', err => { + console.error(err); + }); + + ws.on('message', message => emitter.emit('client-message', message)); + ws.send(JSON.stringify(state)); + }); + + app.get('/preview.html', (req, res) => { + res.type('html'); + res.send(previewDocument); + }); + + app.use('/scripts', (req, res, next) => { + const [current] = compilation.queue; + + if (!current) { + next(); + return; + } + + // tslint:disable-next-line:no-any + const onReady = (fs: any): void => { + compilation.listeners = compilation.listeners.filter(l => l !== onReady); + + try { + res.type('js'); + res.send(fs.readFileSync(req.path)); + } catch (err) { + if (err.code === 'ENOENT') { + res.sendStatus(404); + return; + } + res.sendStatus(500); + } + }; + + if (current.type === 'start') { + compilation.listeners.push(onReady); + } else { + onReady(compilation.compiler.outputFileSystem); + } + }); + + // tslint:disable-next-line:no-any + const send = (message: any): void => { + wss.clients.forEach(client => { + if (client.readyState === OPEN) { + client.send(JSON.stringify(message)); + } + }); + }; + + // tslint:disable-next-line:no-any + emitter.on('message', async (message: any) => { + switch (message.type) { + case ServerMessageType.StyleGuideChange: { + const { payload } = message; + if (compilation.path !== payload.styleguidePath) { + if (compilation.compiler && typeof compilation.compiler.close === 'function') { + compilation.compiler.close(); + } + + send({ + type: 'reload', + id: uuid.v4(), + payload: {} + }); + + state.id = uuid.v4(); + state.payload = {}; + const next = await setup({ + analyzerName: payload.analyzerName, + styleguidePath: payload.styleguidePath, + patternsPath: payload.patternsPath + }); + compilation.path = payload.styleguidePath; + compilation.compiler = next.compiler; + compilation.queue = next.queue; + compilation.listeners = []; + + next.compiler.hooks.watchRun.tap('alva', () => { + send({ + type: 'update', + id: uuid.v4(), + payload: {} + }); + }); + + next.compiler.hooks.done.tap('alva', stats => { + compilation.listeners.forEach(l => l(compilation.compiler.outputFileSystem)); + }); + } + break; + } + case ServerMessageType.PageChange: { + state.payload.page = message.payload; + send(state); + break; + } + case ServerMessageType.ElementChange: { + state.payload.elementId = message.payload; + send(message); + break; + } + case ServerMessageType.BundleChange: { + send({ + type: 'reload', + id: uuid.v4(), + payload: {} + }); + break; + } + case ServerMessageType.AppLoaded: { + break; + } + case ServerMessageType.SketchExportRequest: + case ServerMessageType.ContentRequest: { + send(message); + break; + } + default: { + console.warn(`Unknown message type: ${message.type}`); + } + } + }); + + await startServer({ + server, + port: opts.port + }); + + return emitter; +} + +interface ServerStartOptions { + port: number; + server: Http.Server; +} + +// tslint:disable-next-line:promise-function-async +function startServer(options: ServerStartOptions): Promise { + return new Promise((resolve, reject) => { + options.server.once('error', reject); + options.server.listen(options.port, resolve); + }); +} + +// tslint:disable-next-line:no-any +async function setup(update: any): Promise { + const queue: Queue = []; + const init: StyleguidePattern = {}; + + const styleguide = new Styleguide( + update.styleguidePath, + update.patternsPath, + update.analyzerName + ); + + const context = styleguide.getPath(); + + const components = styleguide.getPatterns().reduce((componentMap, pattern) => { + const patternPath = pattern.getImplementationPath(); + + if (!patternPath) { + return componentMap; + } + + componentMap[patternIdToWebpackName(pattern.getId())] = `./${Path.relative( + context, + patternPath + ) + .split(Path.sep) + .join('/')}`; + return componentMap; + }, init); + + const compiler = webpack({ + mode: 'development', + context, + entry: { + components: `${LOADER_PATH}?${QueryString.stringify({ + cwd: context, + components: JSON.stringify(components) + })}!`, + renderer: RENDERER_PATH, + preview: PREVIEW_PATH + }, + output: { + filename: '[name].js', + library: '[name]', + libraryTarget: 'window', + path: '/' + }, + optimization: { + splitChunks: { + cacheGroups: { + vendor: { + chunks: 'initial', + name: 'vendor', + test: /node_modules/, + priority: 10, + enforce: true + } + } + } + }, + plugins: [new webpack.HotModuleReplacementPlugin()] + }); + + compiler.outputFileSystem = new MemoryFs(); + + compiler.hooks.compile.tap('alva', () => { + queue.unshift({ type: WebpackMessageType.Start, id: uuid.v4() }); + }); + + compiler.hooks.done.tap('alva', stats => { + if (stats.hasErrors()) { + queue.unshift({ + type: WebpackMessageType.Error, + payload: stats.toJson('errors-only'), + id: uuid.v4() + }); + } + queue.unshift({ type: WebpackMessageType.Done, id: uuid.v4() }); + }); + + // tslint:disable-next-line:no-empty + compiler.watch({}, (err, stats) => {}); + + return { + compiler, + queue + }; +} diff --git a/src/export/exporter.ts b/src/export/exporter.ts index e76a9ac1c..dc8509e2c 100644 --- a/src/export/exporter.ts +++ b/src/export/exporter.ts @@ -1,8 +1,8 @@ import { WebviewTag } from 'electron'; -import * as FsUtils from 'fs'; +import * as Fs from 'fs'; import * as Util from 'util'; -const writeFile = Util.promisify(FsUtils.writeFile); +const writeFile = Util.promisify(Fs.writeFile); export interface ExportResult { error?: Error; diff --git a/src/export/pdf-exporter.ts b/src/export/pdf-exporter.ts index 246f84c73..9b12572db 100644 --- a/src/export/pdf-exporter.ts +++ b/src/export/pdf-exporter.ts @@ -1,26 +1,108 @@ -import { WebviewTag } from 'electron'; +// import { WebviewTag } from 'electron'; +import { ipcRenderer } from 'electron'; import { Exporter, ExportResult } from './exporter'; +import { ServerMessageType } from '../message'; +import { Store } from '../store/store'; +import * as Url from 'url'; +import * as uuid from 'uuid'; export class PdfExporter extends Exporter { - public async createExport(webview: WebviewTag): Promise { + public async createExport(): Promise { return new Promise(resolve => { - webview.printToPDF( - { - marginsType: 1, - pageSize: 'A4', - printBackground: true, - printSelectionOnly: false, - landscape: false - }, - (error: Error, data: Buffer) => { - if (error) { - resolve({ error }); - return; + const id = uuid.v4(); + const initial = 'data:text/html;'; + + const webview = document.createElement('webview'); + webview.style.position = 'fixed'; + webview.style.top = '100vh'; + webview.src = initial; + webview.webpreferences = 'useContentSize=yes, javascript=no'; + document.body.insertBefore(webview, document.body.firstChild); + + const store = Store.getInstance(); + let started; + + // (1) Request HTML contents from preview + const start = () => { + ipcRenderer.send('message', { + type: ServerMessageType.ContentRequest, + id + }); + }; + + // (2) Receive HTML response from preview and load into webview + // tslint:disable-next-line:no-any + const receive = (_, message: any) => { + if (message.type !== ServerMessageType.ContentResponse || message.id !== id) { + return; + } + + const payload = message.payload; + + const parsed = Url.parse(payload.location); + + if (parsed.host !== `localhost:${store.getServerPort()}`) { + return; + } + + webview.style.width = `${payload.width}px`; + webview.style.height = `${payload.height}px`; + + webview.loadURL( + `data:text/html;charset=utf-8,${encodeURIComponent(payload.document)}`, + { + baseURLForDataURL: payload.location } - this.contents = data; - resolve({ result: this.contents }); + ); + }; + + // (3) Wait for webview to be ready and capture the page + const createPdf = () => { + if (started !== id) { + return; } - ); + + webview.printToPDF( + { + marginsType: 1, + pageSize: 'A4', + printBackground: true, + printSelectionOnly: false, + landscape: false + }, + (error: Error, data: Buffer) => { + if (error) { + resolve({ error }); + return; + } + + this.contents = data; + resolve({ result: this.contents }); + + setTimeout(() => { + document.body.removeChild(webview); + }); + } + ); + }; + + webview.addEventListener('did-finish-load', () => { + if (webview.src === initial) { + return; + } + createPdf(); + }); + + ipcRenderer.on('message', receive); + + webview.addEventListener('dom-ready', () => { + if (started === id) { + return; + } + + started = id; + start(); + }); }); } } diff --git a/src/export/png-exporter.ts b/src/export/png-exporter.ts index 29cdc6444..9bfab160e 100644 --- a/src/export/png-exporter.ts +++ b/src/export/png-exporter.ts @@ -1,55 +1,115 @@ -import { remote, WebviewTag } from 'electron'; +import { ipcRenderer, remote } from 'electron'; import { Exporter, ExportResult } from './exporter'; - -const CODE = ` -bodyTag = document.querySelector('body'); -({ - pageHeight: bodyTag.getBoundingClientRect().height, - pageWidth: bodyTag.getBoundingClientRect().width -}); -`; +import { ServerMessageType } from '../message'; +import { Store } from '../store/store'; +import * as Url from 'url'; +import * as uuid from 'uuid'; export class PngExporter extends Exporter { - public async createExport(webview: WebviewTag): Promise { + public async createExport(): Promise { try { - this.contents = await this.createPngExport(webview); + this.contents = await this.createPngExport(); return { result: this.contents }; } catch (error) { return { error }; } } - private async createPngExport(webview: WebviewTag): Promise { + private async createPngExport(): Promise { return new Promise(resolve => { - webview.executeJavaScript(CODE, false, webviewSize => { - // set the height of the webview tag to the preview body height - // This is needed because capturePage can not capture anything that renders - // outside the webview area (https://github.com/electron/electron/issues/9845) - webview.style.height = webviewSize.pageHeight; - - // Delay the page capture to make sure that the style height changes are done. - // This is only needed because of the change in height in the above line - setTimeout(() => { - const scaleFactor = remote.screen.getPrimaryDisplay().scaleFactor; - webview.capturePage( - { - x: 0, - y: 0, - // round the numbers to remove possible floating numbers - // also multiply by scaleFactor for devices with higher pixel ratio: - // https://github.com/electron/electron/issues/8314 - width: Math.round(webviewSize.pageWidth * scaleFactor), - height: Math.round(webviewSize.pageHeight * scaleFactor) - }, - capture => { - const pngBuffer: Buffer = capture.toPNG(); - resolve(pngBuffer); - - // reset the webview height - webview.style.height = '100%'; - } - ); - }, 100); + const id = uuid.v4(); + const initial = 'data:text/html;'; + const webview = document.createElement('webview'); + webview.style.position = 'fixed'; + webview.style.top = '100vh'; + webview.src = initial; + webview.webpreferences = 'useContentSize=yes, javascript=no'; + document.body.insertBefore(webview, document.body.firstChild); + + const store = Store.getInstance(); + const scaleFactor = remote.screen.getPrimaryDisplay().scaleFactor; + + let config; + let started; + + // (1) Request HTML contents from preview + const start = () => { + ipcRenderer.send('message', { + type: ServerMessageType.ContentRequest, + id + }); + }; + + // (2) Receive HTML response from preview and load into webview + // tslint:disable-next-line:no-any + const receive = (_, message: any) => { + if (message.type !== ServerMessageType.ContentResponse || message.id !== id) { + return; + } + + const payload = message.payload; + const parsed = Url.parse(payload.location); + + if (parsed.host !== `localhost:${store.getServerPort()}`) { + return; + } + + webview.style.width = `${payload.width}px`; + webview.style.height = `${payload.height}px`; + + config = payload; + + webview.loadURL( + `data:text/html;charset=utf-8,${encodeURIComponent(payload.document)}`, + { + baseURLForDataURL: payload.location + } + ); + }; + + // (3) Wait for webview to be ready and capture the page + const createPng = () => { + if (!config) { + return; + } + + webview.capturePage( + { + x: 0, + y: 0, + // round the numbers to remove possible floating numbers + // also multiply by scaleFactor for devices with higher pixel ratio: + // https://github.com/electron/electron/issues/8314 + width: Math.round(config.width * scaleFactor), + height: Math.round(config.height * scaleFactor) + }, + capture => { + const pngBuffer: Buffer = capture.toPNG(); + resolve(pngBuffer); + + setTimeout(() => { + document.body.removeChild(webview); + }); + } + ); + }; + + webview.addEventListener('did-finish-load', () => { + if (webview.src === initial) { + return; + } + createPng(); + }); + + ipcRenderer.on('message', receive); + + webview.addEventListener('dom-ready', () => { + if (started === id) { + return; + } + + started = id; + start(); }); }); } diff --git a/src/export/sketch-exporter.ts b/src/export/sketch-exporter.ts index 2fafcc5ba..47fe82b0a 100644 --- a/src/export/sketch-exporter.ts +++ b/src/export/sketch-exporter.ts @@ -1,104 +1,43 @@ -import * as HtmlSketchApp from '@brainly/html-sketchapp'; -import { ipcRenderer, WebviewTag } from 'electron'; +import { ipcRenderer } from 'electron'; import { Exporter, ExportResult } from './exporter'; +import { ServerMessageType } from '../message'; import { Page } from '../store/page/page'; import { Store } from '../store/store'; import * as uuid from 'uuid'; -type Message = SuccessMessage | ErrorMessage; - -interface SuccessMessage { - contents: string; - id: string; -} - -interface ErrorMessage { - error: Error; - id: string; -} - export class SketchExporter extends Exporter { - private id: string; - - public constructor() { - super(); - this.id = uuid.v4(); - } - - public static createSketchExport(): string { - const element = document.querySelector('#preview > div > div:nth-child(1)') as HTMLElement; - - const page = Store.getInstance().getCurrentPage() as Page; - const pageName = page.getName(); - const projectName = page.getName(); - - const sketchPage = HtmlSketchApp.nodeTreeToSketchPage(element, { - pageName: projectName, - addArtboard: true, - artboardName: pageName, - getGroupName: node => - node.getAttribute('data-sketch-name') || `(${node.nodeName.toLowerCase()})`, - getRectangleName: () => 'background', - skipSystemFonts: true - }); - - return JSON.stringify(sketchPage.toJSON(), null, '\t'); - } - - public async createExport(webview: WebviewTag): Promise { + public async createExport(): Promise { return new Promise((resolve, reject) => { - // tslint:disable-next-line no-any - const onMessage = (_: Event, payload: any) => { - if (!this.isMessage(payload)) { - return; - } + const id = uuid.v4(); + const page = Store.getInstance().getCurrentPage() as Page; + const artboardName = page.getName(); + const pageName = page.getName(); + + // (1) request asketch.json from preview + const start = () => { + ipcRenderer.send('message', { + type: ServerMessageType.SketchExportRequest, + id, + payload: { + artboardName, + pageName + } + }); + }; - if (payload.id !== this.id) { + // tslint:disable-next-line:no-any + const receive = (_, message: any) => { + if (message.type !== ServerMessageType.SketchExportResponse || message.id !== id) { return; } - ipcRenderer.removeListener('export-as-sketch-done', onMessage); - - if (this.isError(payload)) { - resolve({ error: payload.error }); - } else { - this.contents = Buffer.from(payload.contents); - resolve({ result: this.contents }); - } + this.contents = Buffer.from(JSON.stringify(message.payload.page, null, '\t')); + resolve({ result: this.contents }); + return; }; - ipcRenderer.on('export-as-sketch-done', onMessage); - webview.send('export-as-sketch', this.id); + ipcRenderer.on('message', receive); + start(); }); } - - // tslint:disable-next-line no-any - private isError(message: any): message is ErrorMessage { - if (!this.isMessage(message)) { - return false; - } - - if (!message.hasOwnProperty('error')) { - return false; - } - - return true; - } - - // tslint:disable-next-line no-any - private isMessage(message: any): message is Message { - if (typeof message !== 'object') { - return false; - } - - if (!message.hasOwnProperty('id')) { - return false; - } - - if (!message.hasOwnProperty('contents') && !message.hasOwnProperty('error')) { - return false; - } - - return true; - } } diff --git a/src/lsg/patterns/add-button/index.tsx b/src/lsg/patterns/add-button/index.tsx new file mode 100644 index 000000000..1b353d068 --- /dev/null +++ b/src/lsg/patterns/add-button/index.tsx @@ -0,0 +1,86 @@ +import { colors } from '../colors'; +import { Icon, IconName, IconSize } from '../icons'; +import * as React from 'react'; +import { getSpace, SpaceSize } from '../space'; +import styled from 'styled-components'; + +export interface AddButtonProps { + onClick?: React.MouseEventHandler; + label?: string; + active?: boolean; +} + +interface StyledAddButtonProps { + active?: boolean; +} + +interface StyledIconProps { + active?: boolean; +} + +const StyledAddButton = styled.div` + width: 100%; + border-top: 1px solid ${colors.black.toString('rgb', { alpha: 0.1 })}; + @media screen and (-webkit-min-device-pixel-ratio: 2) { + border-top-width: 0.5px; + } + cursor: default; + user-select: none; + display: flex; + flex: none; + height: 40px; + box-sizing: border-box; + padding: ${getSpace(SpaceSize.XXS)}px ${getSpace(SpaceSize.XS)}px ${getSpace(SpaceSize.XXS)}px + ${getSpace(SpaceSize.L)}px; + justify-content: space-between; + color: ${colors.grey36.toString()}; + + &:hover { + background: ${colors.grey90.toString()}; + } + + ${(props: StyledAddButtonProps) => + props.active + ? ` + border-top: 1px solid ${colors.blue.toString('rgb', { alpha: 0.1 })}; + @media screen and (-webkit-min-device-pixel-ratio: 2) { + border-top-width: 0.5px; + } + background: ${colors.blue80.toString()}; + color: ${colors.blue.toString()}; + + &:hover { + background: ${colors.blue80.toString()}; + } + ` + : ''}; +`; + +const StyledLabelWrapper = styled.div` + font-size: 15px; + padding-top: ${getSpace(SpaceSize.XS)}px; +`; + +const StyledIconWrapper = styled.div` + margin: ${getSpace(SpaceSize.XS)}px; + padding: ${getSpace(SpaceSize.XXS)}px; + border-radius: ${getSpace(SpaceSize.XXS)}px; +`; + +const StyledIcon = styled(Icon)` + fill: ${colors.grey60.toString()}; + + ${(props: StyledIconProps) => (props.active ? `fill: ${colors.blue.toString()};` : '')}; +`; + +const AddButton: React.StatelessComponent = props => ( + + {props.label} + + + + + +); + +export default AddButton; diff --git a/src/lsg/patterns/add-button/pattern.json b/src/lsg/patterns/add-button/pattern.json new file mode 100644 index 000000000..86d08b971 --- /dev/null +++ b/src/lsg/patterns/add-button/pattern.json @@ -0,0 +1,6 @@ +{ + "name": "add-button", + "displayName": "Add Button", + "version": "1.0.0", + "flag": "alpha" +} diff --git a/src/lsg/patterns/button/demo.tsx b/src/lsg/patterns/button/demo.tsx index 9a1c11f2c..07ee00366 100644 --- a/src/lsg/patterns/button/demo.tsx +++ b/src/lsg/patterns/button/demo.tsx @@ -1,14 +1,14 @@ import DemoContainer from '../demo-container'; import Button, { Order } from './index'; import * as React from 'react'; -import Space, { Size } from '../space'; +import Space, { SpaceSize } from '../space'; const ButtonDemo: React.StatelessComponent = (): JSX.Element => ( - + - + diff --git a/src/lsg/patterns/chrome/demo.tsx b/src/lsg/patterns/chrome/demo.tsx new file mode 100644 index 000000000..d60053cb6 --- /dev/null +++ b/src/lsg/patterns/chrome/demo.tsx @@ -0,0 +1,30 @@ +import { CopySize } from '../copy'; +import { IconName, IconRegistry } from '../icons'; +import Chrome from './index'; +import * as React from 'react'; +import { ViewSwitch } from '../view-switch'; + +const DemoChrome: React.StatelessComponent = () => ( +
+ + null} + onRightClick={() => null} + leftVisible={true} + rightVisible={true} + title="Page Title" + /> + null} + onRightClick={() => null} + leftVisible={true} + rightVisible={true} + title="Page Title" + /> + + +
+); + +export default DemoChrome; diff --git a/src/lsg/patterns/chrome/index.tsx b/src/lsg/patterns/chrome/index.tsx index 379c67105..0d3f0ec06 100644 --- a/src/lsg/patterns/chrome/index.tsx +++ b/src/lsg/patterns/chrome/index.tsx @@ -1,8 +1,6 @@ -import { colors } from '../colors'; import { fonts } from '../fonts'; -import { Icon, IconName, Size as IconSize } from '../icons'; import * as React from 'react'; -import { getSpace, Size as SpaceSize } from '../space'; +import { getSpace, SpaceSize } from '../space'; import styled from 'styled-components'; export interface ChromeProps { @@ -17,8 +15,8 @@ const StyledChrome = styled.div` box-sizing: border-box; position: absolute; top: 0; - display: flex; - align-items: center; + display: grid; + grid-template-columns: 33.333% 33.333% 33.333%; width: 100%; height: 54px; padding: ${getSpace(SpaceSize.XS)}px ${getSpace(SpaceSize.XXL) * 3}px; @@ -28,66 +26,8 @@ const StyledChrome = styled.div` user-select: none; `; -const StyledChromeTitle = styled.div` - display: flex; - align-items: center; - margin: 0 auto; - color: ${colors.grey36.toString()}; - font-size: 15px; -`; - -const StyledTitleWrapper = styled.div` - position: relative; - margin: 0 ${getSpace(SpaceSize.XS)}px; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - text-align: center; - width: 130px; - } -`; - -interface StyledIconWrapperProps { - visible: boolean; -} - -const StyledIconWrapper = styled.div` - margin: ${getSpace(SpaceSize.XS)}px; - padding: ${getSpace(SpaceSize.XS)}px; - border-radius: ${getSpace(SpaceSize.XXS)}px; - - &:hover { - background: ${colors.grey90.toString()}; - } - - ${(props: StyledIconWrapperProps) => - props.visible ? 'visibility: visible' : 'visibility: hidden'}; -`; - -const StyledLeftIcon = styled(Icon)` - fill: ${colors.grey60.toString()}; - transform: rotate(180deg); -`; - -const StyledRightIcon = styled(Icon)` - fill: ${colors.grey60.toString()}; -`; - const Chrome: React.StatelessComponent = props => ( - - - - - - - {props.title} - - - - - - {props.children} - + {props.children} ); export default Chrome; diff --git a/src/lsg/patterns/colors/index.tsx b/src/lsg/patterns/colors/index.tsx index df1a1576a..3130251df 100644 --- a/src/lsg/patterns/colors/index.tsx +++ b/src/lsg/patterns/colors/index.tsx @@ -71,6 +71,10 @@ export const colors = { displayName: 'Blue 40', rgb: [102, 169, 230] }), + blue80: new Color({ + displayName: 'Blue 80', + rgb: [212, 226, 242] + }), grey20: new Color({ displayName: 'Grey 20', rgb: [52, 61, 69] @@ -95,6 +99,10 @@ export const colors = { displayName: 'Grey 90', rgb: [229, 230, 231] }), + grey97: new Color({ + displayName: 'Grey 97', + rgb: [247, 247, 247] + }), white: new Color({ displayName: 'White', rgb: [255, 255, 255] diff --git a/src/lsg/patterns/copy/index.tsx b/src/lsg/patterns/copy/index.tsx index 3a0000ab6..17041b2b8 100644 --- a/src/lsg/patterns/copy/index.tsx +++ b/src/lsg/patterns/copy/index.tsx @@ -3,16 +3,16 @@ import * as React from 'react'; import styled from 'styled-components'; export interface CopyProps { - size?: Size; + size?: CopySize; textColor?: Color; } export interface StyledCopyProps { - size?: Size; + size?: CopySize; textColor?: Color; } -export enum Size { +export enum CopySize { S = 12, M = 15 } diff --git a/src/lsg/patterns/dropdown-item/index.tsx b/src/lsg/patterns/dropdown-item/index.tsx index bed5d885b..d9a02d1b7 100644 --- a/src/lsg/patterns/dropdown-item/index.tsx +++ b/src/lsg/patterns/dropdown-item/index.tsx @@ -1,8 +1,8 @@ import { Color, colors } from '../colors'; -import { Icon, IconName, Size as IconSize } from '../icons'; +import { Icon, IconName, IconSize } from '../icons'; import Input, { InputTypes } from '../input'; import * as React from 'react'; -import { getSpace, Size as SpaceSize } from '../space'; +import { getSpace, SpaceSize } from '../space'; import styled from 'styled-components'; export interface DropdownItemProps { diff --git a/src/lsg/patterns/dropdown/index.tsx b/src/lsg/patterns/dropdown/index.tsx index 12dfe2997..d19dab86f 100644 --- a/src/lsg/patterns/dropdown/index.tsx +++ b/src/lsg/patterns/dropdown/index.tsx @@ -1,7 +1,7 @@ import { colors } from '../colors'; -import { Icon, IconName, Size as IconSize } from '../icons'; +import { Icon, IconName, IconSize } from '../icons'; import * as React from 'react'; -import { getSpace, Size as SpaceSize } from '../space'; +import { getSpace, SpaceSize } from '../space'; import styled from 'styled-components'; export interface DropdownProps { diff --git a/src/lsg/patterns/element/demo.tsx b/src/lsg/patterns/element/demo.tsx index ad92988ab..237c46197 100644 --- a/src/lsg/patterns/element/demo.tsx +++ b/src/lsg/patterns/element/demo.tsx @@ -4,8 +4,6 @@ import Element from './index'; import * as React from 'react'; import styled from 'styled-components'; -const NOOP = () => {}; // tslint:disable-line no-empty - const StyledTestDiv = styled.div` flex-grow: 1; max-width: 200px; @@ -16,40 +14,34 @@ const ElementDemo: React.StatelessComponent = (): JSX.Element => ( Default - + Active - + With Child and handleIconClick - { - e.stopPropagation(); - }} - handleIconClick={NOOP} - title="Element" - > + e.stopPropagation()} title="Element" dragging> Child With Child and open - + Child With child and active - + Child With child, active and open - + Child diff --git a/src/lsg/patterns/element/index.tsx b/src/lsg/patterns/element/index.tsx index bce1a9377..ffdfe85f2 100644 --- a/src/lsg/patterns/element/index.tsx +++ b/src/lsg/patterns/element/index.tsx @@ -182,7 +182,7 @@ const Element: React.StatelessComponent = props => { {children && ( ( {reduce(props.names, (name, id) => [ - + ])} @@ -43,9 +43,9 @@ const DemoIcons = (props: DemoIconsProps) => ( const IconRegistryDemo: React.StatelessComponent = (): JSX.Element => ( - - - + + + ); diff --git a/src/lsg/patterns/icons/index.tsx b/src/lsg/patterns/icons/index.tsx index 5a093e685..327d19453 100644 --- a/src/lsg/patterns/icons/index.tsx +++ b/src/lsg/patterns/icons/index.tsx @@ -4,7 +4,8 @@ import styled from 'styled-components'; export enum IconName { Arrow, - ArrowFill, + ArrowFillRight, + ArrowFillLeft, Robo, Pattern } @@ -15,12 +16,12 @@ export interface IconRegistryProps { export interface IconProps { className?: string; color?: Color; - handleClick?: React.MouseEventHandler; + handleClick?: React.MouseEventHandler; name: IconName | null; - size?: Size; + size?: IconSize; } -export enum Size { +export enum IconSize { XXS = 12, XS = 15, S = 24 @@ -29,7 +30,7 @@ export enum Size { interface StyledIconProps { className?: string; iconColor?: Color; - size?: Size; + size?: IconSize; } interface IconRegistrySymbolProps { @@ -40,7 +41,8 @@ const icons: { readonly [key: string]: JSX.Element[][] | JSX.Element[] } = { [IconName.Arrow]: [ [] ], - [IconName.ArrowFill]: [[]], + [IconName.ArrowFillRight]: [[]], + [IconName.ArrowFillLeft]: [[]], [IconName.Robo]: [ [] ], @@ -59,8 +61,8 @@ const StyledIconRegistry = styled.svg` `; const StyledIcon = styled.svg` - width: ${(props: StyledIconProps) => props.size || Size.S}px; - height: ${(props: StyledIconProps) => props.size || Size.S}px; + width: ${(props: StyledIconProps) => props.size || IconSize.S}px; + height: ${(props: StyledIconProps) => props.size || IconSize.S}px; color: ${(props: StyledIconProps) => (props.iconColor ? props.iconColor.toString() : 'inherit')}; fill: currentColor; diff --git a/src/lsg/patterns/input/index.tsx b/src/lsg/patterns/input/index.tsx index d0646b15c..063b39187 100644 --- a/src/lsg/patterns/input/index.tsx +++ b/src/lsg/patterns/input/index.tsx @@ -1,6 +1,6 @@ import { colors } from '../colors'; import * as React from 'react'; -import { getSpace, Size } from '../space'; +import { getSpace, SpaceSize } from '../space'; import styled from 'styled-components'; export interface InputProps { @@ -28,12 +28,12 @@ const StyledInput = styled.input` border: none; background: transparent; - margin: 0 ${getSpace(Size.L)}px; + margin: 0 ${getSpace(SpaceSize.L)}px; font-size: 15px; box-sizing: border-box; display: block; - width: calc(100% - ${getSpace(Size.M) * 2}px); + width: calc(100% - ${getSpace(SpaceSize.M) * 2}px); color: ${colors.black.toString()}; font-weight: 500; diff --git a/src/lsg/patterns/layout/demo.tsx b/src/lsg/patterns/layout/demo.tsx index b3cd97df7..3fb3da39b 100644 --- a/src/lsg/patterns/layout/demo.tsx +++ b/src/lsg/patterns/layout/demo.tsx @@ -26,7 +26,7 @@ const LayoutDemo: React.StatelessComponent = (): JSX.Element => ( Horizontal Horizontal
- + Vertical with margins Vertical with margins Vertical with margins diff --git a/src/lsg/patterns/layout/index.tsx b/src/lsg/patterns/layout/index.tsx index ca7d3938a..213cbaf0c 100644 --- a/src/lsg/patterns/layout/index.tsx +++ b/src/lsg/patterns/layout/index.tsx @@ -1,24 +1,43 @@ +import { colors } from '../colors'; import * as React from 'react'; -import { getSpace, Size } from '../space'; import styled from 'styled-components'; export interface LayoutProps { className?: string; directionVertical?: boolean; - handleClick?: React.MouseEventHandler; - hasPaddings?: boolean; + hasBorder?: boolean; + onClick?: React.MouseEventHandler; + side?: string; } const StyledLayout = styled.div` display: flex; ${(props: LayoutProps) => (props.directionVertical ? 'flex-direction: column;' : '')}; - ${(props: LayoutProps) => (props.hasPaddings ? `padding: 0 ${getSpace(Size.L)}px` : '')}; + ${(props: LayoutProps) => + props.hasBorder && props.side == 'left' + ? ` + border-right: 1px solid ${colors.black.toString('rgb', { alpha: 0.1 })}; + @media screen and (-webkit-min-device-pixel-ratio: 2) { + border-right-width: 0.5px; + } + ` + : ''}; + ${(props: LayoutProps) => + props.hasBorder && props.side == 'right' + ? ` + border-left: 1px solid ${colors.black.toString('rgb', { alpha: 0.1 })}; + @media screen and (-webkit-min-device-pixel-ratio: 2) { + border-left-width: 0.5px; + } + ` + : ''}; `; const StyledMainArea = styled(StyledLayout)` box-sizing: border-box; height: 100vh; - padding-top: 54px; + padding-top: 40px; + -webkit-font-smoothing: antialiased; `; const StyledSideBar = styled(StyledLayout)` @@ -30,7 +49,7 @@ export const MainArea: React.StatelessComponent = props => ( {props.children} @@ -40,7 +59,9 @@ export const SideBar: React.StatelessComponent = props => ( {props.children} @@ -50,8 +71,8 @@ const Layout: React.StatelessComponent = props => ( {props.children} diff --git a/src/lsg/patterns/link/demo.tsx b/src/lsg/patterns/link/demo.tsx index e5190e3d6..c90166904 100644 --- a/src/lsg/patterns/link/demo.tsx +++ b/src/lsg/patterns/link/demo.tsx @@ -1,38 +1,24 @@ import { colors } from '../colors'; import Link from './index'; import * as React from 'react'; -import Space, { Size } from '../space'; +import Space, { SpaceSize } from '../space'; const clickHandler = (event: React.MouseEvent): void => console.log(event); const LinkDemo: React.StatelessComponent = (): JSX.Element => ( - - - - Link - + + + Link - - - Link with color - + + Link with color - - + + Link with clickHandler - - + + Link with uppercase diff --git a/src/lsg/patterns/list/index.tsx b/src/lsg/patterns/list/index.tsx index 5ca97b348..205e0bf8f 100644 --- a/src/lsg/patterns/list/index.tsx +++ b/src/lsg/patterns/list/index.tsx @@ -11,13 +11,13 @@ export interface ListItemProps { active?: boolean; children?: ListItemProps[]; draggable?: boolean; - handleDragDrop?: React.DragEventHandler; - handleDragDropForChild?: React.DragEventHandler; - handleDragStart?: React.DragEventHandler; label?: string; onClick?: React.MouseEventHandler; onContextMenu?: React.MouseEventHandler; - value: string; + onDragDrop?: React.DragEventHandler; + onDragDropForChild?: React.DragEventHandler; + onDragStart?: React.DragEventHandler; + title: string; } interface StyledListItemProps { @@ -44,9 +44,9 @@ const StyledUl = styled.ul` const StyledLi = styled.li` line-height: 25px; list-style: none; - ${(props: StyledListItemProps) => (props.onClick ? 'cursor: pointer;' : '')} ${( - props: StyledListItemProps - ) => (props.active ? 'background: #def' : '')}; + ${(props: StyledListItemProps) => (props.onClick ? 'cursor: pointer;' : '')}; + ${(props: StyledListItemProps) => + props.active ? `background: ${colors.blue80.toString()}` : ''}; `; const StyledLabel = styled.span` diff --git a/src/lsg/patterns/panes/element-pane/index.tsx b/src/lsg/patterns/panes/element-pane/index.tsx index 68839c19a..73fea859d 100644 --- a/src/lsg/patterns/panes/element-pane/index.tsx +++ b/src/lsg/patterns/panes/element-pane/index.tsx @@ -1,28 +1,33 @@ +import { colors } from '../../colors'; import * as React from 'react'; +import { getSpace, SpaceSize } from '../../space'; import styled from 'styled-components'; const StyledElementPane = styled.div` position: relative; - flex-grow: 3; - flex-shrink: 0; - flex-basis: 60%; + flex: 1; overflow: scroll; + padding-top: ${getSpace(SpaceSize.M)}px; + padding-bottom: ${getSpace(SpaceSize.XL)}px; - /*FadeOut*/ &::after { content: ''; position: sticky; bottom: 0; display: block; width: 100%; - height: 40px; - background: linear-gradient(to bottom, - rgba(247, 247, 247, 0) 0%, - rgba(247, 247, 247, 0.5) 15%, - rgba(247, 247, 247, 1) 100%); + height: ${getSpace(SpaceSize.XXXL)}px; + background: linear-gradient( + to bottom, + ${colors.grey97.toString('rgb', { alpha: 0 })}, + ${colors.grey97.toString('rgb', { alpha: 1 })} + ); + z-index: 15; } `; -const ElementPane: React.StatelessComponent = props => {props.children}; +const ElementPane: React.StatelessComponent = props => ( + {props.children} +); export default ElementPane; diff --git a/src/lsg/patterns/panes/patterns-pane/index.tsx b/src/lsg/patterns/panes/patterns-pane/index.tsx index 44d9ed1f1..85d90b094 100644 --- a/src/lsg/patterns/panes/patterns-pane/index.tsx +++ b/src/lsg/patterns/panes/patterns-pane/index.tsx @@ -1,20 +1,18 @@ -import { colors } from '../../colors'; import * as React from 'react'; -import { getSpace, Size } from '../../space'; +import { getSpace, SpaceSize } from '../../space'; import styled from 'styled-components'; const StyledPatternsPane = styled.div` box-sizing: border-box; - flex-grow: 2; - flex-shrink: 0; - flex-basis: 40%; - padding: ${getSpace(Size.M)}px 0; - border-top: 1px solid ${colors.grey90.toString()}; + flex: 1; + padding: ${getSpace(SpaceSize.M)}px; overflow: scroll; - margin-left: -${getSpace(Size.L)}px; - margin-right: -${getSpace(Size.L)}px; + margin-left: -${getSpace(SpaceSize.L)}px; + margin-right: -${getSpace(SpaceSize.L)}px; `; -const PatternsPane: React.StatelessComponent = props => {props.children}; +const PatternsPane: React.StatelessComponent = props => ( + {props.children} +); export default PatternsPane; diff --git a/src/lsg/patterns/panes/preview-pane/index.tsx b/src/lsg/patterns/panes/preview-pane/index.tsx index b104675f7..ee6cac2c8 100644 --- a/src/lsg/patterns/panes/preview-pane/index.tsx +++ b/src/lsg/patterns/panes/preview-pane/index.tsx @@ -4,13 +4,14 @@ import * as React from 'react'; import styled from 'styled-components'; export interface PreviewPaneProps { - handleMouseDownLeft?: React.MouseEventHandler; - handleMouseDownRight?: React.MouseEventHandler; - handleMouseMove?: React.MouseEventHandler; - handleMouseUp?: React.MouseEventHandler; + id?: string; + onMouseDownLeft?: React.MouseEventHandler; + onMouseDownRight?: React.MouseEventHandler; + onMouseMove?: React.MouseEventHandler; + onMouseUp?: React.MouseEventHandler; previewFrame?: string; width?: number; - handlePreviewWidthUpdate?(previewWidth: number): void; + onPreviewWidthUpdate?(previewWidth: number): void; } const StyledPreviewWrapper = styled.div` @@ -21,35 +22,58 @@ const StyledPreviewWrapper = styled.div` `; const StyledPreviewResizer = styled.div` - width: 12px; + position: absolute; + top: 0; + left: 0; + width: 11px; height: 100%; cursor: ew-resize; + background-color: ${colors.blackAlpha13.toString()}; + opacity: 0; + transition: opacity 0.15s ease-in-out; + &::after { content: ''; position: absolute; top: 50%; transform: translateY(-50%); height: 36px; - width: 6px; - margin: 3px; - border-radius: 5px; - background: grey; + width: 3px; + margin: 4px; + border-radius: 2px; + background: ${colors.grey80.toString()}; + } + + &:hover { + opacity: 1; + &::after { + background: ${colors.grey60.toString()}; + } + } + &:active { + opacity: 1; + &::after { + background: ${colors.white.toString()}; + } + } + &:last-of-type { + right: 0; + left: auto; } `; const BaseStyledPreviewPane = styled.div` + position: relative; flex-grow: 1; overflow: hidden; background: ${colors.white.toString()}; - border-radius: 6px 6px 0 0; - box-shadow: 0 3px 9px 0 ${colors.black.toRGBString(0.15)}; `; const StyledPreviewPane = BaseStyledPreviewPane.extend.attrs({ style: (props: PreviewPaneProps) => ({ maxWidth: `${props.width}px` || 'none' }) -}) `${(props: PreviewPaneProps) => ({})}`; +})`${(props: PreviewPaneProps) => ({})}`; export default class PreviewPane extends React.Component { private previewPane: HTMLElement; @@ -65,40 +89,37 @@ export default class PreviewPane extends React.Component { } public render(): JSX.Element { - const { - handleMouseDownLeft, - handleMouseDownRight, - handleMouseMove, - handleMouseUp, - width, - previewFrame - } = this.props; + const props = this.props; return ( (this.previewPane = ref)} - onMouseMove={handleMouseMove} - onMouseUp={handleMouseUp} + onMouseMove={props.onMouseMove} + onMouseUp={props.onMouseUp} > - - ` - }} - /> - + + + + + ); } private updatePreviewWidth(): void { - if (!this.props.handlePreviewWidthUpdate) { + if (!this.props.onPreviewWidthUpdate) { return; } const previewWidth = this.previewPane.offsetWidth; - this.props.handlePreviewWidthUpdate(previewWidth); + this.props.onPreviewWidthUpdate(previewWidth); } } + +const StyledPreviewFrame = styled('iframe')` + width: 100%; + height: 100%; + border: none; + border-radius: 6px 6px 0 0; + overflow: hidden; +`; diff --git a/src/lsg/patterns/panes/property-pane/index.tsx b/src/lsg/patterns/panes/property-pane/index.tsx index ecebd737b..f4acd37bc 100644 --- a/src/lsg/patterns/panes/property-pane/index.tsx +++ b/src/lsg/patterns/panes/property-pane/index.tsx @@ -1,12 +1,17 @@ import * as React from 'react'; +import { getSpace, SpaceSize } from '../../space'; import styled from 'styled-components'; const StyledPropertyPane = styled.div` flex-grow: 1; flex-shrink: 0; flex-basis: 40%; + padding: ${getSpace(SpaceSize.M)}px; + overflow: scroll; `; -const PropertyPane: React.StatelessComponent = props => {props.children}; +const PropertyPane: React.StatelessComponent = props => ( + {props.children} +); export default PropertyPane; diff --git a/src/lsg/patterns/pattern-list/index.tsx b/src/lsg/patterns/pattern-list/index.tsx index 5e13d5acd..f12ddca33 100644 --- a/src/lsg/patterns/pattern-list/index.tsx +++ b/src/lsg/patterns/pattern-list/index.tsx @@ -1,7 +1,7 @@ import { colors } from '../colors'; -import { Icon, IconName, Size as IconSize } from '../icons'; +import { Icon, IconName, IconSize } from '../icons'; import * as React from 'react'; -import { getSpace, Size } from '../space'; +import { getSpace, SpaceSize } from '../space'; import styled from 'styled-components'; export interface PatternListItemProps { @@ -22,21 +22,21 @@ const StyledPatternList = styled.div` const StyledPatternLabel = styled.div` flex-basis: 100%; - margin-top: ${getSpace(Size.L)}px; - margin-bottom: ${getSpace(Size.S)}px; + margin-top: ${getSpace(SpaceSize.L)}px; + margin-bottom: ${getSpace(SpaceSize.S)}px; color: ${colors.grey60.toString()}; text-transform: capitalize; &:first-of-type { - margin-top: ${getSpace(Size.S)}px; + margin-top: ${getSpace(SpaceSize.S)}px; } `; const StyledPatternListItem = styled.div` box-sizing: border-box; - width: calc(50% - ${getSpace(Size.XS) / 2}px); - padding: ${getSpace(Size.S)}px; - margin-bottom: ${getSpace(Size.XS)}px; + width: calc(50% - ${getSpace(SpaceSize.XS) / 2}px); + padding: ${getSpace(SpaceSize.S)}px; + margin-bottom: ${getSpace(SpaceSize.XS)}px; border-radius: 3px; background: ${colors.white.toString()}; font-size: 12px; @@ -56,14 +56,14 @@ const StyledPatternListItem = styled.div` const StyledIcon = styled(Icon)` display: block; - margin: 0 auto ${getSpace(Size.XS)}px; + margin: 0 auto ${getSpace(SpaceSize.XS)}px; `; const StyledImg = styled.img` display: block; width: 18px; height: 18px; - margin: 0 auto ${getSpace(Size.XS)}px; + margin: 0 auto ${getSpace(SpaceSize.XS)}px; user-drag: none; user-select: none; `; diff --git a/src/lsg/patterns/preview-tile/demo.tsx b/src/lsg/patterns/preview-tile/demo.tsx index f261a5f1f..8cb70970e 100644 --- a/src/lsg/patterns/preview-tile/demo.tsx +++ b/src/lsg/patterns/preview-tile/demo.tsx @@ -4,63 +4,68 @@ import Copy from '../copy'; import { Headline } from '../headline'; import { PreviewTile } from './index'; import Layout from '../layout'; -import Space, { Size } from '../space'; +import Space, { SpaceSize } from '../space'; const handleChange = (e: React.ChangeEvent): string => e.target.value; const currentDate = new Date(); export const DemoPreviewTile = (): JSX.Element => ( - - + + Project Name - + Last Saved {currentDate.toDateString()} - + - + - + - + - + diff --git a/src/lsg/patterns/preview-tile/index.tsx b/src/lsg/patterns/preview-tile/index.tsx index 8f2f95297..a1afaa5b5 100644 --- a/src/lsg/patterns/preview-tile/index.tsx +++ b/src/lsg/patterns/preview-tile/index.tsx @@ -1,7 +1,7 @@ import { colors } from '../colors'; import Input, { InputTypes } from '../input'; import * as React from 'react'; -import { getSpace, Size } from '../space'; +import { getSpace, SpaceSize } from '../space'; import styled from 'styled-components'; export interface PreviewTileProps { @@ -9,6 +9,7 @@ export interface PreviewTileProps { focused: boolean; id?: string; name: string; + named: boolean; onBlur?: React.FocusEventHandler; onChange?: React.ChangeEventHandler; onClick?: React.MouseEventHandler; @@ -21,6 +22,10 @@ interface StyledPreviewTileProps { focused: boolean; } +interface StyledPreviewTitle { + named: boolean; +} + const StyledPreview = styled.section` width: 245px; text-align: center; @@ -42,16 +47,18 @@ const StyledPreviewTile = styled.div` const StyledTitle = styled.strong` display: inline-block; width: 100%; - margin-bottom: ${getSpace(Size.S)}px; + margin-bottom: ${getSpace(SpaceSize.S)}px; font-size: 12px; font-weight: normal; + color: ${(props: StyledPreviewTitle) => + props.named ? colors.black.toString() : colors.grey80.toString()} cursor: pointer; `; const StyledEditableTitle = styled(Input)` display: inline-block; padding: 0; - margin: 0 0 ${getSpace(Size.S)}px 0; + margin: 0 0 ${getSpace(SpaceSize.S)}px 0; font-size: 12px; font-weight: normal; text-align: center; @@ -79,7 +86,7 @@ export const PreviewTile: React.StatelessComponent = (props): {props.value} ) : ( - {props.name} + {props.name} )} diff --git a/src/lsg/patterns/property-items/asset-item/index.tsx b/src/lsg/patterns/property-items/asset-item/index.tsx index 55ca25db9..6808f0b7f 100644 --- a/src/lsg/patterns/property-items/asset-item/index.tsx +++ b/src/lsg/patterns/property-items/asset-item/index.tsx @@ -1,7 +1,7 @@ import { colors } from '../../colors'; import { fonts } from '../../fonts'; import * as React from 'react'; -import { getSpace, Size } from '../../space'; +import { getSpace, SpaceSize } from '../../space'; import styled from 'styled-components'; export interface AssetItemProps { @@ -22,12 +22,12 @@ const StyledPreview = styled.div` display: flex; flex-direction: row; align-items: center; - margin-bottom: ${getSpace(Size.XS)}px; + margin-bottom: ${getSpace(SpaceSize.XS)}px; `; const StyledLabel = styled.span` display: block; - margin-bottom: ${getSpace(Size.XS)}px; + margin-bottom: ${getSpace(SpaceSize.XS)}px; font-size: 12px; font-family: ${fonts().NORMAL_FONT}; color: ${colors.grey36.toString()}; @@ -84,7 +84,7 @@ const StyledButton = styled.button` border: 0.5px solid ${colors.grey90.toString()}; border-radius: 3px; background-color: ${colors.white.toString()}; - padding: ${getSpace(Size.XS)}px ${getSpace(Size.S)}px; + padding: ${getSpace(SpaceSize.XS)}px ${getSpace(SpaceSize.S)}px; `; export const AssetItem: React.StatelessComponent = props => ( diff --git a/src/lsg/patterns/property-items/boolean-item/index.tsx b/src/lsg/patterns/property-items/boolean-item/index.tsx index ac9980b7f..913d82d40 100644 --- a/src/lsg/patterns/property-items/boolean-item/index.tsx +++ b/src/lsg/patterns/property-items/boolean-item/index.tsx @@ -1,7 +1,7 @@ import { colors } from '../../colors'; import { fonts } from '../../fonts'; import * as React from 'react'; -import { getSpace, Size } from '../../space'; +import { getSpace, SpaceSize } from '../../space'; import styled from 'styled-components'; export interface BooleanItemProps { @@ -21,7 +21,7 @@ const StyledBooleanItem = styled.div` const StyledLabelWrapper = styled.label` display: block; - margin-bottom: ${getSpace(Size.L)}px; + margin-bottom: ${getSpace(SpaceSize.L)}px; `; const indicatorWidth = 42; @@ -70,7 +70,7 @@ const StyledLabel = styled.span` font-size: 12px; font-family: ${fonts().NORMAL_FONT}; color: ${colors.grey36.toString()}; - margin-bottom: ${getSpace(Size.XS)}px; + margin-bottom: ${getSpace(SpaceSize.XS)}px; `; const StyledInput = styled.input` diff --git a/src/lsg/patterns/property-items/enum-item/index.tsx b/src/lsg/patterns/property-items/enum-item/index.tsx index 9c50ae999..ee7a58dea 100644 --- a/src/lsg/patterns/property-items/enum-item/index.tsx +++ b/src/lsg/patterns/property-items/enum-item/index.tsx @@ -1,7 +1,7 @@ import { colors } from '../../colors'; import { fonts } from '../../fonts'; import * as React from 'react'; -import { getSpace, Size } from '../../space'; +import { getSpace, SpaceSize } from '../../space'; import styled from 'styled-components'; export interface Values { @@ -32,8 +32,8 @@ const StyledSelect = styled.select` color: ${colors.grey36.toString()}; font-size: 15px; border-bottom: 1px solid transparent; - padding-bottom: ${getSpace(Size.M) / 2}px; - margin-bottom: ${getSpace(Size.L)}px; + padding-bottom: ${getSpace(SpaceSize.M) / 2}px; + margin-bottom: ${getSpace(SpaceSize.L)}px; transition: all 0.2s; &:hover { @@ -50,7 +50,7 @@ const StyledSelect = styled.select` const StyledLabel = styled.span` display: block; - margin-bottom: ${getSpace(Size.XS)}px; + margin-bottom: ${getSpace(SpaceSize.XS)}px; font-size: 12px; font-family: ${fonts().NORMAL_FONT}; color: ${colors.grey36.toString()}; diff --git a/src/lsg/patterns/property-items/string-item/index.tsx b/src/lsg/patterns/property-items/string-item/index.tsx index 699e974c0..458fe15da 100644 --- a/src/lsg/patterns/property-items/string-item/index.tsx +++ b/src/lsg/patterns/property-items/string-item/index.tsx @@ -1,7 +1,7 @@ import { colors } from '../../colors'; import { fonts } from '../../fonts'; import * as React from 'react'; -import { getSpace, Size } from '../../space'; +import { getSpace, SpaceSize } from '../../space'; import styled from 'styled-components'; export interface StringItemProps { @@ -18,7 +18,7 @@ const StyledStringItem = styled.div` const StyledLabel = styled.span` display: block; - margin-bottom: ${getSpace(Size.XS)}px; + margin-bottom: ${getSpace(SpaceSize.XS)}px; font-size: 12px; font-family: ${fonts().NORMAL_FONT}; color: ${colors.grey36.toString()}; @@ -34,9 +34,9 @@ const StyledInput = styled.input` background: transparent; font-family: ${fonts().NORMAL_FONT}; font-size: 15px; - padding-bottom: ${getSpace(Size.M) / 2}px; + padding-bottom: ${getSpace(SpaceSize.M) / 2}px; color: ${colors.grey36.toString()}; - margin-bottom: ${getSpace(Size.L)}px; + margin-bottom: ${getSpace(SpaceSize.L)}px; transition: all 0.2s; ::-webkit-input-placeholder { diff --git a/src/lsg/patterns/space/demo.tsx b/src/lsg/patterns/space/demo.tsx index f829a9a11..a078fd748 100644 --- a/src/lsg/patterns/space/demo.tsx +++ b/src/lsg/patterns/space/demo.tsx @@ -1,12 +1,12 @@ import { Color, colors } from '../colors'; import DemoContainer from '../demo-container'; -import Space, { Size } from './index'; +import Space, { SpaceSize } from './index'; import * as React from 'react'; import styled from 'styled-components'; -const blue40: Color = new Color({displayName: 'Demo Blue', rgb: [91, 177, 255]}); -const green: Color = new Color({displayName: 'Demo Green', rgb: [91, 255, 151]}); -const violet: Color = new Color({displayName: 'Demo Violet', rgb: [181, 91, 255]}); +const blue40: Color = new Color({ displayName: 'Demo Blue', rgb: [91, 177, 255] }); +const green: Color = new Color({ displayName: 'Demo Green', rgb: [91, 255, 151] }); +const violet: Color = new Color({ displayName: 'Demo Violet', rgb: [181, 91, 255] }); const DemoTileSpace = styled(Space)` display: flex; @@ -45,77 +45,119 @@ const Content = styled.div` const SpaceDemo: React.StatelessComponent = (): JSX.Element => ( Inset - - - {Size[Size.XS]} - - - {Size[Size.XS]} - - - {Size[Size.S]} - - - {Size[Size.M]} - - - {Size[Size.L]} - - - {Size[Size.XL]} - - - {Size[Size.XXL]} + + + + {SpaceSize[SpaceSize.XS]} + + + + + {SpaceSize[SpaceSize.XS]} + + + + + {SpaceSize[SpaceSize.S]} + + + + + {SpaceSize[SpaceSize.M]} + + + + + {SpaceSize[SpaceSize.L]} + + + + + {SpaceSize[SpaceSize.XL]} + + + + + {SpaceSize[SpaceSize.XXL]} + - Inline - - - {Size[Size.XS]} - - - {Size[Size.XS]} - - - {Size[Size.S]} - - - {Size[Size.M]} - - - {Size[Size.L]} - - - {Size[Size.XL]} - - - {Size[Size.XXL]} + Inline + + + + {SpaceSize[SpaceSize.XS]} + + + + + {SpaceSize[SpaceSize.XS]} + + + + + {SpaceSize[SpaceSize.S]} + + + + + {SpaceSize[SpaceSize.M]} + + + + + {SpaceSize[SpaceSize.L]} + + + + + {SpaceSize[SpaceSize.XL]} + + + + + {SpaceSize[SpaceSize.XXL]} + - Stack - - - {Size[Size.XS]} - - - {Size[Size.XS]} - - - {Size[Size.S]} - - - {Size[Size.M]} - - - {Size[Size.L]} - - - {Size[Size.XL]} - - - {Size[Size.XXL]} + Stack + + + + {SpaceSize[SpaceSize.XS]} + + + + + {SpaceSize[SpaceSize.XS]} + + + + + {SpaceSize[SpaceSize.S]} + + + + + {SpaceSize[SpaceSize.M]} + + + + + {SpaceSize[SpaceSize.L]} + + + + + {SpaceSize[SpaceSize.XL]} + + + + + {SpaceSize[SpaceSize.XXL]} + diff --git a/src/lsg/patterns/space/index.tsx b/src/lsg/patterns/space/index.tsx index f65e42462..9e470ccfc 100644 --- a/src/lsg/patterns/space/index.tsx +++ b/src/lsg/patterns/space/index.tsx @@ -4,21 +4,21 @@ import styled from 'styled-components'; export interface SpaceProps { className?: string; inside?: boolean; - size?: Size | Size[]; - sizeBottom?: Size; - sizeLeft?: Size; - sizeRight?: Size; - sizeTop?: Size; + size?: SpaceSize | SpaceSize[]; + sizeBottom?: SpaceSize; + sizeLeft?: SpaceSize; + sizeRight?: SpaceSize; + sizeTop?: SpaceSize; } export interface StyledSpaceProps { inside: boolean; // Redefine `size` as `spaceSize` property for styled components to avoid collision // with `size` property on `React.HTMLAttributes` interface. - spaceSize: Size[]; + spaceSize: SpaceSize[]; } -export enum Size { +export enum SpaceSize { // Explicity set enum values to indicate that they will be used later on as // array indicies. None = 0, @@ -46,7 +46,7 @@ export enum PageInset { * given growth mapping. * @param size The size used for calculation. */ -export function getSpace(size: Size): number { +export function getSpace(size: SpaceSize): number { if (undefined !== size) { return size; } @@ -80,7 +80,7 @@ function merge(shorthand: T | T[], top?: T, right?: T, bottom?: T, left?: T): ]; } -function calculate(property: string, space: Size[]): string { +function calculate(property: string, space: SpaceSize[]): string { const values = space.map((value, index) => `${getSpace(value)}px`); const result = `${property}: ${values.join(' ')};`; diff --git a/src/lsg/patterns/tab-navigation/index.tsx b/src/lsg/patterns/tab-navigation/index.tsx index 278723985..ffb3f36e9 100644 --- a/src/lsg/patterns/tab-navigation/index.tsx +++ b/src/lsg/patterns/tab-navigation/index.tsx @@ -1,7 +1,7 @@ import { colors } from '../colors'; import { fonts } from '../fonts'; import * as React from 'react'; -import { getSpace, Size } from '../space'; +import { getSpace, SpaceSize } from '../space'; import styled from 'styled-components'; export interface TabNavigationProps { @@ -26,32 +26,29 @@ const StyledTabNavigation = styled.div` const StyledTabNavigationItem = styled.div` flex-grow: 1; - padding: ${getSpace(Size.XS)}px ${getSpace(Size.M)}px; - ${(props: TabNavigationItemProps) => props.active - ? `background: ${colors.grey60.toString()}; + padding: ${getSpace(SpaceSize.XS)}px ${getSpace(SpaceSize.M)}px; + ${(props: TabNavigationItemProps) => + props.active + ? `background: ${colors.grey60.toString()}; color: ${colors.white.toString()};` - : `background: ${colors.white.toString()}; - color: ${colors.grey60.toString()};` - } + : `background: ${colors.white.toString()}; + color: ${colors.grey60.toString()};`} font-family: ${fonts().NORMAL_FONT}; font-size: 12px; text-align: center; cursor: pointer; `; -export const TabNavigationItem: React.StatelessComponent = (props): JSX.Element => ( - +export const TabNavigationItem: React.StatelessComponent = ( + props +): JSX.Element => ( + {props.tabText} ); export const TabNavigation: React.StatelessComponent = (props): JSX.Element => ( - - {props.children} - + {props.children} ); export default TabNavigation; diff --git a/src/lsg/patterns/tag.tsx b/src/lsg/patterns/tag.tsx new file mode 100644 index 000000000..979bfb469 --- /dev/null +++ b/src/lsg/patterns/tag.tsx @@ -0,0 +1,24 @@ +import { omit, pick } from 'lodash'; +import * as React from 'react'; + +export interface Tag { + omit(whitelist: string[]): React.SFC>; + pick(blacklist: string[]): React.SFC>; +} + +export function tag(TagName: string): Tag { + return { + omit(blacklist: string[]): React.SFC<{}> { + return props => { + const p = omit(props, blacklist); + return {p.children}; + }; + }, + pick(whitelist: string[]): React.SFC<{}> { + return props => { + const p = pick(props, whitelist); + return {p.children}; + }; + } + }; +} diff --git a/src/lsg/patterns/view-switch/demo.tsx b/src/lsg/patterns/view-switch/demo.tsx new file mode 100644 index 000000000..8e63e27af --- /dev/null +++ b/src/lsg/patterns/view-switch/demo.tsx @@ -0,0 +1,18 @@ +import { IconName, IconRegistry } from '../icons'; +import { ViewSwitch } from './index'; +import * as React from 'react'; + +const DemoViewSwitch: React.StatelessComponent = (): JSX.Element => ( +
+ null} + onRightClick={() => null} + leftVisible={true} + rightVisible={true} + title="Page Name" + /> + +
+); + +export default DemoViewSwitch; diff --git a/src/lsg/patterns/view-switch/index.tsx b/src/lsg/patterns/view-switch/index.tsx new file mode 100644 index 000000000..8d0725c78 --- /dev/null +++ b/src/lsg/patterns/view-switch/index.tsx @@ -0,0 +1,94 @@ +import { colors } from '../colors'; +import { CopySize } from '../copy'; +import { Icon, IconName, IconProps, IconSize } from '../icons'; +import * as React from 'react'; +import { getSpace, SpaceSize } from '../space'; +import styled from 'styled-components'; + +export type JustifyType = 'start' | 'center' | 'end' | 'stretch'; + +export interface ViewSwitchProps { + fontSize?: CopySize; + justify?: JustifyType; + leftVisible: boolean; + onLeftClick?: React.MouseEventHandler; + onRightClick?: React.MouseEventHandler; + rightVisible: boolean; + title: string; +} + +export interface ViewTitleProps { + fontSize?: CopySize; + justify?: JustifyType; + title: string; +} + +interface StyledIconProps extends IconProps { + visible: boolean; +} + +interface StyledViewSwitchProps { + fontSize?: CopySize; + justify?: 'start' | 'center' | 'end' | 'stretch'; +} + +const StyledViewSwitch = styled.div` + display: flex; + align-self: center; + justify-self: ${(props: StyledViewSwitchProps) => props.justify || 'start'}; + font-size: ${(props: StyledViewSwitchProps) => + props.fontSize ? `${props.fontSize}px` : `${CopySize.S}px`}; +`; + +const StyledTitle = styled.strong` + position: relative; + align-self: center; + display: inline-block; + width: 130px; + margin: 0 ${getSpace(SpaceSize.XS)}px; + overflow: hidden; + color: ${colors.grey36.toString()}; + font-size: inherit; + font-weight: normal; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const StyledIcons = styled(Icon)` + padding: ${getSpace(SpaceSize.XS)}px; + border-radius: ${getSpace(SpaceSize.XXS)}px; + visibility: ${(props: StyledIconProps) => (props.visible ? 'visible' : 'hidden')}; + cursor: pointer; + pointer-events: auto; + + &:hover { + background: ${colors.grey90.toString()}; + } +`; + +export const ViewTitle: React.StatelessComponent = (props): JSX.Element => ( + + {props.title} + +); + +export const ViewSwitch: React.StatelessComponent = (props): JSX.Element => ( + + + {props.title} + + +); diff --git a/src/lsg/patterns/view-switch/pattern.json b/src/lsg/patterns/view-switch/pattern.json new file mode 100644 index 000000000..68a684f76 --- /dev/null +++ b/src/lsg/patterns/view-switch/pattern.json @@ -0,0 +1,6 @@ +{ + "name": "viewswitch", + "displayName": "View Switch", + "version": "1.0.0", + "flag": "alpha" +} diff --git a/src/message/index.ts b/src/message/index.ts new file mode 100644 index 000000000..5213379ff --- /dev/null +++ b/src/message/index.ts @@ -0,0 +1,42 @@ +export enum PreviewMessageType { + ContentRequest = 'content-request', + ContentResponse = 'content-response', + ElementChange = 'element-change', + Reload = 'reload', + SketchExportRequest = 'sketch-export-request', + SketchExportResponse = 'sketch-export-response', + State = 'state', + Update = 'update' +} + +export enum ServerMessageType { + AppLoaded = 'app-loaded', + BundleChange = 'bundle-change', + ContentRequest = 'content-request', + ContentResponse = 'content-response', + ElementChange = 'element-change', + PageChange = 'page-change', + SketchExportRequest = 'sketch-export-request', + SketchExportResponse = 'sketch-export-response', + StartApp = 'start-app', + StyleGuideChange = 'styleguide-change' +} + +export interface Envelope { + id: string; + payload: T; + type: V; +} + +export type ServerMessage = + | ContentRequest + | ContentResponse + | SketchExportRequest + | SketchExportResponse + | StartAppMessage; + +export type StartAppMessage = Envelope; +export type ContentRequest = Envelope; +export type ContentResponse = Envelope; +export type SketchExportRequest = Envelope; +export type SketchExportResponse = Envelope; diff --git a/src/preview/components-loader.ts b/src/preview/components-loader.ts new file mode 100644 index 000000000..0a236570b --- /dev/null +++ b/src/preview/components-loader.ts @@ -0,0 +1,27 @@ +import { getOptions } from 'loader-utils'; +import { loader } from 'webpack'; + +// No typings available for commondir +const commondir = require('commondir'); + +module.exports = alvaEntryLoader; + +interface StringMap { + [name: string]: string; +} + +export function alvaEntryLoader(this: loader.LoaderContext): string { + const options = getOptions(this); + const components: StringMap = JSON.parse(options.components); + const common = commondir(options.cwd, Object.values(components)); + + this.addContextDependency(common); + + return Object.entries(components) + .map(([name, id]) => createExport(name, id)) + .join('\n'); +} + +function createExport(name: string, id: string): string { + return `module.exports[${JSON.stringify(name)}] = require(${JSON.stringify(id)})`; +} diff --git a/src/preview/get-component.ts b/src/preview/get-component.ts new file mode 100644 index 000000000..738df0b2c --- /dev/null +++ b/src/preview/get-component.ts @@ -0,0 +1,66 @@ +import { camelCase, upperFirst } from 'lodash'; +import { patternIdToWebpackName } from './pattern-id-to-webpack-name'; + +export interface PassedComponentProps { + // tslint:disable-next-line:no-any + [propName: string]: any; +} + +export interface InputComponentProps extends PassedComponentProps { + name: string; + pattern: string; +} + +export interface SyntheticComponents { + // tslint:disable-next-line:no-any + asset: T; + // tslint:disable-next-line:no-any + text: T; +} + +export type ComponentGetter = ( + props: InputComponentProps, + synthetics: SyntheticComponents +) => T | null; + +export function getComponent( + props: InputComponentProps, + synthetics: SyntheticComponents +): T | undefined { + const fragments = props.pattern ? props.pattern.split(':') : []; + + if (fragments[0] === 'synthetic') { + const syntheticType = fragments[1]; + return synthetics[syntheticType]; + } + + // tslint:disable-next-line:no-any + const win: any = window; + + const component = props.pattern ? win.components[patternIdToWebpackName(props.pattern)] : null; + + if (!component) { + return; + } + + const Component = resolveExport(component, props.exportName); + + if (!Component) { + return; + } + + // tslint:disable-next-line:no-any + (Component as any).displayName = upperFirst(camelCase(props.name)); + + return Component; +} + +// tslint:disable-next-line:no-any +function resolveExport(candidate: any, exportName: string): T | undefined { + if (exportName === 'default') { + const Component = typeof candidate.default === 'function' ? candidate.default : candidate; + return Component; + } + + return candidate[exportName]; +} diff --git a/src/styleguide/renderer/highlight-area.ts b/src/preview/highlight-area.ts similarity index 93% rename from src/styleguide/renderer/highlight-area.ts rename to src/preview/highlight-area.ts index b5a44620d..dbbfb1a75 100644 --- a/src/styleguide/renderer/highlight-area.ts +++ b/src/preview/highlight-area.ts @@ -34,6 +34,9 @@ export class HighlightArea { } public show(element: Element, pageElementId: string): void { + if (typeof element.getBoundingClientRect !== 'function') { + return; + } if (this.pageElementId === pageElementId) { return; } diff --git a/src/preview/pattern-id-to-webpack-name.ts b/src/preview/pattern-id-to-webpack-name.ts new file mode 100644 index 000000000..2d6c4de44 --- /dev/null +++ b/src/preview/pattern-id-to-webpack-name.ts @@ -0,0 +1,8 @@ +export function patternIdToWebpackName(id: string): string { + return encodeURIComponent( + id + .split('@')[0] + .split('/') + .join('-') + ); +} diff --git a/src/electron/renderer/preview.html b/src/preview/preview-document.ts similarity index 75% rename from src/electron/renderer/preview.html rename to src/preview/preview-document.ts index 06512f573..d83342060 100644 --- a/src/electron/renderer/preview.html +++ b/src/preview/preview-document.ts @@ -1,5 +1,5 @@ +export const previewDocument = ` - alva™ -