├── .circleci └── config.yml ├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── .prettierignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── .yarnclean ├── .yarnrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── commitlint.config.js ├── docs ├── about-us.md ├── contributors │ ├── RELEASE.md │ ├── architecture-diagram.md │ ├── architecture-diagram.svg │ ├── contributing.md │ ├── develop-alva.md │ └── test-autoupdater.md ├── github.md ├── gitter.md ├── guides-design.md ├── guides-design │ ├── design-drafts.md │ ├── getting-started.md │ ├── interaction.md │ └── variables.md ├── guides-dev.md ├── guides-dev │ ├── create-library.md │ ├── create-pattern.md │ ├── create-properties.md │ └── install-local-library.md ├── howtos.md ├── legalnotice.md ├── privacypolicy.md ├── references │ ├── glossary.md │ ├── library-requirements.md │ ├── properties.md │ └── reference.md └── start.md ├── jest.config.js ├── lerna.json ├── package.json ├── packages ├── analyzer-cli │ ├── bin │ │ └── analyze.js │ ├── package.json │ ├── src │ │ └── bin │ │ │ └── analyze.ts │ └── tsconfig.json ├── analyzer │ ├── index.d.ts │ ├── index.js │ ├── package.json │ ├── src │ │ ├── compiler │ │ │ ├── compiler-safe-name.ts │ │ │ ├── create-compiler.ts │ │ │ └── index.ts │ │ ├── fixtures │ │ │ ├── children.tsx │ │ │ ├── enum-member-name.ts │ │ │ ├── enum.ts │ │ │ ├── export-infos-class-metadata.tsx │ │ │ ├── export-infos-class.tsx │ │ │ ├── export-infos-default-metadata.tsx │ │ │ ├── export-infos-default.tsx │ │ │ ├── export-infos-export-declaration-metadata.d.ts │ │ │ ├── export-infos-export-declaration-metadata.js │ │ │ ├── export-infos-export-declaration.d.ts │ │ │ ├── export-infos-export-declaration.js │ │ │ ├── export-infos-variable-metadata.tsx │ │ │ ├── export-infos-variable.tsx │ │ │ ├── prop-any.ts │ │ │ ├── prop-event-function.ts │ │ │ ├── prop-event-handler.ts │ │ │ ├── prop-event-method.ts │ │ │ ├── prop-generic-event-handler.ts │ │ │ ├── prop-slots.tsx │ │ │ ├── shared-properties-ab.ts │ │ │ ├── shared-properties-c.ts │ │ │ └── string-enum.ts │ │ ├── get-package.ts │ │ ├── id-hasher.ts │ │ ├── index.ts │ │ ├── react-utils │ │ │ ├── find-react-component-type.ts │ │ │ ├── get-event-type.test.ts │ │ │ ├── get-event-type.ts │ │ │ ├── get-react-slot-type.ts │ │ │ ├── has-react-typings.ts │ │ │ ├── index.ts │ │ │ ├── is-react-component-type.ts │ │ │ ├── is-react-event-handler-type.test.ts │ │ │ ├── is-react-event-handler-type.ts │ │ │ ├── is-react-slot-type.test.ts │ │ │ ├── is-react-slot-type.ts │ │ │ ├── uses-children.test.ts │ │ │ └── uses-children.ts │ │ ├── test-utils.ts │ │ ├── typescript-react-analyzer │ │ │ ├── fixtures │ │ │ │ ├── props-comments.ts │ │ │ │ ├── props-slot-defaults.tsx │ │ │ │ ├── slot-analyzer-fenced-tick.md │ │ │ │ ├── slot-analyzer-fenced-tilde.md │ │ │ │ ├── slot-analyzer-fenced-whitespace.md │ │ │ │ ├── slot-analyzer-last.md │ │ │ │ └── slot-analyzer-other-tags.md │ │ │ ├── index.ts │ │ │ ├── property-analyzer │ │ │ │ ├── create-enum-property.test.ts │ │ │ │ ├── create-enum-property.ts │ │ │ │ ├── index.ts │ │ │ │ ├── property-analyzer.ts │ │ │ │ ├── property-creators.ts │ │ │ │ └── types.ts │ │ │ ├── slot-analyzer.test.ts │ │ │ ├── slot-analyzer.ts │ │ │ ├── slot-default-analyzer.test.ts │ │ │ ├── slot-default-analyzer.ts │ │ │ ├── typescript-react-analyzer.test.ts │ │ │ └── typescript-react-analyzer.ts │ │ └── typescript-utils │ │ │ ├── find-declaration.ts │ │ │ ├── find-type-declaration.ts │ │ │ ├── get-export-infos.test.ts │ │ │ ├── get-export-infos.ts │ │ │ ├── get-exported-node.test.ts │ │ │ ├── get-exported-node.ts │ │ │ ├── get-exports.ts │ │ │ ├── get-tsa-export.ts │ │ │ ├── index.ts │ │ │ ├── is-export.ts │ │ │ ├── is-type-reference.ts │ │ │ ├── is-union-type.ts │ │ │ ├── jsdoc.ts │ │ │ ├── set-extname.ts │ │ │ ├── typescript-export.ts │ │ │ └── typescript-type.ts │ └── tsconfig.json ├── components │ ├── index.d.ts │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── add-button │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── badge-icon │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── button-group │ │ │ └── index.tsx │ │ ├── button │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── chrome-button │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── chrome │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── colors │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── copy │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── create-select │ │ │ ├── create-select.tsx │ │ │ └── index.tsx │ │ ├── demo-container.tsx │ │ ├── drag-area │ │ │ ├── demo.tsx │ │ │ ├── drag-area.tsx │ │ │ ├── index.tsx │ │ │ ├── pattern.json │ │ │ └── target-signal.tsx │ │ ├── editable-title │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── element-slot │ │ │ ├── demo.tsx │ │ │ ├── element-slot.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── element │ │ │ ├── demo.tsx │ │ │ ├── element.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── empty-state │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── flex.tsx │ │ ├── fonts │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── global-styles.tsx │ │ ├── headline │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── icons │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── image │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── index.ts │ │ ├── input-button │ │ │ └── index.tsx │ │ ├── item │ │ │ ├── index.ts │ │ │ ├── item.styles.ts │ │ │ └── item.tsx │ │ ├── layout-switch │ │ │ ├── index.tsx │ │ │ ├── layout-switch.tsx │ │ │ └── pattern.json │ │ ├── layout │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── library-box │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ ├── library-box.tsx │ │ │ ├── library-image.tsx │ │ │ └── pattern.json │ │ ├── link-icon │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── link │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── list │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── page-tile │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── panes │ │ │ ├── element-pane │ │ │ │ └── index.tsx │ │ │ ├── index.ts │ │ │ ├── patterns-pane │ │ │ │ └── index.tsx │ │ │ ├── preview-pane │ │ │ │ └── index.tsx │ │ │ ├── project-pane │ │ │ │ └── index.tsx │ │ │ └── property-pane │ │ │ │ └── index.tsx │ │ ├── pattern-list │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── property-box │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── property-details │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── property-input │ │ │ └── index.tsx │ │ ├── property-item-asset │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── property-item-boolean │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── property-item-buttongroup │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── property-item-color │ │ │ ├── index.tsx │ │ │ ├── pattern.json │ │ │ └── property-item-color.tsx │ │ ├── property-item-number │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ ├── pattern.json │ │ │ ├── property-item-number.test.tsx │ │ │ └── property-item-number.tsx │ │ ├── property-item-select │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── property-item-string │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── property-item │ │ │ ├── index.tsx │ │ │ ├── property-description.tsx │ │ │ ├── property-item.tsx │ │ │ └── property-label.tsx │ │ ├── property-reference │ │ │ └── index.tsx │ │ ├── search │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── select │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── space │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── spinner │ │ │ └── index.tsx │ │ ├── splash-screen │ │ │ ├── index.tsx │ │ │ ├── pattern.json │ │ │ ├── splash-screen.styles.ts │ │ │ └── splash-screen.tsx │ │ ├── tab-switch.tsx │ │ ├── tab │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ │ ├── teaser │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ ├── pattern.json │ │ │ └── teaser.tsx │ │ ├── update-badge │ │ │ ├── index.ts │ │ │ ├── update-badge.styles.tsx │ │ │ └── update-badge.tsx │ │ └── view-switch │ │ │ ├── demo.tsx │ │ │ ├── index.tsx │ │ │ └── pattern.json │ └── tsconfig.json ├── core │ ├── .prettierignore │ ├── bin │ │ └── alva.js │ ├── dev-app-update.yml │ ├── package.json │ ├── package.ncc.json │ ├── src │ │ ├── adapters │ │ │ ├── browser-adapter.ts │ │ │ ├── electron-adapter │ │ │ │ ├── electron-adapter.test.ts │ │ │ │ ├── electron-adapter.ts │ │ │ │ ├── electron-main-menu.ts │ │ │ │ ├── electron-updater.ts │ │ │ │ └── index.ts │ │ │ └── node-adapter.ts │ │ ├── bin │ │ │ ├── alva.ts │ │ │ ├── analyze.ts │ │ │ ├── electron.ts │ │ │ ├── node.ts │ │ │ └── static │ │ │ │ ├── build.ts │ │ │ │ ├── index.ts │ │ │ │ └── static.ts │ │ ├── container │ │ │ ├── app-pane.tsx │ │ │ ├── app-view.tsx │ │ │ ├── app.tsx │ │ │ ├── chrome │ │ │ │ ├── chrome-container.tsx │ │ │ │ └── chrome-switch.tsx │ │ │ ├── connect-pane-container.tsx │ │ │ ├── dragged-element-image.tsx │ │ │ ├── editable-title │ │ │ │ └── editable-title-container.tsx │ │ │ ├── element-drag-image.tsx │ │ │ ├── element-list │ │ │ │ ├── element-container.tsx │ │ │ │ ├── element-content-container.tsx │ │ │ │ ├── element-list.test.tsx │ │ │ │ ├── element-list.tsx │ │ │ │ ├── element-slot-container.tsx │ │ │ │ └── index.tsx │ │ │ ├── file-input.tsx │ │ │ ├── library-store-container.tsx │ │ │ ├── library-store-item-container.tsx │ │ │ ├── match.tsx │ │ │ ├── menu │ │ │ │ ├── accelerator.tsx │ │ │ │ ├── context-menu.tsx │ │ │ │ ├── index.ts │ │ │ │ └── menu.tsx │ │ │ ├── page-list │ │ │ │ ├── page-list-container.tsx │ │ │ │ └── page-tile-container.tsx │ │ │ ├── pane-development-editor.tsx │ │ │ ├── pattern-list │ │ │ │ ├── index.tsx │ │ │ │ ├── pattern-item-container.tsx │ │ │ │ └── pattern-list-container.tsx │ │ │ ├── preview-pane-wrapper.tsx │ │ │ ├── property-list │ │ │ │ ├── action-payload-input.tsx │ │ │ │ ├── action-payload-select.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── property-item-asset.tsx │ │ │ │ ├── property-item-boolean.tsx │ │ │ │ ├── property-item-button-group.tsx │ │ │ │ ├── property-item-color.tsx │ │ │ │ ├── property-item-enum.tsx │ │ │ │ ├── property-item-event.tsx │ │ │ │ ├── property-item-number.tsx │ │ │ │ ├── property-item-string.tsx │ │ │ │ ├── property-list-item.tsx │ │ │ │ ├── property-list-virtual.tsx │ │ │ │ ├── property-list.test.tsx │ │ │ │ ├── property-list.tsx │ │ │ │ ├── property-row.tsx │ │ │ │ ├── property-unknown-editor-skeleton.tsx │ │ │ │ ├── property-unknown-editor.tsx │ │ │ │ ├── reference-select.tsx │ │ │ │ └── reference.tsx │ │ │ ├── recent-files-list.tsx │ │ │ ├── splash-screen-container.tsx │ │ │ ├── splash-screen-view.tsx │ │ │ ├── user-store-property-select.tsx │ │ │ ├── view-details.tsx │ │ │ └── when.tsx │ │ ├── context-menu │ │ │ ├── element-menu.ts │ │ │ └── index.ts │ │ ├── export │ │ │ ├── export-html-project.ts │ │ │ └── index.ts │ │ ├── hosts │ │ │ ├── browser-data-host.ts │ │ │ ├── browser-host.tsx │ │ │ ├── electron-host │ │ │ │ ├── create-window-preload.ts │ │ │ │ ├── create-window.ts │ │ │ │ ├── electron-host.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── local-data-host.ts │ │ │ ├── node-host │ │ │ │ └── index.ts │ │ │ └── restart-listener.ts │ │ ├── matchers │ │ │ ├── add-app.ts │ │ │ ├── browser.ts │ │ │ ├── connect-npm-pattern-library-request.ts │ │ │ ├── connect-pattern-library-request.ts │ │ │ ├── context.ts │ │ │ ├── copy.ts │ │ │ ├── create-new-file-request.ts │ │ │ ├── cut.ts │ │ │ ├── export-html-project.ts │ │ │ ├── index.ts │ │ │ ├── open-asset.ts │ │ │ ├── open-external-url.ts │ │ │ ├── open-file-request.ts │ │ │ ├── open-remote-file-request.ts │ │ │ ├── open-window.ts │ │ │ ├── paste.ts │ │ │ ├── save-as.ts │ │ │ ├── save.ts │ │ │ ├── show-context-menu.ts │ │ │ ├── show-error.ts │ │ │ ├── show-message.test.ts │ │ │ ├── show-message.ts │ │ │ ├── show-update-details.ts │ │ │ ├── update-npm-pattern-library-request.ts │ │ │ ├── update-pattern-library-request.ts │ │ │ ├── use-file-request.ts │ │ │ └── use-file-response.ts │ │ ├── menu │ │ │ ├── app-menu.ts │ │ │ ├── context.ts │ │ │ ├── edit-menu.ts │ │ │ ├── file-menu.tsx │ │ │ ├── help-menu.ts │ │ │ ├── index.ts │ │ │ ├── view-menu.ts │ │ │ └── window-menu.ts │ │ ├── migrator │ │ │ ├── abstract-migration.ts │ │ │ ├── fixtures │ │ │ │ ├── v0-conditional.alva │ │ │ │ ├── v0-user-property.alva │ │ │ │ ├── v0.alva │ │ │ │ ├── v1-designkit.alva │ │ │ │ ├── v1.alva │ │ │ │ ├── v2-content.alva │ │ │ │ ├── v2-user-property.alva │ │ │ │ └── v2.alva │ │ │ ├── index.ts │ │ │ ├── migration-v0-v1.test.ts │ │ │ ├── migration-v0-v1.ts │ │ │ ├── migration-v1-v2.test.ts │ │ │ ├── migration-v1-v2.ts │ │ │ ├── migration-v2-v3.test.ts │ │ │ ├── migration-v2-v3.ts │ │ │ └── migrator.ts │ │ ├── persistence │ │ │ ├── index.ts │ │ │ └── persistence.ts │ │ ├── preview-document │ │ │ ├── index.ts │ │ │ ├── preview-document.ts │ │ │ ├── sketch-document.ts │ │ │ └── static-document.ts │ │ ├── preview-renderer │ │ │ ├── index.ts │ │ │ ├── preview-application.tsx │ │ │ ├── preview-component-error.tsx │ │ │ ├── preview-component.tsx │ │ │ └── preview-renderer.tsx │ │ ├── preview │ │ │ ├── element-area.ts │ │ │ ├── get-components.ts │ │ │ ├── get-initial-data.ts │ │ │ ├── preview-store.test.ts │ │ │ ├── preview-store.ts │ │ │ ├── preview.ts │ │ │ └── test.ts │ │ ├── renderer │ │ │ ├── create-handlers.ts │ │ │ ├── create-listeners.ts │ │ │ ├── create-notifiers.ts │ │ │ ├── edit-handlers │ │ │ │ ├── cut.ts │ │ │ │ ├── delete-selected.ts │ │ │ │ ├── duplicate-element.ts │ │ │ │ ├── duplicate-selected.ts │ │ │ │ ├── index.ts │ │ │ │ ├── paste-element.ts │ │ │ │ ├── paste-page.ts │ │ │ │ ├── redo.ts │ │ │ │ ├── remove-element.ts │ │ │ │ └── undo.ts │ │ │ ├── index.tsx │ │ │ ├── message-handlers │ │ │ │ ├── activate-page.ts │ │ │ │ ├── app-request.ts │ │ │ │ ├── change-user-store.ts │ │ │ │ ├── check-pattern-library.ts │ │ │ │ ├── connect-pattern-library.ts │ │ │ │ ├── create-new-page.ts │ │ │ │ ├── highlight-element.ts │ │ │ │ ├── index.ts │ │ │ │ ├── keyboard-change.ts │ │ │ │ ├── log.ts │ │ │ │ ├── project-records-changed.ts │ │ │ │ ├── project-request.ts │ │ │ │ ├── save.ts │ │ │ │ ├── select-element.ts │ │ │ │ ├── set-pane.ts │ │ │ │ ├── start-app.ts │ │ │ │ ├── update-downloaded.ts │ │ │ │ ├── update-pattern-library-request.ts │ │ │ │ ├── update-pattern-library.ts │ │ │ │ └── updating-pattern-library.ts │ │ │ ├── renderer-document.ts │ │ │ └── renderer.tsx │ │ ├── resources │ │ │ ├── alpha │ │ │ │ ├── document.icns │ │ │ │ ├── document.ico │ │ │ │ ├── icon.icns │ │ │ │ └── icon.ico │ │ │ ├── document.icns │ │ │ ├── document.ico │ │ │ ├── icon.icns │ │ │ └── icon.ico │ │ ├── sender │ │ │ ├── index.ts │ │ │ ├── is-message-type.ts │ │ │ ├── is-message.ts │ │ │ ├── sender.ts │ │ │ └── serde.ts │ │ ├── server │ │ │ ├── index.ts │ │ │ ├── routes │ │ │ │ ├── context.ts │ │ │ │ ├── export.ts │ │ │ │ ├── index.ts │ │ │ │ ├── library.ts │ │ │ │ ├── main.tsx │ │ │ │ ├── preview.ts │ │ │ │ ├── project-store.ts │ │ │ │ ├── project.ts │ │ │ │ └── scripts.ts │ │ │ └── server.ts │ │ └── store │ │ │ ├── index.ts │ │ │ ├── menu-store.ts │ │ │ └── view-store.ts │ ├── tsconfig.json │ ├── tslint.json │ └── vendor │ │ └── yarn.js ├── essentials │ ├── package.json │ ├── src │ │ ├── analysis.ts │ │ ├── box.tsx │ │ ├── conditional.tsx │ │ ├── image.tsx │ │ ├── index.ts │ │ ├── link.tsx │ │ ├── page.tsx │ │ └── text.tsx │ └── tsconfig.json ├── message │ ├── index.d.ts │ ├── index.js │ ├── package.json │ ├── src │ │ ├── envelope.ts │ │ ├── index.ts │ │ ├── message.ts │ │ └── request-response.ts │ └── tsconfig.json ├── model-tree │ ├── index.d.ts │ ├── index.js │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── model-tree.ts │ └── tsconfig.json ├── model │ ├── index.d.ts │ ├── index.js │ ├── package.json │ ├── src │ │ ├── alva-app.ts │ │ ├── any-model.ts │ │ ├── design-time-user-store.ts │ │ ├── edit-history │ │ │ ├── edit-history-commit.ts │ │ │ ├── edit-history-stage.ts │ │ │ ├── edit-history.test.ts │ │ │ ├── edit-history.ts │ │ │ ├── index.ts │ │ │ └── revert.ts │ │ ├── element-action.ts │ │ ├── element │ │ │ ├── element-content.ts │ │ │ ├── element-property │ │ │ │ ├── element-property.ts │ │ │ │ └── index.ts │ │ │ ├── element.test.ts │ │ │ ├── element.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── library-data.ts │ │ ├── library-store-item.ts │ │ ├── library-store.ts │ │ ├── page │ │ │ ├── index.ts │ │ │ └── page.ts │ │ ├── pattern-library-file.ts │ │ ├── pattern-library │ │ │ ├── builtin-pattern-library.ts │ │ │ ├── index.ts │ │ │ └── pattern-library.ts │ │ ├── pattern-property │ │ │ ├── asset-property.ts │ │ │ ├── boolean-property.ts │ │ │ ├── enum-property.test.ts │ │ │ ├── enum-property.ts │ │ │ ├── event-handler-property.ts │ │ │ ├── href-property.ts │ │ │ ├── index.ts │ │ │ ├── number-property.test.ts │ │ │ ├── number-property.ts │ │ │ ├── property-base.ts │ │ │ ├── property.ts │ │ │ ├── string-property.test.ts │ │ │ ├── string-property.ts │ │ │ ├── unknown-property.test.ts │ │ │ └── unknown-property.ts │ │ ├── pattern-search.ts │ │ ├── pattern │ │ │ ├── index.ts │ │ │ ├── pattern-slot.ts │ │ │ └── pattern.ts │ │ ├── project.test.ts │ │ ├── project.ts │ │ ├── user-store-action │ │ │ ├── index.ts │ │ │ └── user-store-action.ts │ │ ├── user-store-enhancer.ts │ │ ├── user-store-property │ │ │ ├── index.ts │ │ │ └── user-store-property.ts │ │ ├── user-store-reference.ts │ │ └── user-store.ts │ └── tsconfig.json ├── site │ ├── package.json │ ├── src │ │ ├── cookie-notice.tsx │ │ ├── global-css.ts │ │ ├── graphik-web.ts │ │ ├── releases-data.ts │ │ ├── releases.tsx │ │ ├── render.ts │ │ └── site.tsx │ └── tsconfig.json ├── tools │ ├── alva-dependencies.js │ ├── alva-deploy.js │ ├── alva-release.js │ ├── alva-trigger.js │ ├── alva-version.js │ ├── package.json │ ├── src │ │ └── check-dependencies.ts │ └── tsconfig.json ├── types │ ├── index.d.ts │ ├── index.js │ ├── package.json │ ├── src │ │ ├── export.ts │ │ ├── hosts.ts │ │ ├── index.ts │ │ ├── menu.ts │ │ ├── mobx.ts │ │ ├── pattern-property.ts │ │ ├── persistence.ts │ │ ├── sender.ts │ │ ├── serialized-model.ts │ │ ├── server.ts │ │ ├── types.ts │ │ ├── updater.ts │ │ └── user-store.ts │ └── tsconfig.json └── util │ ├── index.d.ts │ ├── index.js │ ├── package.json │ ├── src │ ├── compute-difference.ts │ ├── drag-and-drop.ts │ ├── ensure-array.ts │ ├── index.ts │ ├── mkdirp.ts │ ├── new-issue-url.ts │ ├── noop.ts │ ├── parse-json.ts │ ├── set-search.ts │ ├── target.ts │ └── to-json.ts │ └── tsconfig.json ├── patternplate.config.js ├── renovate.json ├── tsconfig.json ├── tslint.json ├── webpack.config.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | continuation_indent_size = 1 4 | curly_bracket_next_line = false 5 | end_of_line = lf 6 | indent_style = tab 7 | insert_final_newline = true 8 | max_line_length = 100 9 | spaces_around_brackets = false 10 | spaces_around_operators = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.{yaml,yml,md}] 14 | indent_style = space 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | 19 | [package.json] 20 | indent_style = space 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | src/**/*.ts text eol=lf -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Device (please complete the following information):** 24 | - OS & Version: [e.g. macOS 10.13.4] 25 | - Version of Alva [e.g. 1.0.0] 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Additional context** 14 | Add any other context or screenshots about the feature request here. 15 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Proposed Changes 2 | Short description of the changes: 3 | * … 4 | 5 | ### Checklist 6 | - [ ] Added documentation of the changes 7 | 8 | ### Linked Issues 9 | Fixes #[Issue] 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | lib 3 | nccbuild 4 | dist 5 | node_modules 6 | **/vendor/alva-analyze.js 7 | **/vendor/alva-analyze.js.map 8 | *.log 9 | .DS_Store 10 | .static 11 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | .travis.yml 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "msjsdiag.debugger-for-chrome", 8 | "orta.vscode-jest", 9 | "eg2.tslint", 10 | "editorconfig.editorconfig" 11 | ], 12 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 13 | "unwantedRecommendations": [ 14 | 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | "jest.debugCodeLens.showWhenTestStateIn": [ 4 | "fail", 5 | "unknown", 6 | "pass", 7 | "skip" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.yarnclean: -------------------------------------------------------------------------------- 1 | monaco-editor/dev 2 | monaco-editor/min 3 | monaco-editor/min-maps 4 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | --install.ignore-engines true 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present SinnerSchrader Deutschland GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'header-max-length': [2, 'always', 100] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/contributors/RELEASE.md: -------------------------------------------------------------------------------- 1 | --- 2 | options: 3 | order: 2 4 | tags: 5 | - contributing 6 | --- 7 | 8 | # Release 9 | 10 | ## Build requirements: 11 | - For signing the executable for OSX the build needs to be run on OSX 12 | - Xcode is needed for building on OSX 13 | - Electron-builder dependencies: 14 | - glib 15 | ``` 16 | brew install glib 17 | ``` 18 | - snapcraft 19 | ``` 20 | brew install snapcraft 21 | ``` 22 | 23 | ## Build steps: 24 | 1. Run: 25 | ``` 26 | npm run dist 27 | ``` 28 | 2. Check if all the created executables are working correctly on every environment 29 | 3. Push the created commit and tag: 30 | ``` 31 | git push && git push origin 32 | ``` 33 | 4. Create a new release on Github 34 | 1. https://github.com/meetalva/alva/releases/new 35 | 2. Select the created tag 36 | 3. Title should be the tag name (Exp: v0.6.1) 37 | 4. Add the release notes from the changelog.md for the specific version in the description 38 | 5. New contributors should be mentioned additionally 39 | 5. Upload the files from the `upload-for-github-release` folders 40 | -------------------------------------------------------------------------------- /docs/contributors/contributing.md: -------------------------------------------------------------------------------- 1 | --- 2 | options: 3 | order: 96 4 | query: tags=contributing OR path=README.md OR path=CHANGELOG.md OR path=LICENSE.md 5 | --- 6 | 7 | # Contributing 8 | -------------------------------------------------------------------------------- /docs/contributors/develop-alva.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: 3 | - contributing 4 | --- 5 | 6 | # Howto: Develop Alva 7 | 8 | :woman_student: **Level**: Expert 9 | 10 | ## What to expect 11 | 12 | Shows how to build Alva from sources. Most likely 13 | this is interesting to you if you want to contribute 14 | code changes to Alva or try out features that are not 15 | released yet. 16 | 17 | **After following the steps below you have a working dev setup for Alva** 18 | 19 | ## Prerequesites 20 | 21 | * :evergreen_tree: [git](https://git-scm.com/downloads) 22 | * :turtle: [Node.js](https://nodejs.org/en/) `>= 8` 23 | * :cat2: [yarn](https://yarnpkg.com/en/) 24 | * :computer: A terminal emulator 25 | * :globe_with_meridians: Internet connection 26 | 27 | 28 | ## Fetch and prepare project 29 | 30 | Open a terminal and enter 31 | 32 | ```sh 33 | git clone https://github.com/meetalva/alva.git 34 | cd alva 35 | yarn 36 | ``` 37 | 38 | ## Start the build processes 39 | 40 | ``` 41 | yarn tsc -b -w 42 | ``` 43 | 44 | ## Start webpack 45 | In a second terminal window: 46 | 47 | ``` 48 | yarn webpack -w 49 | ``` 50 | 51 | ## Start Alva 52 | In a third terminal window: 53 | ```sh 54 | yarn alva # start electron version 55 | # alternatively: 56 | # yarn alva --host=node 57 | # yarn alva --host=static --serve 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/github.md: -------------------------------------------------------------------------------- 1 | --- 2 | displayName: Alva repo on GitHub 3 | options: 4 | href: https://github.com/meetalva/alva 5 | order: 100 6 | --- 7 | -------------------------------------------------------------------------------- /docs/gitter.md: -------------------------------------------------------------------------------- 1 | --- 2 | displayName: Chat with us 3 | options: 4 | href: https://gitter.im/meetalva/Lobby 5 | order: 101 6 | --- 7 | -------------------------------------------------------------------------------- /docs/guides-design.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: guides-design 3 | displayName: Prototyping with Alva 4 | options: 5 | query: tag=guide AND tag=design 6 | order: 3 7 | --- 8 | -------------------------------------------------------------------------------- /docs/guides-design/design-drafts.md: -------------------------------------------------------------------------------- 1 | --- 2 | displayName: 4. Add design drafts 3 | 4 | tags: 5 | - guide 6 | - design 7 | - rapid 8 | 9 | options: 10 | order: 4 11 | --- 12 | 13 | # Add Design Drafts 14 | 15 | --- 16 | 17 | In this guide you will learn how to integrate your freshest design drafts into your prototype. 18 | 19 | ## 1. Export a design component 20 | **Export** a design component from your favorite design tool – like Sketch, Figma or Photoshop – as SVG, PNG or JPG. 21 | 22 | ## 2. Add an Image Component 23 | Add the *Image* Component from the *Essentials* library and drag it onto the element list above. 24 | 25 | ## 3. Upload your design component 26 | After selecting the component, you’ll see an Image property on the right side. Click on "Select" and then select your exported file. 27 | 28 | ## 4. Set the image size 29 | You can adjust the size of your component and even define minimum or maximum sizes. For example, your component can be 80% width, but at maximum 1280px. 30 | 31 | --- 32 | -------------------------------------------------------------------------------- /docs/guides-dev.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: guides-dev 3 | displayName: Build for Alva 4 | options: 5 | query: tag=guide AND tag=dev 6 | order: 4 7 | --- 8 | -------------------------------------------------------------------------------- /docs/howtos.md: -------------------------------------------------------------------------------- 1 | --- 2 | displayName: Howto 3 | options: 4 | query: path*=*/howtos/* 5 | order: 1 6 | --- 7 | -------------------------------------------------------------------------------- /docs/legalnotice.md: -------------------------------------------------------------------------------- 1 | --- 2 | options: 3 | order: 98 4 | --- 5 | 6 | # Legal notice 7 | ## SinnerSchrader Deutschland GmbH 8 | Völckersstr. 38 9 | 22765 Hamburg 10 | Germany 11 | 12 | Tel.: +49 40 39 88 55-0 13 | Fax: +49 40 39 88 55-55 14 | info@sinnerschrader.de 15 | 16 | --- 17 | 18 | Management: 19 | Matthias Schrader (CEO), Jürgen Alker, Dr. Axel Averdung, Holger Blank, Thomas Dyckhoff, Dr. Lars Finke, Martin Gassner, Peggy Hutchinson 20 | 21 | Court of Registry: 22 | Amtsgericht Hamburg Registration Number – HRB 63663 23 | Sales Tax Identification Number: DE812160091 -------------------------------------------------------------------------------- /docs/references/glossary.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: 3 | - reference 4 | --- 5 | 6 | # Glossary 7 | 8 | ## Element 9 | 10 | An instance inside Alvas Element tree. By configuring an Element's [properties](#property) the content, display or behaviour of the element may be controlled. Elements contain the information where which data should be rendered according to which [pattern](#pattern). 11 | 12 | ## Library 13 | 14 | A collection of code [Patterns](#pattern) that can be analyzed and used by Alva. This means a [NPM package](https://docs.npmjs.com/getting-started/packages#what-is-a-package-) that provides React component imlementations and TypeScript typings. 15 | 16 | ## Pattern 17 | 18 | The React component implementation and API description required for Alva to detect, configure and render parts of the prototype with. A pattern contains the information which [Properties](#property) are configurable with which data types for [Element](#element) instances created from it. 19 | -------------------------------------------------------------------------------- /docs/references/reference.md: -------------------------------------------------------------------------------- 1 | --- 2 | displayName: Reference 3 | options: 4 | query: tags=reference 5 | order: 5 6 | --- 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testMatch: ['**/*.test.ts', '**/*.test.tsx'], 5 | modulePathIgnorePatterns: ['\/packages\/core\/nccbuild\/(.*)'], 6 | roots: [ 7 | 'packages/analyzer', 8 | 'packages/components', 9 | 'packages/core', 10 | 'packages/essentials', 11 | 'packages/message', 12 | 'packages/model', 13 | 'packages/model-tree', 14 | 'packages/site', 15 | 'packages/tools', 16 | 'packages/types', 17 | 'packages/util' 18 | ], 19 | testPathIgnorePatterns: ['node_modules', 'lib'], 20 | globals: { 21 | 'ts-jest': { 22 | diagnostics: { 23 | ignoreCodes: [151001, 2339] 24 | } 25 | } 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "useWorkspaces": true, 4 | "version": "0.8.1" 5 | } 6 | -------------------------------------------------------------------------------- /packages/analyzer-cli/bin/analyze.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/bin/analyze'); 3 | -------------------------------------------------------------------------------- /packages/analyzer-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meetalva/analyzer-cli", 3 | "version": "1.0.0", 4 | "description": "Derive component meta data and bundles from React projects", 5 | "bin": { 6 | "alva-analyze": "./bin/analyze" 7 | }, 8 | "scripts": { 9 | "check:dependencies": "alva-dependencies -i typescript-eslint-parser" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/meetalva/alva.git" 14 | }, 15 | "author": { 16 | "email": "hey@meetalva.io", 17 | "name": "Meet Alva Team", 18 | "url": "https://meetalva.io/" 19 | }, 20 | "license": "MIT", 21 | "homepage": "https://meetalva.github.io/", 22 | "bugs": { 23 | "url": "https://github.com/meetalva/alva/issues" 24 | }, 25 | "devDependencies": { 26 | "@meetalva/tools": "1.0.0" 27 | }, 28 | "dependencies": { 29 | "@meetalva/analyzer": "1.0.0", 30 | "@meetalva/types": "1.0.0", 31 | "@types/node": "^10.12.18", 32 | "yargs-parser": "10.1.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/analyzer-cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [ 3 | { 4 | "path": "../analyzer" 5 | }, 6 | { 7 | "path": "../types" 8 | }, 9 | { 10 | "path": "../model" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "composite": true, 15 | "outDir": "lib", 16 | "skipLibCheck": true, 17 | "lib": ["es2017"], 18 | "experimentalDecorators": true, 19 | "moduleResolution": "node", 20 | "module": "commonjs", 21 | "rootDir": "src", 22 | "target": "es2017", 23 | "jsx": "react", 24 | "strict": true, 25 | "sourceMap": true, 26 | "declarationMap": true 27 | }, 28 | "include": ["src"] 29 | } 30 | -------------------------------------------------------------------------------- /packages/analyzer/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib'; 2 | -------------------------------------------------------------------------------- /packages/analyzer/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /packages/analyzer/src/compiler/compiler-safe-name.ts: -------------------------------------------------------------------------------- 1 | export function compilerSafeName(id: string): string { 2 | return encodeURIComponent( 3 | id 4 | .split('@')[0] 5 | .split('/') 6 | .join('-') 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /packages/analyzer/src/compiler/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-compiler'; 2 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/enum-member-name.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | enum TestStringEnum { 4 | /** @name I */ 5 | One, 6 | /** @name II */ 7 | Two 8 | } 9 | 10 | export interface Props { 11 | thing: TestStringEnum; 12 | } 13 | 14 | export const Component: React.SFC = () => null; 15 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/enum.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | enum TestStringEnum { 4 | One, 5 | Two 6 | } 7 | 8 | export interface Props { 9 | thing: TestStringEnum; 10 | } 11 | 12 | export const Component: React.SFC = () => null; 13 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/export-infos-class-metadata.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | /** 4 | * @name New Name 5 | */ 6 | export class BasicComponentClass extends React.Component { 7 | public render(): JSX.Element { 8 | return
Hello World
; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/export-infos-class.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export class BasicComponentClass extends React.Component { 4 | public render(): JSX.Element { 5 | return
Hello World
; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/export-infos-default-metadata.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const BasicComponent: React.SFC = () => { 4 | return
Hello World
; 5 | }; 6 | 7 | /** 8 | * @name New Name 9 | */ 10 | export default BasicComponent; 11 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/export-infos-default.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const BasicComponent: React.SFC = () => { 4 | return
Hello World
; 5 | }; 6 | 7 | export default BasicComponent; 8 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/export-infos-export-declaration-metadata.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export declare const BasicComponent: React.SFC; 4 | /** 5 | * @name New Name 6 | */ 7 | export default BasicComponent; 8 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/export-infos-export-declaration-metadata.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const BasicComponent = () => { 4 | return
Hello World
; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/export-infos-export-declaration.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export declare const BasicComponent: React.SFC; 4 | export default BasicComponent; 5 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/export-infos-export-declaration.js: -------------------------------------------------------------------------------- 1 | 2 | import * as React from 'react'; 3 | 4 | export const BasicComponent = () => { 5 | return
Hello World
; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/export-infos-variable-metadata.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | /** 4 | * @name New Name 5 | */ 6 | export const BasicComponent: React.SFC = () => { 7 | return
Hello World
; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/export-infos-variable.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const BasicComponent: React.SFC = () => { 4 | return
Hello World
; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/prop-any.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface Props { 4 | // tslint:disable-next-line:no-any 5 | doAnyThing: any; // :trollface: 6 | } 7 | 8 | export const Component: React.SFC = () => null; 9 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/prop-event-function.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:prefer-method-signature 2 | // tslint:disable:no-any 3 | import * as React from 'react'; 4 | 5 | export interface Props { 6 | doFunctionClickThing: (e: React.MouseEvent) => void; 7 | } 8 | 9 | export const Component: React.SFC = () => null; 10 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/prop-event-handler.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface Props { 4 | // tslint:disable-next-line:no-any 5 | doEventHandlerThing: React.ChangeEventHandler; 6 | } 7 | 8 | export const Component: React.SFC = () => null; 9 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/prop-event-method.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface Props { 4 | // tslint:disable-next-line:no-any 5 | doMethodClickThing(e: React.MouseEvent): void; 6 | } 7 | 8 | export const Component: React.SFC = () => null; 9 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/prop-generic-event-handler.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface Props { 4 | // tslint:disable-next-line:no-any 5 | doEventHandlerThing: React.EventHandler>; 6 | } 7 | 8 | export const Component: React.SFC = () => null; 9 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/prop-slots.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface PropsWithSlots { 4 | reactNode: React.ReactNode; 5 | reactNodeArray: React.ReactNode[]; 6 | explicitReactNodeArray: React.ReactNodeArray; 7 | reactChild: React.ReactChild; 8 | reactChildArray: React.ReactChild[]; 9 | reactElement: React.ReactElement; 10 | reactElementArray: React.ReactElement[]; 11 | jsxElement: JSX.Element; 12 | jsxElementArray: JSX.Element[]; 13 | union: React.ReactChild | React.ReactElement | JSX.Element | string; 14 | unionArray: (React.ReactChild | React.ReactElement | JSX.Element | string)[]; 15 | disjunct: string | any; 16 | disjunctArray: string[]; 17 | } 18 | 19 | export const ReactElement: React.SFC = () => null; 20 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/shared-properties-ab.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface A { 4 | a: string; 5 | } 6 | 7 | export interface B extends A { 8 | b: string; 9 | } 10 | 11 | export const ComponentOne: React.SFC = () => null; 12 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/shared-properties-c.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { A } from './shared-properties-ab'; 3 | 4 | export interface C extends A { 5 | c: string; 6 | } 7 | 8 | export const ComponentTwo: React.SFC = () => null; 9 | -------------------------------------------------------------------------------- /packages/analyzer/src/fixtures/string-enum.ts: -------------------------------------------------------------------------------- 1 | export enum TestStringEnum { 2 | One = 'one', 3 | Two = 'two' 4 | } 5 | -------------------------------------------------------------------------------- /packages/analyzer/src/id-hasher.ts: -------------------------------------------------------------------------------- 1 | import * as Crypto from 'crypto'; 2 | 3 | export function getGlobalPatternId(contextId: string): string { 4 | return getId(contextId); 5 | } 6 | 7 | export function getGlobalPropertyId(patternId: string, contextId: string): string { 8 | return getNestedId(patternId, contextId); 9 | } 10 | 11 | export function getGlobalEnumOptionId(enumId: string, contextId: string): string { 12 | return getNestedId(enumId, contextId); 13 | } 14 | 15 | export function getGlobalSlotId(patternId: string, contextId: string): string { 16 | return getNestedId(patternId, contextId); 17 | } 18 | 19 | function getId(contextId: string): string { 20 | const hash = Crypto.createHash('sha1'); 21 | hash.update(contextId); 22 | return hash.digest('hex'); 23 | } 24 | 25 | function getNestedId(parentId: string, contextId: string): string { 26 | const hash = Crypto.createHash('sha1'); 27 | hash.update([parentId, contextId].join(':')); 28 | return hash.digest('hex'); 29 | } 30 | -------------------------------------------------------------------------------- /packages/analyzer/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typescript-react-analyzer'; 2 | export * from './get-package'; 3 | import * as IdHasher from './id-hasher'; 4 | export { IdHasher }; 5 | -------------------------------------------------------------------------------- /packages/analyzer/src/react-utils/find-react-component-type.ts: -------------------------------------------------------------------------------- 1 | import { isReactComponentType } from './is-react-component-type'; 2 | import * as TypeScript from 'typescript'; 3 | import { TypeScriptType } from '../typescript-utils'; 4 | 5 | /** 6 | * Takes a type and tries to resolve it to a well-known (Alva supported) React component type, 7 | * walking through all of its base types and checking against a list of known types. 8 | * @param program The TypeScript program containing all styleguide declarations. 9 | * @param type The type to resolve to a React component type. 10 | * @return The well-known (Alva supported) React component type, or undefined if the given type cannot be resolved to one. 11 | */ 12 | export function findReactComponentType( 13 | type: TypeScriptType, 14 | ctx: { program: TypeScript.Program } 15 | ): TypeScriptType | undefined { 16 | if (isReactComponentType(type.type, ctx)) { 17 | return type; 18 | } 19 | 20 | for (const baseType of type.getBaseTypes()) { 21 | const wellKnownReactType = findReactComponentType(baseType, ctx); 22 | 23 | if (wellKnownReactType) { 24 | return wellKnownReactType; 25 | } 26 | } 27 | 28 | return; 29 | } 30 | -------------------------------------------------------------------------------- /packages/analyzer/src/react-utils/get-react-slot-type.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-bitwise 2 | import * as TypeScript from 'typescript'; 3 | import { isTypeReference } from '../typescript-utils/is-type-reference'; 4 | 5 | const REACT_SLOT_TYPES_SINGLE = ['Element', 'ReactElement', 'ReactChild']; 6 | 7 | export function getReactSlotType( 8 | type: TypeScript.Type, 9 | ctx: { program: TypeScript.Program } 10 | ): string | undefined { 11 | const typechecker = ctx.program.getTypeChecker(); 12 | const symbol = type.aliasSymbol || type.symbol || type.getSymbol(); 13 | 14 | if (!symbol) { 15 | return; 16 | } 17 | 18 | const resolvedSymbol = 19 | (symbol.flags & TypeScript.SymbolFlags.AliasExcludes) === TypeScript.SymbolFlags.AliasExcludes 20 | ? typechecker.getAliasedSymbol(symbol) 21 | : symbol; 22 | 23 | if (resolvedSymbol.name === 'Array' && isTypeReference(type) && type.typeArguments) { 24 | const arg = type.typeArguments[0]!; 25 | 26 | if (!arg) { 27 | return; 28 | } 29 | 30 | return getReactSlotType(arg, ctx); 31 | } 32 | 33 | if (REACT_SLOT_TYPES_SINGLE.includes(resolvedSymbol.name)) { 34 | return 'single'; 35 | } 36 | 37 | return 'multiple'; 38 | } 39 | -------------------------------------------------------------------------------- /packages/analyzer/src/react-utils/has-react-typings.ts: -------------------------------------------------------------------------------- 1 | import * as TypeScript from 'typescript'; 2 | 3 | export function hasReactTypings(declarations: TypeScript.Declaration[]): boolean { 4 | return declarations 5 | .map(decl => decl.getSourceFile()) 6 | .some(sourceFile => sourceFile.fileName.endsWith('react/index.d.ts')); 7 | } 8 | -------------------------------------------------------------------------------- /packages/analyzer/src/react-utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './find-react-component-type'; 2 | export * from './is-react-event-handler-type'; 3 | export * from './is-react-slot-type'; 4 | export * from './get-react-slot-type'; 5 | export * from './get-event-type'; 6 | -------------------------------------------------------------------------------- /packages/analyzer/src/react-utils/is-react-component-type.ts: -------------------------------------------------------------------------------- 1 | import { hasReactTypings } from './has-react-typings'; 2 | import * as TypeScript from 'typescript'; 3 | 4 | const REACT_COMPONENT_TYPES = [ 5 | 'Component', 6 | 'ComponentClass', 7 | 'PureComponent', 8 | 'StatelessComponent', 9 | 'ComponentType', 10 | 'FunctionComponent' 11 | ]; 12 | 13 | export function isReactComponentType( 14 | type: TypeScript.Type, 15 | ctx: { program: TypeScript.Program } 16 | ): boolean { 17 | const symbol = type.getSymbol() || type.aliasSymbol; 18 | 19 | if (!symbol) { 20 | return false; 21 | } 22 | 23 | if (!REACT_COMPONENT_TYPES.includes(symbol.name)) { 24 | return false; 25 | } 26 | 27 | const declarations = symbol.getDeclarations(); 28 | 29 | if (!declarations) { 30 | return false; 31 | } 32 | 33 | return hasReactTypings(declarations); 34 | } 35 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-react-analyzer/fixtures/props-comments.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface PropsWithSlots { 4 | /** Some basic comment */ 5 | plainComment: string; 6 | /** 7 | * @default 8 | * ``` 9 | * import { Text } from '@meetalva/essentials'; 10 | * ``` 11 | */ 12 | atSign: React.ReactNode; 13 | /** 14 | * @default 15 | * ~~~ 16 | * import { Text } from '@meetalva/essentials'; 17 | * ~~~ 18 | */ 19 | atSignTilde: React.ReactNode; 20 | } 21 | 22 | export const ReactElement: React.SFC = () => null; 23 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-react-analyzer/fixtures/props-slot-defaults.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface PropsWithSlots { 4 | /** 5 | * @default 6 | * ```tsx 7 | * import * as React from 'react'; 8 | * import { Text } from '@meetalva/essentials'; 9 | * export default () => 10 | * ``` 11 | **/ 12 | backtickBlock: string; 13 | /** 14 | * @default 15 | * ~~~tsx 16 | * import * as React from 'react'; 17 | * import { Text } from '@meetalva/essentials'; 18 | * export default () => 19 | * ~~~ 20 | **/ 21 | tildeBlock: string; 22 | } 23 | 24 | export const ReactElement: React.SFC = () => null; 25 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-react-analyzer/fixtures/slot-analyzer-fenced-tick.md: -------------------------------------------------------------------------------- 1 | @default 2 | ```tsx 3 | export default () =>
Hello, World
4 | ``` 5 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-react-analyzer/fixtures/slot-analyzer-fenced-tilde.md: -------------------------------------------------------------------------------- 1 | @default 2 | ~~~tsx 3 | export default () =>
Hello, World
4 | ~~~ 5 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-react-analyzer/fixtures/slot-analyzer-fenced-whitespace.md: -------------------------------------------------------------------------------- 1 | @default 2 | 3 | export default () =>
Hello, World
4 | 5 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-react-analyzer/fixtures/slot-analyzer-last.md: -------------------------------------------------------------------------------- 1 | @default 2 | ~~~ 3 | First 4 | ~~~ 5 | 6 | @default 7 | ~~~ 8 | Second 9 | ~~~ 10 | 11 | @default 12 | ~~~ 13 | Last 14 | ~~~ 15 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-react-analyzer/fixtures/slot-analyzer-other-tags.md: -------------------------------------------------------------------------------- 1 | @other 2 | ~~~ 3 | First 4 | ~~~ 5 | 6 | @ignore 7 | ~~~ 8 | Second 9 | ~~~ 10 | 11 | @tag 12 | ~~~ 13 | Last 14 | ~~~ 15 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-react-analyzer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typescript-react-analyzer'; 2 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-react-analyzer/property-analyzer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './property-analyzer'; 2 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-react-analyzer/property-analyzer/property-creators.ts: -------------------------------------------------------------------------------- 1 | export * from './create-enum-property'; 2 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-react-analyzer/property-analyzer/types.ts: -------------------------------------------------------------------------------- 1 | import * as Ts from 'typescript'; 2 | 3 | export interface PropertyAnalyzeContext { 4 | program: Ts.Program; 5 | getEnumOptionId(enumId: string, contextId: string): string; 6 | getPropertyId(contextId: string): string; 7 | } 8 | 9 | export interface PropertyInit { 10 | symbol: Ts.Symbol; 11 | type: Ts.Type; 12 | typechecker: Ts.TypeChecker; 13 | } 14 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-utils/find-declaration.ts: -------------------------------------------------------------------------------- 1 | import * as TypeScript from 'typescript'; 2 | 3 | export function findDeclaration( 4 | expression: TypeScript.Expression 5 | ): TypeScript.Declaration | undefined { 6 | const sourceFile = expression.getSourceFile(); 7 | 8 | if (!sourceFile) { 9 | return; 10 | } 11 | 12 | return sourceFile.statements 13 | .filter(statement => TypeScript.isVariableStatement(statement)) 14 | .reduce( 15 | (result, statement: any) => [...result, ...statement.declarationList.declarations], 16 | [] 17 | ) 18 | .find( 19 | (declaration: TypeScript.Declaration) => 20 | (declaration as any).name.getText() === expression.getText() 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-utils/find-type-declaration.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-bitwise 2 | import * as TypeScript from 'typescript'; 3 | 4 | export function findTypeDeclaration( 5 | symbol: TypeScript.Symbol, 6 | ctx: { typechecker: TypeScript.TypeChecker } 7 | ): TypeScript.Declaration | undefined { 8 | const resolved = 9 | symbol.flags & TypeScript.SymbolFlags.AliasExcludes 10 | ? ctx.typechecker.getAliasedSymbol(symbol) 11 | : symbol; 12 | 13 | if (resolved.valueDeclaration) { 14 | return resolved.valueDeclaration; 15 | } 16 | 17 | const [declaration = null] = resolved.getDeclarations() || []; 18 | 19 | if (declaration) { 20 | return declaration; 21 | } 22 | 23 | return; 24 | } 25 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-utils/get-exported-node.test.ts: -------------------------------------------------------------------------------- 1 | import * as Tsa from 'ts-simple-ast'; 2 | import { getExportedNode } from './get-exported-node'; 3 | 4 | const fixtures = require('fixturez')(__dirname); 5 | 6 | let project: Tsa.Project; 7 | let sourceFile: Tsa.SourceFile; 8 | 9 | beforeAll(() => { 10 | const fixturePath = fixtures.find('children.tsx'); 11 | project = new Tsa.Project(); 12 | project.addExistingSourceFile(fixturePath); 13 | sourceFile = project.getSourceFileOrThrow('children.tsx'); 14 | }); 15 | 16 | test('extracts class correctly', () => { 17 | const basicClass = sourceFile.getClass('BasicClassComponent')!; 18 | const actual = getExportedNode(basicClass.getSymbol()); 19 | 20 | expect(actual).not.toBeUndefined(); 21 | expect(actual).toBe(basicClass.getSymbol()!.getDeclarations()[0]); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-utils/get-exported-node.ts: -------------------------------------------------------------------------------- 1 | import * as Tsa from 'ts-simple-ast'; 2 | 3 | export function getExportedNode(symbol: Tsa.Symbol | undefined): Tsa.Node | undefined { 4 | if (!symbol) { 5 | return; 6 | } 7 | 8 | const decl = symbol.getDeclarations()[0]; 9 | 10 | if (!decl) { 11 | return; 12 | } 13 | 14 | if (Tsa.TypeGuards.isExportAssignment(decl)) { 15 | const expr = decl.getExpression(); 16 | 17 | return Tsa.TypeGuards.isIdentifier(expr) 18 | ? expr.getDefinitionNodes()[0] 19 | : symbol && symbol.getValueDeclaration(); 20 | } 21 | 22 | return decl; 23 | } 24 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-utils/get-exports.ts: -------------------------------------------------------------------------------- 1 | import { getExportInfos } from './get-export-infos'; 2 | import { isExport } from './is-export'; 3 | import * as TypeScript from 'typescript'; 4 | import { TypescriptExport } from './typescript-export'; 5 | 6 | export function getExports( 7 | sourceFile: TypeScript.SourceFile, 8 | program: TypeScript.Program 9 | ): TypescriptExport[] { 10 | return sourceFile.statements 11 | .filter(isExport) 12 | .reduce( 13 | (acc, exportStatement) => [...acc, ...getExportInfos(program, exportStatement)], 14 | [] 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-utils/get-tsa-export.ts: -------------------------------------------------------------------------------- 1 | import * as Path from 'path'; 2 | import * as Tsa from 'ts-simple-ast'; 3 | import { TypescriptExport } from './typescript-export'; 4 | import { setExtname } from './set-extname'; 5 | 6 | export function getTsaExport( 7 | ex: TypescriptExport, 8 | { project }: { project: Tsa.Project } 9 | ): Tsa.Symbol | undefined { 10 | const declPath = (ex.statement.getSourceFile() as any).path; 11 | const sourcePath = setExtname(declPath, '.ts'); 12 | const sourcePathTSX = setExtname(declPath, '.tsx'); 13 | 14 | const compilerOptions = project.getCompilerOptions(); 15 | const relSourcePath = Path.relative((compilerOptions.outDir || '').toLowerCase(), sourcePath); 16 | const relSourcePathTSX = Path.relative( 17 | (compilerOptions.outDir || '').toLowerCase(), 18 | sourcePathTSX 19 | ); 20 | 21 | const sourceFile = project 22 | .getSourceFiles() 23 | .find( 24 | f => f.getFilePath().endsWith(relSourcePath) || f.getFilePath().endsWith(relSourcePathTSX) 25 | ); 26 | 27 | return sourceFile 28 | ? sourceFile.getExportSymbols().find(s => s.getName() === (ex.exportName || 'default')) 29 | : undefined; 30 | } 31 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './find-type-declaration'; 2 | export * from './get-exports'; 3 | export * from './is-export'; 4 | export * from './jsdoc'; 5 | export * from './typescript-export'; 6 | export * from './typescript-type'; 7 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-utils/is-export.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-bitwise 2 | import * as TypeScript from 'typescript'; 3 | 4 | export function isExport(node: TypeScript.Node): boolean { 5 | if ( 6 | TypeScript.isExportAssignment(node) || 7 | TypeScript.isExportDeclaration(node) || 8 | TypeScript.isExportSpecifier(node) 9 | ) { 10 | return true; 11 | } 12 | 13 | if (!node.modifiers) { 14 | return false; 15 | } 16 | 17 | // tslint:disable-next-line:no-any 18 | const modifiers = TypeScript.getCombinedModifierFlags(node as any); 19 | 20 | if ((modifiers & TypeScript.ModifierFlags.Export) === TypeScript.ModifierFlags.Export) { 21 | return true; 22 | } 23 | 24 | return false; 25 | } 26 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-utils/is-type-reference.ts: -------------------------------------------------------------------------------- 1 | import * as TypeScript from 'typescript'; 2 | 3 | export function isTypeReference(t: TypeScript.Type): t is TypeScript.TypeReference { 4 | if (!isObjectType(t)) { 5 | return false; 6 | } 7 | 8 | // tslint:disable-next-line:no-bitwise 9 | return (t.objectFlags & TypeScript.ObjectFlags.Reference) === TypeScript.ObjectFlags.Reference; 10 | } 11 | 12 | function isObjectType(t: TypeScript.Type): t is TypeScript.ObjectType { 13 | // tslint:disable-next-line:no-bitwise 14 | return (t.flags & TypeScript.TypeFlags.Object) === TypeScript.TypeFlags.Object; 15 | } 16 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-utils/is-union-type.ts: -------------------------------------------------------------------------------- 1 | import * as TypeScript from 'typescript'; 2 | 3 | export function isUnionType(t: TypeScript.Type): t is TypeScript.UnionType { 4 | // tslint:disable-next-line:no-bitwise 5 | return (t.flags & TypeScript.TypeFlags.Union) === TypeScript.TypeFlags.Union; 6 | } 7 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-utils/jsdoc.ts: -------------------------------------------------------------------------------- 1 | import * as TypeScript from 'typescript'; 2 | 3 | export function getJsDocValueFromNode(node: TypeScript.Node, tagName: string): string | undefined { 4 | const jsdocTag = (TypeScript.getJSDocTags(node) || []).find( 5 | tag => tag.tagName && tag.tagName.text === tagName 6 | ); 7 | return jsdocTag ? (jsdocTag.comment || '').trim() : undefined; 8 | } 9 | 10 | export function getJsDocValueFromSymbol( 11 | symbol: TypeScript.Symbol, 12 | tagName: string 13 | ): string | undefined { 14 | const jsdocTag = symbol.getJsDocTags().find(tag => tag.name === tagName); 15 | return jsdocTag ? (jsdocTag.text || '').trim() : undefined; 16 | } 17 | 18 | export function hasJsDocTagFromSymbol(symbol: TypeScript.Symbol, tagName: string): boolean { 19 | return symbol.getJsDocTags().some(tag => tag.name === tagName); 20 | } 21 | -------------------------------------------------------------------------------- /packages/analyzer/src/typescript-utils/set-extname.ts: -------------------------------------------------------------------------------- 1 | import * as Path from 'path'; 2 | 3 | export function setExtname(input: string, ext: string): string { 4 | const candidate = Path.basename(input, Path.extname(input)); 5 | 6 | const basename = 7 | Path.extname(candidate) === '.d' 8 | ? Path.basename(candidate, Path.extname(candidate)) 9 | : candidate; 10 | 11 | return Path.join(Path.dirname(input), `${basename}${ext}`); 12 | } 13 | -------------------------------------------------------------------------------- /packages/analyzer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [ 3 | { 4 | "path": "../util" 5 | }, 6 | { 7 | "path": "../types" 8 | }, 9 | { 10 | "path": "../model" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "composite": true, 15 | "outDir": "lib", 16 | "skipLibCheck": true, 17 | "lib": ["es2017"], 18 | "experimentalDecorators": true, 19 | "moduleResolution": "node", 20 | "module": "commonjs", 21 | "rootDir": "src", 22 | "target": "es2017", 23 | "jsx": "react", 24 | "strict": true, 25 | "sourceMap": true, 26 | "declarationMap": true 27 | }, 28 | "include": ["src"] 29 | } 30 | -------------------------------------------------------------------------------- /packages/components/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib'; 2 | -------------------------------------------------------------------------------- /packages/components/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /packages/components/src/add-button/demo.tsx: -------------------------------------------------------------------------------- 1 | import DemoContainer from '../demo-container'; 2 | import * as React from 'react'; 3 | import { AddButton } from '.'; 4 | 5 | const AddButtonDemo: React.StatelessComponent = (): JSX.Element => ( 6 | 7 | Add Page 8 | 9 | ); 10 | 11 | export default AddButtonDemo; 12 | -------------------------------------------------------------------------------- /packages/components/src/add-button/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "add-button", 3 | "description": "Button to add a page or library", 4 | "displayName": "Add Button", 5 | "version": "1.0.0", 6 | "tags": ["atom", "button"], 7 | "flag": "alpha" 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/badge-icon/demo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import DemoContainer from '../demo-container'; 3 | import { BadgeIcon } from './'; 4 | import { Color } from '../colors'; 5 | 6 | export default (): JSX.Element => ( 7 | 8 | 3 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /packages/components/src/badge-icon/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import { Color } from '../colors'; 4 | 5 | export interface BadgeIconProps { 6 | color: Color; 7 | } 8 | 9 | const StyledBadge = styled.div` 10 | display: flex; 11 | height: 15px; 12 | width: 15px; 13 | padding-top: 1px; 14 | box-sizing: border-box; 15 | justify-content: center; 16 | font-weight: bold; 17 | font-size: 10px; 18 | border-radius: 50%; 19 | background: ${(props: BadgeIconProps) => props.color}; 20 | color: ${Color.White}; 21 | `; 22 | 23 | export const BadgeIcon: React.StatelessComponent = props => { 24 | return {props.children}; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/components/src/badge-icon/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "badge-icon", 3 | "displayName": "Badge Icon", 4 | "description": "Counts e.g. notifications", 5 | "flag": "alpha", 6 | "version": "1.0.0", 7 | "tags": ["atom"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/button/demo.tsx: -------------------------------------------------------------------------------- 1 | import DemoContainer from '../demo-container'; 2 | import { Button, ButtonOrder, ButtonSize } from './index'; 3 | import * as React from 'react'; 4 | 5 | const ButtonDemo: React.StatelessComponent = (): JSX.Element => ( 6 | 7 | 8 | 11 | 14 | 15 | 16 | 19 | 22 | 23 | 24 | 27 | 30 | 31 | ); 32 | 33 | export default ButtonDemo; 34 | -------------------------------------------------------------------------------- /packages/components/src/button/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "button", 3 | "displayName": "Button", 4 | "description": "Weighted Interaction", 5 | "version": "1.0.0", 6 | "tags": ["atom", "button"], 7 | "flag": "alpha" 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/chrome-button/demo.tsx: -------------------------------------------------------------------------------- 1 | import { ChromeButton } from './index'; 2 | import * as React from 'react'; 3 | 4 | const ChromeButtonDemo: React.StatelessComponent = (): JSX.Element => ( 5 | 6 | ); 7 | 8 | export default ChromeButtonDemo; 9 | -------------------------------------------------------------------------------- /packages/components/src/chrome-button/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome-button", 3 | "displayName": "Chrome Button", 4 | "description": "Button in the chrome", 5 | "version": "1.0.0", 6 | "tags": ["chrome-button"], 7 | "flag": "alpha" 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/chrome/demo.tsx: -------------------------------------------------------------------------------- 1 | import { CopySize } from '../copy'; 2 | import DemoContainer from '../demo-container'; 3 | import { Chrome } from './index'; 4 | import * as React from 'react'; 5 | import { ViewSwitch } from '../view-switch'; 6 | 7 | const DemoChrome: React.StatelessComponent = () => ( 8 | 9 | 10 | null} 13 | onRightClick={() => null} 14 | leftVisible={true} 15 | rightVisible={true} 16 | > 17 | Page Name 18 | 19 | null} 22 | onRightClick={() => null} 23 | leftVisible={true} 24 | rightVisible={true} 25 | > 26 | Page Name 27 | 28 | 29 | 30 | ); 31 | 32 | export default DemoChrome; 33 | -------------------------------------------------------------------------------- /packages/components/src/chrome/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome", 3 | "description": "Custom title bar", 4 | "displayName": "Chrome", 5 | "version": "1.0.0", 6 | "flag": "alpha" 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/colors/index.tsx: -------------------------------------------------------------------------------- 1 | export enum Color { 2 | Black = 'rgb(1, 12, 22)', 3 | BlackAlpha15 = 'rgba(0, 0, 0, .15)', 4 | BlackAlpha50 = 'rgba(0, 0, 0, .5)', 5 | Blue = 'rgb(0, 112, 214)', 6 | BlueAlpha40 = 'rgba(0, 112, 214, .4)', 7 | Blue20 = 'rgb(51, 141, 222)', 8 | Blue40 = 'rgb(102, 169, 230)', 9 | Blue60 = 'rgb(153, 198, 239)', 10 | Blue80 = 'rgb(212, 226, 242)', 11 | Blue90 = 'rgb(222, 236, 252)', 12 | Green = 'rgb(50, 180, 150)', 13 | SignalGreen = 'rgb(31, 163, 133)', 14 | Grey10 = 'rgb(30, 32, 36)', 15 | Grey20 = 'rgb(52, 61, 69)', 16 | Grey36 = 'rgb(92, 92, 92)', 17 | Grey50 = 'rgb(103, 109, 115)', 18 | Grey60 = 'rgb(153, 158, 162)', 19 | Grey80 = 'rgb(204, 206, 208)', 20 | Grey90 = 'rgb(229, 230, 231)', 21 | Grey97 = 'rgb(247, 247, 247)', 22 | Orange = 'rgb(255, 127, 115)', 23 | Yellow = 'rgb(251, 186, 80)', 24 | Red = 'rgb(187, 50, 94)', 25 | White = 'rgb(255, 255, 255)', 26 | WhiteAlpha75 = 'rgba(255, 255, 255, .75)' 27 | } 28 | -------------------------------------------------------------------------------- /packages/components/src/colors/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colors", 3 | "displayName": "Colors", 4 | "version": "1.0.0", 5 | "tags": ["natural-laws"], 6 | "flag": "alpha" 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/copy/demo.tsx: -------------------------------------------------------------------------------- 1 | import { Copy, CopySize } from './index'; 2 | import DemoContainer from '../demo-container'; 3 | import * as React from 'react'; 4 | 5 | const LOREM = ` 6 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 7 | In imperdiet eleifend ante. Pellentesque ut quam ac magna suscipit tincidunt nec nec magna. 8 | Vivamus sit amet tellus quis nibh lobortis eleifend a et massa. 9 | Vestibulum tempor erat mi, ac convallis sapien fermentum aliquet. 10 | Nam ornare, nisi venenatis ultrices iaculis, massa nisl lobortis neque, 11 | at fermentum tellus dui quis purus. 12 | Vestibulum tincidunt dui turpis, sed iaculis dolor venenatis sit amet. 13 | `; 14 | 15 | const CopyDemo: React.StatelessComponent = (): JSX.Element => ( 16 | 17 | {LOREM} 18 | {LOREM} 19 | 20 | ); 21 | 22 | export default CopyDemo; 23 | -------------------------------------------------------------------------------- /packages/components/src/copy/index.tsx: -------------------------------------------------------------------------------- 1 | import { Color } from '../colors'; 2 | import * as React from 'react'; 3 | import styled from 'styled-components'; 4 | 5 | export interface CopyProps { 6 | size?: CopySize; 7 | textColor?: Color; 8 | children?: React.ReactNode; 9 | cut?: boolean; 10 | className?: string; 11 | } 12 | 13 | export interface StyledCopyProps { 14 | size?: CopySize; 15 | textColor?: Color; 16 | cut?: boolean; 17 | } 18 | 19 | export enum CopySize { 20 | S = 12, 21 | M = 15 22 | } 23 | 24 | const StyledCopy = 25 | styled.p < 26 | StyledCopyProps > 27 | ` 28 | margin: 0; 29 | line-height: 1.4; 30 | cursor: default; 31 | ${props => (typeof props.size !== 'undefined' ? `font-size: ${props.size}px;` : 'font-size: 12px')}; 32 | ${props => (typeof props.textColor !== 'undefined' ? `color: ${props.textColor};` : '')}; 33 | white-space: ${props => (props.cut ? 'nowrap' : 'normal')}; 34 | overflow: ${props => (props.cut ? 'hidden' : 'auto')}; 35 | text-overflow: ${props => (props.cut ? 'ellipsis' : 'clip')}; 36 | `; 37 | 38 | export const Copy: React.StatelessComponent = props => ( 39 | 45 | {props.children} 46 | 47 | ); 48 | -------------------------------------------------------------------------------- /packages/components/src/copy/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "copy", 3 | "displayName": "Copy", 4 | "description": "Basic text style", 5 | "version": "1.0.0", 6 | "tags": ["atom", "copy"], 7 | "flag": "alpha" 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/create-select/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './create-select'; 2 | -------------------------------------------------------------------------------- /packages/components/src/demo-container.tsx: -------------------------------------------------------------------------------- 1 | import { GlobalStyle } from './global-styles'; 2 | import { Headline } from './headline'; 3 | import * as Icon from './icons'; 4 | import * as React from 'react'; 5 | import { Space, SpaceSize } from './space'; 6 | import styled from 'styled-components'; 7 | 8 | export interface DemoContainerProps { 9 | className?: string; 10 | title?: string; 11 | } 12 | 13 | const StyledDemoContainer = styled.div` 14 | display: flex; 15 | flex-direction: column; 16 | flex-wrap: wrap; 17 | `; 18 | 19 | const DemoContainer: React.StatelessComponent = props => ( 20 | 21 | 22 | {props.title && {props.title}} 23 | {React.Children.map(props.children, child => {child})} 24 | 25 | ); 26 | 27 | export default DemoContainer; 28 | -------------------------------------------------------------------------------- /packages/components/src/drag-area/demo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import DemoContainer from '../demo-container'; 3 | 4 | import { DragArea, DragAreaAnchors } from './index'; 5 | 6 | export const DemoDragArea: React.SFC<{}> = (): JSX.Element => ( 7 | 8 | e} 14 | onDragLeave={e => e} 15 | onDragOver={e => e} 16 | onDrop={e => e} 17 | onDragEnter={e => e} 18 | > 19 |
Drag Area Element
20 |
21 |
22 | ); 23 | 24 | export default DemoDragArea; 25 | -------------------------------------------------------------------------------- /packages/components/src/drag-area/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './drag-area'; 2 | export * from './target-signal'; 3 | -------------------------------------------------------------------------------- /packages/components/src/drag-area/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drag-area", 3 | "displayName": "Drag Area", 4 | "flag": "alpha", 5 | "version": "1.0.0", 6 | "tags": ["globals"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/editable-title/demo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import DemoContainer from '../demo-container'; 4 | import { Headline } from '../headline'; 5 | import { Space, SpaceSize } from '../space'; 6 | import { EditableTitle, EditableTitleState } from '.'; 7 | 8 | export default (): JSX.Element => ( 9 | 10 | 11 | Editable Title 12 | 17 | 18 | 19 | Editable Title 20 | 25 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /packages/components/src/editable-title/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "editable-tile", 3 | "displayName": "Editable Tile", 4 | "flag": "alpha", 5 | "version": "1.0.0", 6 | "tags": ["atom"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/element-slot/demo.tsx: -------------------------------------------------------------------------------- 1 | import DemoContainer from '../demo-container'; 2 | import * as React from 'react'; 3 | import { ElementSlot, ElementSlotState } from '.'; 4 | 5 | const FloatingButtonDemo: React.StatelessComponent = (): JSX.Element => ( 6 | 7 | <> 8 | Default 9 | 16 | 17 | <> 18 | Open 19 | 26 | 27 | <> 28 | Disabled 29 | 36 | 37 | <> 38 | Highlighted 39 | 46 | 47 | 48 | ); 49 | 50 | export default FloatingButtonDemo; 51 | -------------------------------------------------------------------------------- /packages/components/src/element-slot/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './element-slot'; 2 | -------------------------------------------------------------------------------- /packages/components/src/element-slot/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "element-slot", 3 | "main": "./element-slot", 4 | "description": "Slot in element tree", 5 | "displayName": "Element Slot", 6 | "flag": "alpha", 7 | "version": "1.0.0", 8 | "tags": [] 9 | } 10 | -------------------------------------------------------------------------------- /packages/components/src/element/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './element'; 2 | -------------------------------------------------------------------------------- /packages/components/src/element/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "element", 3 | "description": "Node in element tree", 4 | "displayName": "Element", 5 | "flag": "alpha", 6 | "version": "1.0.0", 7 | "tags": [] 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/empty-state/demo.tsx: -------------------------------------------------------------------------------- 1 | import { EmptyState } from './index'; 2 | import * as React from 'react'; 3 | 4 | const EmptyStateDemo: React.StatelessComponent = (): JSX.Element => ( 5 | 6 | ); 7 | 8 | export default EmptyStateDemo; 9 | -------------------------------------------------------------------------------- /packages/components/src/empty-state/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "empty-state", 3 | "displayName": "Empty State", 4 | "description": "Explanation of todos in case of an empty state", 5 | "version": "1.0.0", 6 | "tags": ["empty-state"], 7 | "flag": "alpha" 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/fonts/index.tsx: -------------------------------------------------------------------------------- 1 | const NORMAL_FONT = 2 | '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";'; 3 | 4 | export interface Fonts { 5 | NORMAL_FONT: string; 6 | } 7 | 8 | export function fonts(): Fonts { 9 | return { 10 | NORMAL_FONT 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/components/src/fonts/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fonts", 3 | "displayName": "Fonts", 4 | "version": "1.0.0", 5 | "tags": ["natural-laws", "font-face"], 6 | "flag": "alpha", 7 | "options": { 8 | "hidden": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/components/src/global-styles.tsx: -------------------------------------------------------------------------------- 1 | import { Color } from './colors'; 2 | import { fonts } from './fonts'; 3 | import { createGlobalStyle } from 'styled-components'; 4 | 5 | export const GlobalStyle = createGlobalStyle` 6 | body, 7 | html { 8 | height: 100%; 9 | width: 100%; 10 | margin: 0; 11 | background-color: ${Color.Grey97}; 12 | font-family: ${fonts().NORMAL_FONT}; 13 | font-size: 12px; 14 | min-width: 720px; 15 | } 16 | 17 | .dnd-poly-drag-image { 18 | opacity: .5 !important; 19 | } 20 | 21 | @supports (-webkit-overflow-scrolling: touch) { 22 | body, 23 | html { 24 | position: fixed; 25 | } 26 | } 27 | `; 28 | -------------------------------------------------------------------------------- /packages/components/src/headline/demo.tsx: -------------------------------------------------------------------------------- 1 | import DemoContainer from '../demo-container'; 2 | import { Headline } from './index'; 3 | import * as React from 'react'; 4 | 5 | const HeadlineDemo: React.StatelessComponent = (): JSX.Element => ( 6 | 7 | 8 | Headline Order 1 (Bold) 9 | 10 | 11 | Headline Order 1 12 | 13 | 14 | Headline Order 2 (Bold) 15 | 16 | Headline Order 2 17 | 18 | Headline Order 3 (Bold) 19 | 20 | Headline Order 3 21 | 22 | ); 23 | 24 | export default HeadlineDemo; 25 | -------------------------------------------------------------------------------- /packages/components/src/headline/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "headline", 3 | "description": "Typographic Headings", 4 | "displayName": "Headline", 5 | "version": "1.0.0", 6 | "tags": [ 7 | "atoms", 8 | "headline" 9 | ], 10 | "flag": "alpha" 11 | } 12 | -------------------------------------------------------------------------------- /packages/components/src/icons/demo.tsx: -------------------------------------------------------------------------------- 1 | import { Icon, IconSize, getIcon } from './index'; 2 | import * as React from 'react'; 3 | import { Color } from '../colors'; 4 | 5 | const IconDemo: React.SFC = () => ( 6 | <> 7 | 8 | {getIcon({ icon: 'Box' })} 9 | {getIcon({ icon: 'Hello' })} 10 | 11 | ); 12 | 13 | export default IconDemo; 14 | -------------------------------------------------------------------------------- /packages/components/src/icons/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "icons", 3 | "description": "Set of availble vector graphics", 4 | "displayName": "Icons", 5 | "version": "1.0.0", 6 | "tags": ["atoms"], 7 | "flag": "alpha" 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/image/demo.tsx: -------------------------------------------------------------------------------- 1 | import DemoContainer from '../demo-container'; 2 | import { Image } from './index'; 3 | import * as React from 'react'; 4 | 5 | const ImageDemo = () => { 6 | const image = { 7 | alt: 'Gourgeously crafted alternate text', 8 | src: 'https://meetalva.github.io/media/alva.svg' 9 | }; 10 | return ( 11 | 12 | {image.alt} 13 | 14 | ); 15 | }; 16 | 17 | export default ImageDemo; 18 | -------------------------------------------------------------------------------- /packages/components/src/image/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | export interface ImageProps { 5 | alt: string; 6 | className?: string; 7 | src: string; 8 | } 9 | 10 | const StyledImage = styled.img` 11 | display: block; 12 | `; 13 | 14 | export const Image: React.StatelessComponent = props => ( 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /packages/components/src/image/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "Image", 3 | "description": "Basic image component", 4 | "flag": "alpha", 5 | "name": "image", 6 | "version": "1.0.0", 7 | "tags": ["image", "atom"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/item/index.ts: -------------------------------------------------------------------------------- 1 | export * from './item'; 2 | -------------------------------------------------------------------------------- /packages/components/src/item/item.styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import * as Space from '../space'; 3 | import { Color } from '../colors'; 4 | 5 | export const Item = styled.div` 6 | display: flex; 7 | justify-content: center; 8 | padding: ${Space.getSpace(Space.SpaceSize.XS + Space.SpaceSize.XXS)}px 9 | ${Space.getSpace(Space.SpaceSize.S) * 2}px; 10 | cursor: default; 11 | user-select: none; 12 | 13 | &:hover { 14 | background: ${Color.White}; 15 | } 16 | 17 | &:active { 18 | background: ${Color.Grey90}; 19 | } 20 | `; 21 | 22 | export const ItemSymbol = styled.div` 23 | display: flex; 24 | justify-content: center; 25 | align-items: start; 26 | flex: 0 0 15px; 27 | margin-right: 10px; 28 | margin-top: ${Space.getSpace(Space.SpaceSize.XXS)}px; 29 | `; 30 | 31 | export const ItemContent = styled.div` 32 | flex: 1 1 100%; 33 | width: calc(100% - 40px); 34 | `; 35 | 36 | export const ItemTitle = styled.div` 37 | flex: 1 1 100%; 38 | `; 39 | 40 | export const ItemDetails = styled.div` 41 | flex: 1 1 100%; 42 | overflow: hidden; 43 | `; 44 | -------------------------------------------------------------------------------- /packages/components/src/item/item.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as S from './item.styles'; 3 | 4 | export interface ItemProps { 5 | icon: React.ReactNode; 6 | title: React.ReactNode; 7 | details: React.ReactNode; 8 | } 9 | 10 | export function Item(props: ItemProps) { 11 | return ( 12 | 13 | {props.icon} 14 | 15 | {props.title} 16 | {props.details} 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /packages/components/src/layout-switch/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './layout-switch'; 2 | -------------------------------------------------------------------------------- /packages/components/src/layout-switch/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layout-switch", 3 | "displayName": "Layout Switch", 4 | "description": "Switch between Views", 5 | "version": "1.0.0", 6 | "tags": ["layout", "switch"], 7 | "flag": "alpha" 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/layout/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layout", 3 | "description": "Set spatial constraints for children", 4 | "displayName": "Layout", 5 | "flag": "alpha", 6 | "version": "1.0.0", 7 | "tags": ["layout"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/library-box/demo.tsx: -------------------------------------------------------------------------------- 1 | import { Color } from '../colors'; 2 | import DemoContainer from '../demo-container'; 3 | import { LibraryBox, LibraryBoxState, LibraryBoxSize } from './index'; 4 | import { Button, ButtonOrder, ButtonSize } from '../button'; 5 | import * as React from 'react'; 6 | 7 | const LibraryBoxDemo: React.StatelessComponent = (): JSX.Element => ( 8 | 9 | 23 | Already installed 24 | 25 | } 26 | version="v1" 27 | /> 28 | 29 | ); 30 | 31 | export default LibraryBoxDemo; 32 | -------------------------------------------------------------------------------- /packages/components/src/library-box/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './library-box'; 2 | -------------------------------------------------------------------------------- /packages/components/src/library-box/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "library-box", 3 | "displayName": "Library Box", 4 | "description": "Represent a library", 5 | "flag": "alpha", 6 | "version": "1.0.0", 7 | "tags": ["atom"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/link-icon/demo.tsx: -------------------------------------------------------------------------------- 1 | import { Color } from '../colors'; 2 | import DemoContainer from '../demo-container'; 3 | import { LinkIcon } from './index'; 4 | import * as React from 'react'; 5 | import { CopySize } from '../copy'; 6 | 7 | const LinkIconDemo: React.StatelessComponent = (): JSX.Element => ( 8 | 9 | 10 | Link 11 | 12 | 13 | ); 14 | 15 | export default LinkIconDemo; 16 | -------------------------------------------------------------------------------- /packages/components/src/link-icon/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "link-icon", 3 | "displayName": "Link with Icon", 4 | "flag": "alpha", 5 | "version": "1.0.0", 6 | "tags": ["link", "link-icon"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/link/demo.tsx: -------------------------------------------------------------------------------- 1 | import { Color } from '../colors'; 2 | import DemoContainer from '../demo-container'; 3 | import { Link } from './index'; 4 | import * as React from 'react'; 5 | 6 | const LinkDemo: React.StatelessComponent = (): JSX.Element => ( 7 | 8 | Link 9 | Link with color 10 | Link with clickHandler 11 | 12 | Link with uppercase 13 | 14 | 15 | ); 16 | 17 | export default LinkDemo; 18 | -------------------------------------------------------------------------------- /packages/components/src/link/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "link", 3 | "description": "Navigating / tertiary interaction target", 4 | "displayName": "Link", 5 | "flag": "alpha", 6 | "version": "1.0.0", 7 | "tags": ["link"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/list/demo.tsx: -------------------------------------------------------------------------------- 1 | import DemoContainer from '../demo-container'; 2 | import { List, Li, Ul } from './index'; 3 | import * as React from 'react'; 4 | 5 | const ListDemo: React.StatelessComponent = (): JSX.Element => ( 6 | 7 | 8 |
    9 |
  • Item1
  • 10 |
  • Item2
  • 11 |
12 |
13 |
14 | ); 15 | 16 | export default ListDemo; 17 | -------------------------------------------------------------------------------- /packages/components/src/list/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "list", 3 | "description": "Base list", 4 | "displayName": "List", 5 | "flag": "alpha", 6 | "version": "1.0.0", 7 | "tags": ["list"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/page-tile/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "page-tile", 3 | "displayName": "Page Tile", 4 | "description": "Represent a page", 5 | "flag": "alpha", 6 | "version": "1.0.0", 7 | "tags": ["atom"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/panes/element-pane/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { getSpace, SpaceSize } from '../../space'; 3 | import styled from 'styled-components'; 4 | 5 | const StyledElementPane = styled.div` 6 | position: relative; 7 | flex: 1; 8 | overflow: auto; 9 | padding-top: ${getSpace(SpaceSize.M)}px; 10 | padding-bottom: ${getSpace(SpaceSize.XL)}px; 11 | `; 12 | 13 | export interface ElementPaneProps { 14 | children?: React.ReactNode; 15 | } 16 | 17 | export const ElementPane: React.StatelessComponent = props => ( 18 | {props.children} 19 | ); 20 | -------------------------------------------------------------------------------- /packages/components/src/panes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './element-pane'; 2 | export * from './patterns-pane'; 3 | export * from './preview-pane'; 4 | export * from './project-pane'; 5 | export * from './property-pane'; 6 | -------------------------------------------------------------------------------- /packages/components/src/panes/patterns-pane/index.tsx: -------------------------------------------------------------------------------- 1 | import { Color } from '../../colors'; 2 | import * as React from 'react'; 3 | import { getSpace, SpaceSize } from '../../space'; 4 | import styled from 'styled-components'; 5 | 6 | const StyledPatternsPane = styled.div` 7 | box-sizing: border-box; 8 | flex: 1; 9 | padding: 0 ${getSpace(SpaceSize.M)}px; 10 | overflow-y: auto; 11 | height: 100%; 12 | border-top: 1px solid ${Color.BlackAlpha15}; 13 | 14 | @media screen and (-webkit-min-device-pixel-ratio: 2) { 15 | border-top-width: 0.5px; 16 | } 17 | `; 18 | 19 | export interface PatternsPaneProps { 20 | children?: React.ReactNode; 21 | } 22 | 23 | export const PatternsPane: React.StatelessComponent = props => ( 24 | {props.children} 25 | ); 26 | -------------------------------------------------------------------------------- /packages/components/src/panes/project-pane/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const StyledProjectPane = styled.div` 5 | flex-grow: 1; 6 | flex-shrink: 0; 7 | flex-basis: 60%; 8 | `; 9 | 10 | export interface ProjectPaneProps { 11 | children: React.ReactNode; 12 | } 13 | 14 | export const ProjectPane: React.StatelessComponent = props => ( 15 | {props.children} 16 | ); 17 | -------------------------------------------------------------------------------- /packages/components/src/panes/property-pane/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { getSpace, SpaceSize } from '../../space'; 3 | import styled from 'styled-components'; 4 | 5 | const StyledPropertyPane = styled.div` 6 | flex-grow: 1; 7 | flex-shrink: 0; 8 | flex-basis: 40%; 9 | overflow: auto; 10 | `; 11 | 12 | export interface PropertyPaneProps { 13 | children: React.ReactNode; 14 | } 15 | 16 | export const PropertyPane: React.StatelessComponent = props => ( 17 | {props.children} 18 | ); 19 | -------------------------------------------------------------------------------- /packages/components/src/pattern-list/demo.tsx: -------------------------------------------------------------------------------- 1 | import DemoContainer from '../demo-container'; 2 | import * as PatternList from '.'; 3 | import * as React from 'react'; 4 | 5 | const PatternListItemDemo: React.StatelessComponent = (): JSX.Element => ( 6 | 7 | 8 | 9 | 10 | Label 11 | Description 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | export default PatternListItemDemo; 19 | -------------------------------------------------------------------------------- /packages/components/src/pattern-list/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pattern-list", 3 | "displayName": "Pattern List", 4 | "description": "List patterns for element creation", 5 | "flag": "alpha", 6 | "version": "1.0.0" 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/property-box/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "property-box", 3 | "displayName": "Property Box", 4 | "description": "Box for property groups", 5 | "version": "1.0.0", 6 | "tags": ["property"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/property-details/demo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import DemoContainer from '../demo-container'; 3 | import { PropertyDetails } from './'; 4 | import { Headline } from '../headline'; 5 | import { Copy } from '../copy'; 6 | import { Space, SpaceSize } from '../space'; 7 | 8 | const DemoOverlay: React.StatelessComponent = (): JSX.Element => ( 9 | 10 | 14 | Code Properties 15 | 16 | This component accepts code properties 17 | 18 | } 19 | > 20 | Children Content 21 | 22 | 23 | ); 24 | 25 | export default DemoOverlay; 26 | -------------------------------------------------------------------------------- /packages/components/src/property-details/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "property-details", 3 | "displayName": "Property Details", 4 | "flag": "alpha", 5 | "version": "1.0.0", 6 | "tags": ["property-details", "property"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/property-item-asset/demo.tsx: -------------------------------------------------------------------------------- 1 | import { PropertyItemAsset, PropertyItemAssetInputType } from '.'; 2 | import DemoContainer from '../demo-container'; 3 | import * as React from 'react'; 4 | 5 | const NOOP = () => {}; // tslint:disable-line no-empty 6 | 7 | const AssetItemDemo: React.StatelessComponent = (): JSX.Element => ( 8 | 9 | 15 | 23 | 32 | 33 | ); 34 | 35 | export default AssetItemDemo; 36 | -------------------------------------------------------------------------------- /packages/components/src/property-item-asset/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "property-item-asset", 3 | "displayName": "Property Item: Asset", 4 | "description": "Input field for asset values", 5 | "version": "1.0.0", 6 | "tags": [ 7 | "property" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /packages/components/src/property-item-boolean/demo.tsx: -------------------------------------------------------------------------------- 1 | import { PropertyItemBoolean } from '.'; 2 | import * as React from 'react'; 3 | import DemoContainer from '../demo-container'; 4 | 5 | export interface BooleanItemDemoState { 6 | checked?: boolean; 7 | } 8 | 9 | export class BooleanItemDemo extends React.Component<{}, BooleanItemDemoState> { 10 | public state = { 11 | checked: false 12 | }; 13 | 14 | private handleChange(e: React.SyntheticEvent): void { 15 | this.setState({ 16 | checked: !this.state.checked 17 | }); 18 | } 19 | 20 | public render(): JSX.Element { 21 | return ( 22 | 23 | this.handleChange(e)} 27 | /> 28 | this.handleChange(e)} 32 | /> 33 | 34 | ); 35 | } 36 | } 37 | 38 | export default BooleanItemDemo; 39 | -------------------------------------------------------------------------------- /packages/components/src/property-item-boolean/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "property-item-boolean", 3 | "displayName": "Property Item: boolean", 4 | "description": "Input field for boolean values", 5 | "version": "1.0.0", 6 | "tags": [ 7 | "property" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /packages/components/src/property-item-buttongroup/demo.tsx: -------------------------------------------------------------------------------- 1 | import { PropertyItemButtonGroup, PropertyItemButtonGroupValues } from './index'; 2 | import * as React from 'react'; 3 | import DemoContainer from '../demo-container'; 4 | 5 | export interface EnumItemDemoState { 6 | selectedItem: string; 7 | values: PropertyItemButtonGroupValues[]; 8 | } 9 | 10 | export class BooleanItemDemo extends React.Component<{}, EnumItemDemoState> { 11 | public constructor(props: {}) { 12 | super(props); 13 | 14 | const values = [ 15 | { id: 'id1', name: 'Medium', icon: 'Plus' }, 16 | { id: 'id2', name: 'Rare', icon: 'Youtube' }, 17 | { id: 'id3', name: 'Solid Shoe', icon: 'Plus' } 18 | ]; 19 | 20 | this.state = { 21 | values, 22 | selectedItem: values[0].name 23 | }; 24 | } 25 | 26 | public render(): JSX.Element { 27 | return ( 28 | 29 | 30 | 36 | 37 | ); 38 | } 39 | } 40 | 41 | export default BooleanItemDemo; 42 | -------------------------------------------------------------------------------- /packages/components/src/property-item-buttongroup/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "property-item-buttongroup", 3 | "displayName": "Property Item: Button Group", 4 | "description": "Input field for enum values displayed as button group", 5 | "version": "1.0.0", 6 | "tags": ["property"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/property-item-color/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './property-item-color'; 2 | -------------------------------------------------------------------------------- /packages/components/src/property-item-color/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "property-item-number", 3 | "displayName": "Property Item: Number", 4 | "description": "Input field for number values", 5 | "version": "1.0.0", 6 | "tags": ["property"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/property-item-number/demo.tsx: -------------------------------------------------------------------------------- 1 | import { PropertyItemNumber } from './property-item-number'; 2 | import * as React from 'react'; 3 | import DemoContainer from '../demo-container'; 4 | 5 | const NOOP = () => {}; // tslint:disable-line no-empty 6 | 7 | const StringItemDemo: React.StatelessComponent = (): JSX.Element => ( 8 | 9 | 10 | 16 | 17 | ); 18 | 19 | export default StringItemDemo; 20 | -------------------------------------------------------------------------------- /packages/components/src/property-item-number/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './property-item-number'; 2 | -------------------------------------------------------------------------------- /packages/components/src/property-item-number/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "property-item-number", 3 | "displayName": "Property Item: Number", 4 | "description": "Input field for number values", 5 | "version": "1.0.0", 6 | "tags": ["property"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/property-item-number/property-item-number.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as RTL from 'react-testing-library'; 3 | import { PropertyItemNumber } from './property-item-number'; 4 | 5 | // tslint:disable-next-line:no-submodule-imports 6 | import 'jest-dom/extend-expect'; 7 | 8 | // tslint:disable-next-line:no-submodule-imports 9 | import 'react-testing-library/cleanup-after-each'; 10 | 11 | const NOOP = () => {}; // tslint:disable-line no-empty 12 | 13 | test('renders falsy numbers as string representation', () => { 14 | const rendered = RTL.render(); 15 | expect(rendered.baseElement.querySelector('input')).toHaveAttribute('value', '0'); 16 | }); 17 | 18 | test('renders NaN as empty value', () => { 19 | const rendered = RTL.render( 20 | 21 | ); 22 | expect(rendered.baseElement.querySelector('input')).toHaveAttribute('value', ''); 23 | }); 24 | 25 | test('renders undefined as empty value', () => { 26 | const rendered = RTL.render(); 27 | expect(rendered.baseElement.querySelector('input')).toHaveAttribute('value', ''); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/components/src/property-item-number/property-item-number.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { PropertyInput, PropertyInputType } from '../property-input'; 3 | import { PropertyItem } from '../property-item'; 4 | 5 | export interface PropertyItemNumberProps { 6 | className?: string; 7 | description?: string; 8 | label: string; 9 | onBlur?: React.FocusEventHandler; 10 | onChange?: React.ChangeEventHandler; 11 | placeholder?: string; 12 | value?: number; 13 | 14 | onMinusClick?: React.MouseEventHandler; 15 | onPlusClick?: React.MouseEventHandler; 16 | } 17 | 18 | export const PropertyItemNumber: React.StatelessComponent = props => ( 19 | 20 | 33 | 34 | ); 35 | -------------------------------------------------------------------------------- /packages/components/src/property-item-select/demo.tsx: -------------------------------------------------------------------------------- 1 | import { PropertyItemSelect, PropertyItemSelectValues } from './index'; 2 | import * as React from 'react'; 3 | import DemoContainer from '../demo-container'; 4 | 5 | export interface EnumItemDemoState { 6 | selectedItem: string; 7 | values: PropertyItemSelectValues[]; 8 | } 9 | 10 | export class BooleanItemDemo extends React.Component<{}, EnumItemDemoState> { 11 | public constructor(props: {}) { 12 | super(props); 13 | 14 | const values = [ 15 | { id: 'id1', name: 'Medium' }, 16 | { id: 'id2', name: 'Rare' }, 17 | { id: 'id3', name: 'Solid Shoe' } 18 | ]; 19 | 20 | this.state = { 21 | values, 22 | selectedItem: values[0].name 23 | }; 24 | } 25 | 26 | public render(): JSX.Element { 27 | return ( 28 | 29 | 34 | 40 | 41 | ); 42 | } 43 | } 44 | 45 | export default BooleanItemDemo; 46 | -------------------------------------------------------------------------------- /packages/components/src/property-item-select/index.tsx: -------------------------------------------------------------------------------- 1 | import { PropertyItem } from '../property-item'; 2 | import * as React from 'react'; 3 | import { Select, SimpleSelectOption } from '../select'; 4 | 5 | export interface PropertyItemSelectValues { 6 | id: string; 7 | name: string; 8 | } 9 | 10 | export interface PropertyItemSelectProps { 11 | className?: string; 12 | description?: string; 13 | label: string; 14 | required?: boolean; 15 | selectedValue: string | undefined; 16 | values: PropertyItemSelectValues[]; 17 | onChange?(item: SimpleSelectOption): void; 18 | onFocus?(): void; 19 | } 20 | 21 | export const PropertyItemSelect: React.StatelessComponent = props => { 22 | const selected = props.values.find(v => v.id === props.selectedValue); 23 | const value = selected ? { label: selected.name, value: selected.id } : undefined; 24 | 25 | return ( 26 | 27 | 18 | { 21 | if (e.target.files === null) { 22 | return; 23 | } 24 | 25 | const file = e.target.files[0]; 26 | 27 | if (!file) { 28 | return; 29 | } 30 | 31 | const reader = new FileReader(); 32 | 33 | (() => { 34 | switch (props.format) { 35 | case FileFormat.Binary: 36 | reader.readAsDataURL(file); 37 | break; 38 | case FileFormat.Text: 39 | default: 40 | reader.readAsText(file, 'UTF-8'); 41 | } 42 | })(); 43 | 44 | reader.onload = async o => { 45 | if (!o.target) { 46 | return; 47 | } 48 | 49 | if (props.onChange) { 50 | props.onChange(reader.result as string); 51 | } 52 | }; 53 | }} 54 | /> 55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /packages/core/src/container/match.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface MatchProps { 4 | value: T; 5 | children: React.ReactNode; 6 | } 7 | 8 | export interface BranchProps { 9 | when: ((value: T) => boolean) | T; 10 | children?: React.ReactNode; 11 | } 12 | 13 | export const Match = function Match(props: MatchProps): JSX.Element | null { 14 | const branch = React.Children.toArray(props.children) 15 | .filter( 16 | (child): child is React.ReactElement> => 17 | typeof child === 'object' && child.type === MatchBranch 18 | ) 19 | .find(branch => { 20 | if (typeof branch.props.when === 'function') { 21 | const when = branch.props.when as ((value: T) => boolean); 22 | return when(props.value); 23 | } 24 | return branch.props.when === props.value; 25 | }); 26 | 27 | return branch || null; 28 | }; 29 | 30 | export const MatchBranch = function Branch(props: BranchProps): JSX.Element { 31 | return <>{props.children}; 32 | }; 33 | -------------------------------------------------------------------------------- /packages/core/src/container/menu/index.ts: -------------------------------------------------------------------------------- 1 | export * from './menu'; 2 | export * from './context-menu'; 3 | -------------------------------------------------------------------------------- /packages/core/src/container/pattern-list/index.tsx: -------------------------------------------------------------------------------- 1 | export { PatternListContainer } from './pattern-list-container'; 2 | -------------------------------------------------------------------------------- /packages/core/src/container/property-list/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './property-list'; 2 | -------------------------------------------------------------------------------- /packages/core/src/container/property-list/property-list.tsx: -------------------------------------------------------------------------------- 1 | import * as MobxReact from 'mobx-react'; 2 | import * as React from 'react'; 3 | import { ViewStore } from '../../store'; 4 | import { PropertyListVirtual } from './property-list-virtual'; 5 | import * as Components from '@meetalva/components'; 6 | import * as T from '@meetalva/types'; 7 | 8 | export interface PropertyListContainerProps { 9 | onClick?: React.MouseEventHandler; 10 | } 11 | 12 | @MobxReact.inject('store') 13 | @MobxReact.observer 14 | export class PropertyListContainer extends React.Component { 15 | public render(): React.ReactNode { 16 | const { store } = this.props as { store: ViewStore }; 17 | const selectedElement = store.getSelectedElement(); 18 | 19 | if (!selectedElement || selectedElement.getRole() === T.ElementRole.Root) { 20 | return ( 21 | 25 | ); 26 | } 27 | return ; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/src/context-menu/index.ts: -------------------------------------------------------------------------------- 1 | export * from './element-menu'; 2 | -------------------------------------------------------------------------------- /packages/core/src/export/export-html-project.ts: -------------------------------------------------------------------------------- 1 | import * as Fs from 'fs'; 2 | import * as Model from '@meetalva/model'; 3 | import * as Types from '@meetalva/types'; 4 | import * as PreviewDocument from '../preview-document'; 5 | 6 | const MemoryFileSystem = require('memory-fs'); 7 | 8 | export async function exportHtmlProject({ 9 | project, 10 | location 11 | }: { 12 | project: Model.Project; 13 | location: Types.Location; 14 | }): Promise { 15 | const fs = new MemoryFileSystem() as typeof Fs; 16 | 17 | const previewProject = project.clone(); 18 | const firstPage = previewProject.getPages()[0]; 19 | 20 | if (!firstPage) { 21 | return { 22 | type: Types.ExportResultType.ExportError, 23 | error: new Error(`Could not determine leading page for ${previewProject.getName()}`) 24 | }; 25 | } 26 | 27 | previewProject.setActivePage(firstPage); 28 | 29 | fs.writeFileSync( 30 | `/${project.getId()}.html`, 31 | await PreviewDocument.staticDocument({ 32 | location, 33 | data: previewProject.toJSON(), 34 | scripts: previewProject 35 | .getPatternLibraries() 36 | .map(lib => ``) 37 | }) 38 | ); 39 | 40 | return { 41 | type: Types.ExportResultType.ExportSuccess, 42 | fs 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /packages/core/src/export/index.ts: -------------------------------------------------------------------------------- 1 | export * from './export-html-project'; 2 | -------------------------------------------------------------------------------- /packages/core/src/hosts/electron-host/create-window-preload.ts: -------------------------------------------------------------------------------- 1 | import * as Electron from 'electron'; 2 | 3 | Electron.webFrame.setLayoutZoomLevelLimits(-999999, 999999); 4 | Electron.webFrame.setZoomFactor(1); 5 | 6 | const ezl = Electron.webFrame.getZoomLevel(); 7 | Electron.webFrame.setLayoutZoomLevelLimits(ezl, ezl); 8 | -------------------------------------------------------------------------------- /packages/core/src/hosts/electron-host/index.ts: -------------------------------------------------------------------------------- 1 | export * from './electron-host'; 2 | -------------------------------------------------------------------------------- /packages/core/src/hosts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './electron-host'; 2 | export * from './browser-host'; 3 | export * from './local-data-host'; 4 | export * from './restart-listener'; 5 | export * from './node-host'; 6 | -------------------------------------------------------------------------------- /packages/core/src/hosts/restart-listener.ts: -------------------------------------------------------------------------------- 1 | import * as Readline from 'readline'; 2 | 3 | export interface RestartListenerInit { 4 | readline: Readline.ReadLine; 5 | } 6 | 7 | const readline = Readline.createInterface({ 8 | input: process.stdin, 9 | terminal: false 10 | }); 11 | 12 | export class RestartListener { 13 | private readline: Readline.ReadLine; 14 | // tslint:disable-next-line:no-empty 15 | private listener: () => void = () => {}; 16 | 17 | private constructor(init: RestartListenerInit) { 18 | this.readline = init.readline; 19 | } 20 | 21 | public static async fromProcess(process: NodeJS.Process): Promise { 22 | return new RestartListener({ readline }); 23 | } 24 | 25 | private onRs = (line: string): void => { 26 | if (!line.endsWith('rs')) { 27 | return; 28 | } 29 | 30 | Readline.moveCursor(process.stdin, 0, -1); 31 | Readline.clearLine(process.stdin, 0); 32 | 33 | this.listener(); 34 | }; 35 | 36 | public subscribe(listener: () => void): void { 37 | this.listener = listener; 38 | this.readline.on('line', this.onRs); 39 | } 40 | 41 | public unsubscribe(): void { 42 | this.readline.removeAllListeners(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/core/src/matchers/add-app.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as Model from '@meetalva/model'; 3 | import { MatcherCreator } from './context'; 4 | 5 | export const addApp: MatcherCreator = ({ host }) => { 6 | return async message => { 7 | host.addApp(Model.AlvaApp.from(message.payload.app, { sender: await host.getSender() })); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/core/src/matchers/browser.ts: -------------------------------------------------------------------------------- 1 | export * from './add-app'; 2 | export * from './copy'; 3 | export * from './create-new-file-request'; 4 | export * from './cut'; 5 | export * from './export-html-project'; 6 | export * from './open-external-url'; 7 | export * from './open-file-request'; 8 | export * from './open-remote-file-request'; 9 | export * from './open-window'; 10 | export * from './paste'; 11 | export * from './save'; 12 | export * from './save-as'; 13 | export * from './show-context-menu'; 14 | export * from './show-error'; 15 | export * from './show-message'; 16 | export * from './use-file-request'; 17 | export * from './use-file-response'; 18 | -------------------------------------------------------------------------------- /packages/core/src/matchers/context.ts: -------------------------------------------------------------------------------- 1 | import * as T from '@meetalva/types'; 2 | import * as Model from '@meetalva/model'; 3 | import * as M from '@meetalva/message'; 4 | 5 | export type MatcherContext = T.MatcherContext, Model.Project, M.Message>; 6 | export type Matcher = (m: M) => Promise; 7 | export type MatcherCreator = ( 8 | context: MatcherContext, 9 | config?: C 10 | ) => Matcher; 11 | -------------------------------------------------------------------------------- /packages/core/src/matchers/copy.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as Serde from '../sender/serde'; 3 | import * as uuid from 'uuid'; 4 | import { MatcherCreator } from './context'; 5 | 6 | export const copy: MatcherCreator = ({ host, dataHost }) => { 7 | return async m => { 8 | const project = await dataHost.getProject(m.payload.projectId); 9 | 10 | if (!project) { 11 | host.log(`copy: no resolveable project for ${m}`); 12 | return; 13 | } 14 | 15 | const item = project.getItem(m.payload.itemId, m.payload.itemType); 16 | 17 | if (!item) { 18 | host.log(`copy: no resolveable item for ${m}`); 19 | return; 20 | } 21 | 22 | return host.writeClipboard( 23 | Serde.serialize({ 24 | type: M.MessageType.Clipboard, 25 | id: uuid.v4(), 26 | payload: { 27 | type: m.payload.itemType, 28 | item: item.toJSON(), 29 | project: project.toJSON() 30 | } 31 | }) 32 | ); 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /packages/core/src/matchers/cut.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as Serde from '../sender/serde'; 3 | import * as uuid from 'uuid'; 4 | import { MatcherCreator } from './context'; 5 | 6 | export const cut: MatcherCreator = ({ host, dataHost }) => { 7 | return async m => { 8 | const project = await dataHost.getProject(m.payload.projectId); 9 | 10 | if (!project) { 11 | return; 12 | } 13 | 14 | await host.writeClipboard( 15 | Serde.serialize({ 16 | type: M.MessageType.Clipboard, 17 | id: uuid.v4(), 18 | payload: { 19 | type: m.payload.itemType, 20 | item: m.payload.item, 21 | project: project.toJSON() 22 | } 23 | }) 24 | ); 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/core/src/matchers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './add-app'; 2 | export * from './connect-npm-pattern-library-request'; 3 | export * from './connect-pattern-library-request'; 4 | export * from './update-pattern-library-request'; 5 | export * from './copy'; 6 | export * from './create-new-file-request'; 7 | export * from './cut'; 8 | export * from './export-html-project'; 9 | export * from './open-asset'; 10 | export * from './open-external-url'; 11 | export * from './open-file-request'; 12 | export * from './open-remote-file-request'; 13 | export * from './open-window'; 14 | export * from './paste'; 15 | export * from './save'; 16 | export * from './save-as'; 17 | export * from './show-context-menu'; 18 | export * from './show-error'; 19 | export * from './show-message'; 20 | export * from './show-update-details'; 21 | export * from './update-npm-pattern-library-request'; 22 | export * from './use-file-request'; 23 | export * from './use-file-response'; 24 | -------------------------------------------------------------------------------- /packages/core/src/matchers/open-external-url.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MatcherCreator } from './context'; 3 | 4 | export const openExternalUrl: MatcherCreator = ({ host }) => { 5 | return async message => host.open(message.payload); 6 | }; 7 | -------------------------------------------------------------------------------- /packages/core/src/matchers/open-remote-file-request.ts: -------------------------------------------------------------------------------- 1 | import * as fetch from 'isomorphic-fetch'; 2 | import * as M from '@meetalva/message'; 3 | import { MatcherCreator } from './context'; 4 | 5 | export const openRemoteFileRequest: MatcherCreator = ({ host }) => { 6 | return async m => { 7 | const sender = (await host.getApp(m.appId || '')) || (await host.getSender()); 8 | const response = await fetch(m.payload.url); 9 | 10 | if (!response.ok) { 11 | sender.send({ 12 | type: M.MessageType.ShowError, 13 | transaction: m.transaction, 14 | id: m.id, 15 | payload: { 16 | message: `Could not load Alva file from ${m.payload.url}`, 17 | detail: `The server responsed with ${response.status}: ${response.statusText}` 18 | } 19 | }); 20 | } 21 | 22 | const downloadedProject = await response.text(); 23 | 24 | sender.send({ 25 | type: M.MessageType.UseFileRequest, 26 | id: m.id, 27 | transaction: m.transaction, 28 | sender: m.sender, 29 | payload: { 30 | silent: false, 31 | replace: false, 32 | contents: downloadedProject 33 | } 34 | }); 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/core/src/matchers/open-window.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as T from '@meetalva/types'; 3 | import { MatcherCreator } from './context'; 4 | 5 | export const openWindow: MatcherCreator = ({ host, location }) => { 6 | return async message => { 7 | switch (message.payload.view) { 8 | case T.AlvaView.PageDetail: 9 | await host.createWindow({ 10 | address: `${location.origin}/project/${message.payload.projectId}`, 11 | variant: T.HostWindowVariant.Normal 12 | }); 13 | return; 14 | case T.AlvaView.SplashScreen: 15 | await host.createWindow({ 16 | address: location.origin, 17 | variant: T.HostWindowVariant.Splashscreen 18 | }); 19 | return; 20 | } 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /packages/core/src/matchers/show-context-menu.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as T from '@meetalva/types'; 3 | import * as ContextMenu from '../context-menu'; 4 | import { MatcherCreator } from './context'; 5 | 6 | export const showContextMenu: MatcherCreator = ({ host, dataHost }) => { 7 | return async m => { 8 | const app = await host.getApp(m.appId || ''); 9 | 10 | if (!app) { 11 | host.log(`showContextMenu: received message without resolveable app: ${m}`); 12 | return; 13 | } 14 | 15 | if (m.payload.menu === T.ContextMenuType.ElementMenu) { 16 | const project = await dataHost.getProject(m.payload.projectId); 17 | 18 | if (!project) { 19 | return; 20 | } 21 | 22 | const element = project.getElementById(m.payload.data.element.id); 23 | 24 | if (!element) { 25 | return; 26 | } 27 | 28 | host.showContextMenu({ 29 | position: m.payload.position, 30 | items: ContextMenu.elementContextMenu({ 31 | app, 32 | project, 33 | element 34 | }) 35 | }); 36 | } 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /packages/core/src/matchers/show-message.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as T from '@meetalva/types'; 3 | import { MatcherCreator } from './context'; 4 | 5 | export const showMessage: MatcherCreator = ({ host }) => { 6 | return async m => { 7 | const app = await host.getApp(m.appId || ''); 8 | 9 | if (!app && host.type !== T.HostType.Electron) { 10 | host.log(`showMessage: received message without resolveable app: ${m}`); 11 | return; 12 | } 13 | 14 | const sender = app || (await host.getSender()); 15 | const button = await host.showMessage(m.payload); 16 | 17 | if (button && button.message) { 18 | sender.send({ ...button.message }); 19 | } 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /packages/core/src/matchers/show-update-details.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as uuid from 'uuid'; 3 | import { MatcherCreator } from './context'; 4 | 5 | const timeago = require('timeago.js'); 6 | 7 | export const showUpdateDetails: MatcherCreator = ({ host }) => { 8 | return async m => { 9 | await host.showMessage({ 10 | message: `Restart to update`, 11 | detail: [ 12 | `The new Alva version ${m.payload.version} is ready to install.`, 13 | `${m.payload.version} has been published ${timeago.format(m.payload.releaseDate)}.`, 14 | '\n', 15 | `Alva needs to restart to install this update.` 16 | ].join('\n'), 17 | buttons: [ 18 | { 19 | label: 'Restart & Install', 20 | selected: true, 21 | message: { 22 | id: uuid.v4(), 23 | type: M.MessageType.InstallUpdate, 24 | payload: undefined 25 | } 26 | }, 27 | { 28 | label: 'Later', 29 | cancel: true 30 | } 31 | ] 32 | }); 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /packages/core/src/matchers/use-file-response.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as T from '@meetalva/types'; 3 | import * as uuid from 'uuid'; 4 | import * as Model from '@meetalva/model'; 5 | import { MatcherCreator } from './context'; 6 | 7 | export const useFileResponse: MatcherCreator = ({ host }) => { 8 | return async m => { 9 | const sender = (await host.getApp(m.appId || '')) || (await host.getSender()); 10 | 11 | if (m.payload.project.status === T.ProjectPayloadStatus.Error) { 12 | host.log('useFileResponse: Received project payload with error status'); 13 | return; 14 | } 15 | 16 | const p = m.payload.project as T.ProjectPayloadSuccess; 17 | const replacement = Model.Project.from(p.contents); 18 | 19 | if (!m.payload.replace) { 20 | sender.send({ 21 | id: uuid.v4(), 22 | type: M.MessageType.OpenWindow, 23 | payload: { 24 | view: T.AlvaView.PageDetail, 25 | projectId: replacement.getId() 26 | }, 27 | transaction: m.transaction, 28 | sender: m.sender 29 | }); 30 | } else { 31 | host.log( 32 | 'useFileResponse: Received project payload with replace parameter "true", skipping' 33 | ); 34 | } 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/core/src/menu/context.ts: -------------------------------------------------------------------------------- 1 | import * as T from '@meetalva/types'; 2 | import * as Mo from '@meetalva/model'; 3 | import { Message } from '@meetalva/message'; 4 | 5 | export type MenuContext = T.MenuContext, Mo.Project>; 6 | export type MenuCreator = (ctx: MenuContext) => T.MenuItem, Mo.Project>; 7 | -------------------------------------------------------------------------------- /packages/core/src/menu/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app-menu'; 2 | export * from './edit-menu'; 3 | export * from './file-menu'; 4 | export * from './help-menu'; 5 | export * from './view-menu'; 6 | export * from './window-menu'; 7 | -------------------------------------------------------------------------------- /packages/core/src/menu/window-menu.ts: -------------------------------------------------------------------------------- 1 | import * as uuid from 'uuid'; 2 | import * as Types from '@meetalva/types'; 3 | import { MenuCreator } from './context'; 4 | 5 | const ids = { 6 | window: uuid.v4(), 7 | minimize: uuid.v4(), 8 | front: uuid.v4() 9 | }; 10 | 11 | export const windowMenu: MenuCreator = ctx => { 12 | const isElectron = ctx.app ? ctx.app.isHostType(Types.HostType.Electron) : false; 13 | 14 | return { 15 | id: ids.window, 16 | label: '&Window', 17 | role: 'window', 18 | visible: isElectron, 19 | submenu: [ 20 | { 21 | id: ids.minimize, 22 | label: '&Minimize', 23 | accelerator: 'CmdOrCtrl+M', 24 | role: 'minimize' 25 | }, 26 | { 27 | id: uuid.v4(), 28 | type: 'separator' 29 | }, 30 | { 31 | id: ids.front, 32 | label: 'Bring All to Front', 33 | role: 'front' 34 | } 35 | ] 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /packages/core/src/migrator/abstract-migration.ts: -------------------------------------------------------------------------------- 1 | import * as Types from '@meetalva/types'; 2 | 3 | export interface MigrationStep { 4 | inputVersion: number; 5 | outputVersion: number; 6 | description: string; 7 | } 8 | 9 | export interface MigrationItem { 10 | project: T; 11 | steps: MigrationStep[]; 12 | } 13 | 14 | export abstract class AbstractMigration< 15 | T extends Types.MigratableProject, 16 | V extends Types.MigratableProject 17 | > { 18 | public readonly inputVersion: number = 0; 19 | public readonly outputVersion: number = 0; 20 | 21 | public async transform(input: MigrationItem): Promise> { 22 | throw new Error( 23 | `Migration.transform is not implemented for ${this.inputVersion} => ${this.outputVersion}` 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/migrator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './migrator'; 2 | -------------------------------------------------------------------------------- /packages/core/src/migrator/migrator.ts: -------------------------------------------------------------------------------- 1 | import * as Types from '@meetalva/types'; 2 | import { ZeroOneMigration } from './migration-v0-v1'; 3 | import { OneTwoMigration } from './migration-v1-v2'; 4 | import { MigrationStep } from './abstract-migration'; 5 | 6 | export class Migrator { 7 | private migrations = [new ZeroOneMigration(), new OneTwoMigration()]; 8 | 9 | public async migrate(input: Types.MigratableProject): Promise { 10 | const inputVersion = typeof (input as any).version === 'undefined' ? 0 : 1; 11 | 12 | const migrating = this.migrations.filter(m => m.inputVersion >= inputVersion).reduce( 13 | async (previous, migration) => migration.transform((await previous) as any), 14 | Promise.resolve({ 15 | project: input, 16 | steps: [] as MigrationStep[] 17 | }) 18 | ); 19 | 20 | return (await migrating).project as Types.SavedProject; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/src/persistence/index.ts: -------------------------------------------------------------------------------- 1 | export * from './persistence'; 2 | -------------------------------------------------------------------------------- /packages/core/src/persistence/persistence.ts: -------------------------------------------------------------------------------- 1 | import * as Yaml from 'js-yaml'; 2 | import { Migrator } from '../migrator'; 3 | import * as Types from '@meetalva/types'; 4 | 5 | export class Persistence { 6 | public static async parse( 7 | contents: string 8 | ): Promise> { 9 | try { 10 | const parsed = (Yaml.load(String(contents)) as unknown) as T; 11 | const migrator = new Migrator(); 12 | const migrated = (await migrator.migrate(parsed)) as T; 13 | 14 | return { 15 | state: Types.PersistenceState.Success, 16 | contents: migrated 17 | }; 18 | } catch (error) { 19 | return { 20 | state: Types.PersistenceState.Error, 21 | error 22 | }; 23 | } 24 | } 25 | 26 | // tslint:disable-next-line:no-any 27 | public static async serialize(model: any): Promise { 28 | try { 29 | return { 30 | state: Types.PersistenceState.Success, 31 | contents: Yaml.safeDump(model.toDisk(), { skipInvalid: true, noRefs: true }) 32 | }; 33 | } catch (error) { 34 | return { 35 | state: Types.PersistenceState.Error, 36 | error 37 | }; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/core/src/preview-document/index.ts: -------------------------------------------------------------------------------- 1 | export * from './preview-document'; 2 | export * from './static-document'; 3 | export * from './sketch-document'; 4 | -------------------------------------------------------------------------------- /packages/core/src/preview-renderer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './preview-renderer'; 2 | -------------------------------------------------------------------------------- /packages/core/src/preview-renderer/preview-application.tsx: -------------------------------------------------------------------------------- 1 | import * as MobxReact from 'mobx-react'; 2 | import { PreviewComponent } from './preview-component'; 3 | import * as React from 'react'; 4 | import { Injection } from '.'; 5 | 6 | @MobxReact.inject('store') 7 | @MobxReact.observer 8 | export class PreviewApplication extends React.Component { 9 | public render(): JSX.Element | null { 10 | const props = this.props as Injection; 11 | const activePage = props.store.getActivePage(); 12 | 13 | if (!activePage) { 14 | return null; 15 | } 16 | 17 | const element = activePage.getRoot(); 18 | 19 | if (!element) { 20 | return null; 21 | } 22 | 23 | return ; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/preview-renderer/preview-renderer.tsx: -------------------------------------------------------------------------------- 1 | import * as MobxReact from 'mobx-react'; 2 | import { PreviewStore, SyntheticComponents } from '../preview/preview-store'; 3 | import { PreviewApplication } from './preview-application'; 4 | import * as React from 'react'; 5 | import * as ReactDom from 'react-dom'; 6 | 7 | export interface Injection { 8 | store: PreviewStore; 9 | } 10 | 11 | export function render(store: PreviewStore, container: HTMLElement): void { 12 | ReactDom.render( 13 | 14 | 15 | , 16 | container 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/preview/element-area.ts: -------------------------------------------------------------------------------- 1 | import * as MobX from 'mobx'; 2 | import * as Types from '@meetalva/types'; 3 | 4 | export interface ElementAreaInit { 5 | top: number; 6 | left: number; 7 | width: number; 8 | height: number; 9 | } 10 | 11 | export class ElementArea { 12 | @MobX.observable public element: Element | undefined; 13 | @MobX.observable public isVisible: boolean = false; 14 | 15 | @MobX.action 16 | public hide(): void { 17 | this.isVisible = false; 18 | } 19 | 20 | @MobX.action 21 | public setElement(element: Element | undefined): void { 22 | this.element = element; 23 | } 24 | 25 | @MobX.action 26 | public show(): void { 27 | this.isVisible = true; 28 | } 29 | 30 | public write(element: HTMLElement, _: { scrollPositon: Types.Point }): void { 31 | const rect = this.element 32 | ? this.element.getBoundingClientRect() 33 | : { top: 0, left: 0, width: 0, height: 0 }; 34 | 35 | element.style.top = `${rect.top}px`; 36 | element.style.left = `${rect.left}px`; 37 | element.style.width = `${rect.width}px`; 38 | element.style.height = `${rect.height}px`; 39 | 40 | element.style.display = !this.isVisible || (!rect.height && !rect.width) ? 'none' : 'block'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/core/src/preview/get-components.ts: -------------------------------------------------------------------------------- 1 | import * as Model from '@meetalva/model'; 2 | 3 | // tslint:disable-next-line:no-any 4 | export function getComponents(project: Model.Project): { [id: string]: any } { 5 | return project 6 | .getPatternLibraries() 7 | .filter(library => library.getBundleId() !== '') 8 | .reduce((components, library) => { 9 | const libraryComponents = (window as any)[library.getBundleId()]; 10 | 11 | if (!libraryComponents) { 12 | return components; 13 | } 14 | 15 | // tslint:disable-next-line:prefer-object-spread 16 | return Object.assign(components, libraryComponents); 17 | }, {}); 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/preview/get-initial-data.ts: -------------------------------------------------------------------------------- 1 | import * as Types from '@meetalva/types'; 2 | 3 | export interface InitialData { 4 | data: Types.SerializedProject; 5 | transferType: 'inline' | 'message'; 6 | mode: 'static' | 'live'; 7 | } 8 | 9 | export function getInitialData(): InitialData | undefined { 10 | const vaultElement = document.querySelector('[data-data="alva"]'); 11 | 12 | if (!vaultElement) { 13 | return; 14 | } 15 | 16 | try { 17 | return JSON.parse(decodeURIComponent(vaultElement.textContent || '{}')); 18 | } catch (err) { 19 | return; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/src/renderer/edit-handlers/cut.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | import * as Types from '@meetalva/types'; 4 | 5 | export function cut({ app, store }: MessageHandlerContext): MessageHandler { 6 | return m => { 7 | if (app.getHasFocusedInput()) { 8 | return; 9 | } 10 | 11 | const project = store.getProject(); 12 | 13 | if (!project) { 14 | return; 15 | } 16 | 17 | switch (project.getFocusedItemType()) { 18 | case Types.ItemType.Element: 19 | store.removeSelectedElement(); 20 | break; 21 | case Types.ItemType.Page: 22 | store.removeSelectedPage(); 23 | } 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/renderer/edit-handlers/delete-selected.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | import * as Types from '@meetalva/types'; 4 | 5 | export function deleteSelected({ 6 | app, 7 | store 8 | }: MessageHandlerContext): MessageHandler { 9 | return m => { 10 | if (app.getHasFocusedInput()) { 11 | return; 12 | } 13 | 14 | const project = store.getProject(); 15 | 16 | if (!project) { 17 | return; 18 | } 19 | 20 | switch (project.getFocusedItemType()) { 21 | case Types.ItemType.Element: 22 | store.removeSelectedElement(); 23 | break; 24 | case Types.ItemType.Page: 25 | store.removeSelectedPage(); 26 | } 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/renderer/edit-handlers/duplicate-element.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | import * as Types from '@meetalva/types'; 4 | 5 | export function duplicateElement({ 6 | app, 7 | store 8 | }: MessageHandlerContext): MessageHandler { 9 | return m => { 10 | if (app.getHasFocusedInput()) { 11 | return; 12 | } 13 | 14 | const project = store.getProject(); 15 | 16 | if (!project) { 17 | return; 18 | } 19 | 20 | switch (project.getFocusedItemType()) { 21 | case Types.ItemType.Element: 22 | store.duplicateElementById(m.payload); 23 | } 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/renderer/edit-handlers/duplicate-selected.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | import * as Types from '@meetalva/types'; 4 | 5 | export function duplicateSelected({ 6 | app, 7 | store 8 | }: MessageHandlerContext): MessageHandler { 9 | return m => { 10 | if (app.getHasFocusedInput()) { 11 | return; 12 | } 13 | 14 | const project = store.getProject(); 15 | 16 | if (!project) { 17 | return; 18 | } 19 | 20 | switch (project.getFocusedItemType()) { 21 | case Types.ItemType.Element: 22 | store.duplicateSelectedElement(); 23 | break; 24 | case Types.ItemType.Page: 25 | store.duplicateActivePage(); 26 | } 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/renderer/edit-handlers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cut'; 2 | export * from './delete-selected'; 3 | export * from './duplicate-element'; 4 | export * from './duplicate-selected'; 5 | export * from './paste-element'; 6 | export * from './paste-element'; 7 | export * from './paste-page'; 8 | export * from './redo'; 9 | export * from './remove-element'; 10 | export * from './undo'; 11 | -------------------------------------------------------------------------------- /packages/core/src/renderer/edit-handlers/redo.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | 4 | export function redo({ app, store }: MessageHandlerContext): MessageHandler { 5 | return m => { 6 | if (app.getHasFocusedInput()) { 7 | return; 8 | } 9 | 10 | store.redo(); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/src/renderer/edit-handlers/remove-element.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | 4 | export function removeElement({ 5 | app, 6 | store 7 | }: MessageHandlerContext): MessageHandler { 8 | return m => { 9 | if (app.getHasFocusedInput()) { 10 | return; 11 | } 12 | 13 | store.removeElementById(m.payload); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/src/renderer/edit-handlers/undo.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | 4 | export function undo({ app, store }: MessageHandlerContext): MessageHandler { 5 | return () => { 6 | if (app.getHasFocusedInput()) { 7 | return; 8 | } 9 | 10 | store.undo(); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import { startRenderer } from './renderer'; 2 | import * as MobileDnD from 'mobile-drag-drop'; 3 | 4 | MobileDnD.polyfill(); 5 | startRenderer(); 6 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/activate-page.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | 4 | export function activatePage({ store }: MessageHandlerContext): MessageHandler { 5 | return m => { 6 | const project = store.getProject(); 7 | 8 | if (!project) { 9 | return; 10 | } 11 | 12 | const page = project.getPageById(m.payload.id); 13 | 14 | if (!page) { 15 | return; 16 | } 17 | 18 | page.setActive(true); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/app-request.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | 4 | export function appRequest({ app }: MessageHandlerContext): MessageHandler { 5 | return m => { 6 | app.send({ 7 | id: m.id, 8 | type: M.MessageType.AppResponse, 9 | transaction: m.transaction, 10 | payload: { 11 | app: app.toJSON() 12 | } 13 | }); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/change-user-store.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | 4 | export function changeUserStore({ 5 | store 6 | }: MessageHandlerContext): MessageHandler { 7 | return m => { 8 | const project = store.getProject(); 9 | 10 | if (!project || project.getId() !== m.payload.projectId) { 11 | return; 12 | } 13 | 14 | project.getUserStore().sync(m, { withEnhancer: false }); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/check-pattern-library.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | import * as Types from '@meetalva/types'; 4 | 5 | export function checkPatternLibrary({ 6 | store 7 | }: MessageHandlerContext): MessageHandler { 8 | return m => { 9 | const project = store.getProject(); 10 | 11 | if (!project) { 12 | return; 13 | } 14 | 15 | m.payload 16 | .map(check => ({ library: project.getPatternLibraryById(check.id), check })) 17 | .forEach(({ library, check }) => { 18 | if (typeof library === 'undefined') { 19 | return; 20 | } 21 | library.setState( 22 | check.connected 23 | ? Types.PatternLibraryState.Connected 24 | : Types.PatternLibraryState.Disconnected 25 | ); 26 | }); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/create-new-page.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | import * as Types from '@meetalva/types'; 4 | 5 | export function createNewPage({ store }: MessageHandlerContext): MessageHandler { 6 | return () => { 7 | const project = store.getProject(); 8 | 9 | if (!project) { 10 | return; 11 | } 12 | 13 | const page = store.executePageAddNew(); 14 | 15 | if (!page) { 16 | return; 17 | } 18 | 19 | store.getProject().setActivePage(page); 20 | page.setNameState(Types.EditableTitleState.Editing); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/highlight-element.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as Mobx from 'mobx'; 3 | import * as Model from '@meetalva/model'; 4 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 5 | import * as Types from '@meetalva/types'; 6 | 7 | export function highlightElement({ 8 | store 9 | }: MessageHandlerContext): MessageHandler { 10 | return m => { 11 | Mobx.runInAction(Types.ActionName.MatchHighlightedElement, () => { 12 | const project = store.getProject(); 13 | 14 | if (!project) { 15 | return; 16 | } 17 | 18 | if (!m.payload.element) { 19 | return; 20 | } 21 | 22 | const el = Model.Element.from(m.payload.element, { project }); 23 | const previousEl = project.getElementById(el.getId()); 24 | 25 | if (!previousEl) { 26 | return; 27 | } 28 | 29 | store.setHighlightedElement(previousEl, { flat: !store.getMetaDown() }); 30 | }); 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './activate-page'; 2 | export * from './app-request'; 3 | export * from './change-user-store'; 4 | export * from './check-pattern-library'; 5 | export * from './connect-pattern-library'; 6 | export * from './create-new-page'; 7 | export * from './highlight-element'; 8 | export * from './keyboard-change'; 9 | export * from './log'; 10 | export * from './project-records-changed'; 11 | export * from './project-request'; 12 | export * from './save'; 13 | export * from './select-element'; 14 | export * from './set-pane'; 15 | export * from './start-app'; 16 | export * from './update-downloaded'; 17 | export * from './update-pattern-library-request'; 18 | export * from './update-pattern-library'; 19 | export * from './updating-pattern-library'; 20 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/keyboard-change.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | 4 | export function keyboardChange({ store }: MessageHandlerContext): MessageHandler { 5 | return m => { 6 | store.setMetaDown(m.payload.metaDown); 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/log.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | 4 | export function log(_: MessageHandlerContext): MessageHandler { 5 | return m => { 6 | if (Array.isArray(m.payload)) { 7 | console.log(...m.payload); 8 | } else { 9 | console.log(m.payload); 10 | } 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/project-records-changed.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | 4 | export function projectRecordsChanged({ 5 | store 6 | }: MessageHandlerContext): MessageHandler { 7 | return m => { 8 | store.setProjects(m.payload.projects); 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/project-request.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | import * as Types from '@meetalva/types'; 4 | 5 | export function projectRequest({ store }: MessageHandlerContext): MessageHandler { 6 | const app = store.getApp(); 7 | 8 | return m => { 9 | const data = store.getProject(); 10 | 11 | if (!data) { 12 | app.send({ 13 | id: m.id, 14 | type: M.MessageType.ProjectResponse, 15 | payload: { 16 | data: undefined, 17 | status: Types.ProjectStatus.None 18 | } 19 | }); 20 | 21 | return; 22 | } 23 | 24 | app.send({ 25 | id: m.id, 26 | type: M.MessageType.ProjectResponse, 27 | transaction: m.transaction, 28 | payload: { 29 | data: data.toJSON(), 30 | status: Types.ProjectStatus.Ok 31 | } 32 | }); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/save.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | 4 | export function save({ store }: MessageHandlerContext): MessageHandler { 5 | return m => { 6 | const project = store.getProject(); 7 | 8 | if (project.getId() !== m.payload.previous) { 9 | return; 10 | } 11 | 12 | const previousDraft = project.getDraft(); 13 | 14 | project.setPath(m.payload.project.path); 15 | project.setId(m.payload.project.id); 16 | project.setName(m.payload.project.name); 17 | project.setDraft(m.payload.project.draft); 18 | 19 | if (previousDraft !== project.getDraft()) { 20 | store.commit(); 21 | } 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/select-element.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as Model from '@meetalva/model'; 3 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 4 | 5 | export function selectElement({ store }: MessageHandlerContext): MessageHandler { 6 | return m => { 7 | const project = store.getProject(); 8 | 9 | if (!project) { 10 | return; 11 | } 12 | 13 | if (!m.payload.element) { 14 | return; 15 | } 16 | 17 | const el = Model.Element.from(m.payload.element, { project }); 18 | const previousEl = project.getElementById(el.getId()); 19 | 20 | if (!previousEl) { 21 | return; 22 | } 23 | 24 | store.setSelectedElement(previousEl); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/set-pane.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | 4 | export function setPane({ app }: MessageHandlerContext): MessageHandler { 5 | return m => app.setPane(m.payload.pane, m.payload.visible); 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/start-app.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as Model from '@meetalva/model'; 3 | import * as Types from '@meetalva/types'; 4 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 5 | 6 | export function startApp({ store, app }: MessageHandlerContext): MessageHandler { 7 | return m => { 8 | store.setServerPort(Number(m.payload.port)); 9 | 10 | try { 11 | if (m.payload.app) { 12 | app.update(Model.AlvaApp.from(m.payload.app, { sender: store.getSender() })); 13 | } 14 | } catch (err) { 15 | console.error(err); 16 | app.setState(Types.AppState.Started); 17 | } finally { 18 | console.log(`App started on port ${store.getServerPort()}.`); 19 | app.setState(Types.AppState.Started); 20 | } 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/update-downloaded.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 3 | 4 | export function updateDownloaded({ 5 | store 6 | }: MessageHandlerContext): MessageHandler { 7 | return m => { 8 | store.getApp().setUpdate(m.payload); 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/update-pattern-library-request.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as T from '@meetalva/types'; 3 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 4 | 5 | export function updatePatternLibraryRequest({ 6 | store 7 | }: MessageHandlerContext): MessageHandler { 8 | return m => { 9 | const project = store.getProject(); 10 | 11 | if (!project) { 12 | return; 13 | } 14 | 15 | const library = project.getPatternLibraryById(m.payload.libId); 16 | 17 | if (!library) { 18 | return; 19 | } 20 | 21 | library.setState(T.PatternLibraryState.Connecting); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/src/renderer/message-handlers/updating-pattern-library.ts: -------------------------------------------------------------------------------- 1 | import * as M from '@meetalva/message'; 2 | import * as T from '@meetalva/types'; 3 | import { MessageHandlerContext, MessageHandler } from '../create-handlers'; 4 | 5 | export function updatingPatternLibrary({ 6 | store 7 | }: MessageHandlerContext): MessageHandler { 8 | return m => { 9 | const project = store.getProject(); 10 | 11 | if (!project) { 12 | return; 13 | } 14 | 15 | const library = project.getPatternLibraryById(m.payload.libraryId); 16 | 17 | if (!library) { 18 | return; 19 | } 20 | 21 | library.setState(T.PatternLibraryState.Connecting); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/src/renderer/renderer-document.ts: -------------------------------------------------------------------------------- 1 | import * as Types from '@meetalva/types'; 2 | import * as Model from '@meetalva/model'; 3 | 4 | export interface RenderDocumentData { 5 | content?: string; 6 | styles?: string; 7 | payload: { 8 | host: Types.HostType; 9 | view: Types.AlvaView; 10 | projectViewMode: Types.ProjectViewMode; 11 | project?: Model.Project; 12 | update?: Types.UpdateInfo; 13 | projects?: { path: string; name: string; valid: boolean }[]; 14 | }; 15 | } 16 | 17 | export const rendererDocument = (data: RenderDocumentData) => ` 18 | 19 | 20 | Alva 21 | 22 | ${data.styles || ''} 23 | 24 | 25 |
${data.content || ''}
26 | 29 | 30 | 31 | 32 | `; 33 | -------------------------------------------------------------------------------- /packages/core/src/resources/alpha/document.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetalva/alva/bfdb6aef4a55b5d99ed154a1dcced1acf9aca894/packages/core/src/resources/alpha/document.icns -------------------------------------------------------------------------------- /packages/core/src/resources/alpha/document.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetalva/alva/bfdb6aef4a55b5d99ed154a1dcced1acf9aca894/packages/core/src/resources/alpha/document.ico -------------------------------------------------------------------------------- /packages/core/src/resources/alpha/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetalva/alva/bfdb6aef4a55b5d99ed154a1dcced1acf9aca894/packages/core/src/resources/alpha/icon.icns -------------------------------------------------------------------------------- /packages/core/src/resources/alpha/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetalva/alva/bfdb6aef4a55b5d99ed154a1dcced1acf9aca894/packages/core/src/resources/alpha/icon.ico -------------------------------------------------------------------------------- /packages/core/src/resources/document.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetalva/alva/bfdb6aef4a55b5d99ed154a1dcced1acf9aca894/packages/core/src/resources/document.icns -------------------------------------------------------------------------------- /packages/core/src/resources/document.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetalva/alva/bfdb6aef4a55b5d99ed154a1dcced1acf9aca894/packages/core/src/resources/document.ico -------------------------------------------------------------------------------- /packages/core/src/resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetalva/alva/bfdb6aef4a55b5d99ed154a1dcced1acf9aca894/packages/core/src/resources/icon.icns -------------------------------------------------------------------------------- /packages/core/src/resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetalva/alva/bfdb6aef4a55b5d99ed154a1dcced1acf9aca894/packages/core/src/resources/icon.ico -------------------------------------------------------------------------------- /packages/core/src/sender/index.ts: -------------------------------------------------------------------------------- 1 | export * from './is-message'; 2 | export * from './is-message-type'; 3 | export * from './sender'; 4 | export * from './serde'; 5 | -------------------------------------------------------------------------------- /packages/core/src/sender/is-message-type.ts: -------------------------------------------------------------------------------- 1 | import { MessageType } from '@meetalva/message'; 2 | 3 | export function isMessageType(candidate: unknown): candidate is MessageType { 4 | return Object.values(MessageType).includes(candidate); 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/src/sender/is-message.ts: -------------------------------------------------------------------------------- 1 | import { Message } from '@meetalva/message'; 2 | import { isMessageType } from './is-message-type'; 3 | 4 | // tslint:disable-next-line:no-any 5 | export function isMessage(input: any): input is Message { 6 | if (typeof input !== 'object') { 7 | return false; 8 | } 9 | 10 | const type = input.type; 11 | 12 | if (typeof type !== 'string' || typeof input.id !== 'string') { 13 | return false; 14 | } 15 | 16 | if (!isMessageType(type)) { 17 | return false; 18 | } 19 | 20 | return true; 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/src/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from './server'; 2 | -------------------------------------------------------------------------------- /packages/core/src/server/routes/context.ts: -------------------------------------------------------------------------------- 1 | import * as Types from '@meetalva/types'; 2 | import * as Model from '@meetalva/model'; 3 | import * as M from '@meetalva/message'; 4 | 5 | export type ServerContext = Types.AlvaServer, Model.Project, M.Message>; 6 | -------------------------------------------------------------------------------- /packages/core/src/server/routes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './export'; 2 | export * from './library'; 3 | export * from './main'; 4 | export * from './preview'; 5 | export * from './project'; 6 | export * from './project-store'; 7 | export * from './scripts'; 8 | -------------------------------------------------------------------------------- /packages/core/src/server/routes/library.ts: -------------------------------------------------------------------------------- 1 | import * as Express from 'express'; 2 | import { ServerContext } from './context'; 3 | 4 | export function libraryRouteFactory(server: ServerContext): Express.RequestHandler { 5 | return async function libraryRoute(req: Express.Request, res: Express.Response): Promise { 6 | if (typeof req.params.projectId !== 'string' || typeof req.params.libraryId !== 'string') { 7 | res.sendStatus(404); 8 | return; 9 | } 10 | 11 | const project = await server.dataHost.getProject(req.params.projectId); 12 | 13 | if (!project) { 14 | res.sendStatus(404); 15 | return; 16 | } 17 | 18 | const library = project.getPatternLibraryById(req.params.libraryId); 19 | 20 | if (!library) { 21 | res.sendStatus(404); 22 | return; 23 | } 24 | 25 | res.type('js'); 26 | res.send(library.getBundle()); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/server/routes/project-store.ts: -------------------------------------------------------------------------------- 1 | import * as Express from 'express'; 2 | import * as RendererDocument from '../../renderer/renderer-document'; 3 | import * as Types from '@meetalva/types'; 4 | import { ServerContext } from './context'; 5 | 6 | export function projectStoureRouteFactory(server: ServerContext): Express.RequestHandler { 7 | return async function projectStoreRoute( 8 | req: Express.Request, 9 | res: Express.Response 10 | ): Promise { 11 | res.type('html'); 12 | 13 | if (typeof req.params.id !== 'string') { 14 | res.sendStatus(404); 15 | return; 16 | } 17 | 18 | const project = await server.dataHost.getProject(req.params.id); 19 | 20 | if (!project) { 21 | res.sendStatus(404); 22 | return; 23 | } 24 | 25 | res.send( 26 | RendererDocument.rendererDocument({ 27 | payload: { 28 | host: server.host.type, 29 | view: Types.AlvaView.PageDetail, 30 | projectViewMode: Types.ProjectViewMode.Libraries, 31 | project, 32 | update: await server.dataHost.getUpdate() 33 | } 34 | }) 35 | ); 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /packages/core/src/server/routes/project.ts: -------------------------------------------------------------------------------- 1 | import * as Express from 'express'; 2 | import * as RendererDocument from '../../renderer/renderer-document'; 3 | import * as Types from '@meetalva/types'; 4 | import { ServerContext } from './context'; 5 | 6 | export function projectRouteFactory(server: ServerContext): Express.RequestHandler { 7 | return async function projectRoute(req: Express.Request, res: Express.Response): Promise { 8 | res.type('html'); 9 | 10 | if (typeof req.params.id !== 'string') { 11 | res.sendStatus(404); 12 | return; 13 | } 14 | 15 | const project = await server.dataHost.getProject(req.params.id); 16 | 17 | if (!project) { 18 | res.sendStatus(404); 19 | return; 20 | } 21 | 22 | res.send( 23 | RendererDocument.rendererDocument({ 24 | payload: { 25 | host: server.host.type, 26 | view: Types.AlvaView.PageDetail, 27 | projectViewMode: Types.ProjectViewMode.Design, 28 | project, 29 | update: await server.dataHost.getUpdate() 30 | } 31 | }) 32 | ); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/src/store/index.ts: -------------------------------------------------------------------------------- 1 | export * from './view-store'; 2 | export * from './menu-store'; 3 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [ 3 | { 4 | "path": "../analyzer" 5 | }, 6 | { 7 | "path": "../analyzer-cli" 8 | }, 9 | { 10 | "path": "../components" 11 | }, 12 | { 13 | "path": "../essentials" 14 | }, 15 | { 16 | "path": "../message" 17 | }, 18 | { 19 | "path": "../model" 20 | }, 21 | { 22 | "path": "../model-tree" 23 | }, 24 | { 25 | "path": "../util" 26 | }, 27 | { 28 | "path": "../types" 29 | } 30 | ], 31 | "compilerOptions": { 32 | "composite": true, 33 | "outDir": "lib", 34 | "skipLibCheck": true, 35 | "lib": ["dom", "es2017"], 36 | "experimentalDecorators": true, 37 | "moduleResolution": "node", 38 | "module": "commonjs", 39 | "rootDir": "src", 40 | "target": "es2017", 41 | "jsx": "react", 42 | "strict": true, 43 | "sourceMap": true 44 | }, 45 | "include": ["src"] 46 | } 47 | -------------------------------------------------------------------------------- /packages/core/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:latest", "tslint-config-prettier"], 3 | "rules": { 4 | "array-type": false, 5 | "interface-name": false, 6 | "ordered-imports": false, 7 | "member-ordering": false, 8 | "jsdoc-format": false, 9 | "max-classes-per-file": false, 10 | "object-literal-sort-keys": false, 11 | "no-string-literal": false, 12 | "no-console": false, 13 | "variable-name": false, 14 | "no-var-requires": false, 15 | "no-implicit-dependencies": false, 16 | "no-this-assignment": false, 17 | "no-duplicate-imports": false, 18 | "no-shadowed-variable": false 19 | }, 20 | "linterOptions": { 21 | "exclude": [ 22 | "build" 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/essentials/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meetalva/essentials", 3 | "version": "1.0.0", 4 | "description": "Built-in components for basic layouts and logic", 5 | "scripts": { 6 | "check:dependencies": "alva-dependencies" 7 | }, 8 | "alva": { 9 | "name": "Essentials" 10 | }, 11 | "main": "lib/index.js", 12 | "homepage": "https://github.com/meetalva/alva", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@meetalva/tools": "1.0.0" 16 | }, 17 | "dependencies": { 18 | "@types/node": "^10.12.18", 19 | "@types/react": "16.7.18", 20 | "@types/react-helmet": "5.0.6", 21 | "react": "16.7.0", 22 | "react-helmet": "5.2.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/essentials/src/image.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ImageProps { 4 | /** @name Image @asset */ 5 | src?: string; 6 | 7 | /** @name Width */ 8 | width?: string; 9 | 10 | /** @name Height */ 11 | height?: string; 12 | 13 | /** @name Min Width */ 14 | minWidth?: string; 15 | 16 | /** @name Min Height */ 17 | minHeight?: string; 18 | 19 | /** @name Max Width */ 20 | maxWidth?: string; 21 | 22 | /** @name Max Height */ 23 | maxHeight?: string; 24 | 25 | /** @name Interaction @description You can set an interaction that happens on Click. */ 26 | onClick?: React.MouseEventHandler; 27 | } 28 | 29 | /** 30 | * @name Design 31 | * @description for Design Drafts 32 | * @icon Image 33 | * @patternType synthetic:image 34 | */ 35 | export const Image: React.SFC = props => { 36 | return ( 37 | 49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /packages/essentials/src/index.ts: -------------------------------------------------------------------------------- 1 | export { Text } from './text'; 2 | export { Box } from './box'; 3 | export { Conditional } from './conditional'; 4 | export { Image } from './image'; 5 | export { Link } from './link'; 6 | export { Page } from './page'; 7 | 8 | export { analysis } from './analysis'; 9 | -------------------------------------------------------------------------------- /packages/essentials/src/link.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface LinkProps { 4 | /** @name Interaction @description You can set an interaction that happens on Click. */ 5 | onClick?: React.MouseEventHandler; 6 | 7 | /** @ignore */ 8 | href?: string; 9 | 10 | /** @ignore */ 11 | target?: string; 12 | 13 | /** @ignore */ 14 | rel?: string; 15 | 16 | children?: React.ReactNode; 17 | } 18 | 19 | /** 20 | * @name Link 21 | * @description for Interaction 22 | * @icon ExternalLink 23 | * @patternType synthetic:link 24 | */ 25 | export const Link: React.SFC = props => { 26 | return ( 27 | 28 | {props.children} 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /packages/essentials/src/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Helmet } from 'react-helmet'; 3 | 4 | export interface PageProps { 5 | // tslint:disable-next-line:no-any 6 | /** @ignore */ head?: any; 7 | /** @ignore */ content?: any; 8 | children?: React.ReactNode; 9 | } 10 | 11 | /** 12 | * @name Page 13 | * @patternType synthetic:page 14 | */ 15 | export const Page: React.SFC = props => { 16 | return ( 17 |
18 | 19 | 20 | {props.head} 21 | 22 | {props.content} 23 | {props.children} 24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/essentials/src/text.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface TextProps { 4 | /** 5 | * @name Text 6 | * @default Text 7 | */ 8 | text: string; 9 | } 10 | 11 | const style = { 12 | display: 'inline-block' 13 | }; 14 | 15 | /** 16 | * @name Text 17 | * @description for Headlines, Copy and more 18 | * @icon Type 19 | * @patternType synthetic:text 20 | */ 21 | export const Text: React.SFC = props => { 22 | return {props.text}; 23 | }; 24 | -------------------------------------------------------------------------------- /packages/essentials/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "outDir": "lib", 5 | "rootDir": "src", 6 | "jsx": "react", 7 | "skipLibCheck": true, 8 | "moduleResolution": "node", 9 | "module": "commonjs", 10 | }, 11 | "include": ["src"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/message/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib'; 2 | -------------------------------------------------------------------------------- /packages/message/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /packages/message/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meetalva/message", 3 | "version": "1.0.0", 4 | "description": "Domain messages for Alva", 5 | "scripts": { 6 | "check:dependencies": "alva-dependencies" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/meetalva/alva.git" 11 | }, 12 | "author": { 13 | "email": "hey@meetalva.io", 14 | "name": "Meet Alva Team", 15 | "url": "https://meetalva.io/" 16 | }, 17 | "license": "MIT", 18 | "homepage": "https://meetalva.github.io/", 19 | "bugs": { 20 | "url": "https://github.com/meetalva/alva/issues" 21 | }, 22 | "devDependencies": { 23 | "@meetalva/tools": "1.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/message/src/envelope.ts: -------------------------------------------------------------------------------- 1 | export interface Envelope { 2 | id: string; 3 | appId?: string; 4 | transaction?: string; 5 | sender?: string[]; 6 | payload: T; 7 | type: V; 8 | } 9 | 10 | export type EmptyEnvelope = Envelope; 11 | -------------------------------------------------------------------------------- /packages/message/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './message'; 2 | export * from './request-response'; 3 | -------------------------------------------------------------------------------- /packages/message/src/request-response.ts: -------------------------------------------------------------------------------- 1 | import * as Message from './message'; 2 | 3 | export type RequestResponsePair = 4 | | AppRequestResponsePair 5 | | ProjectRequestResponsePair 6 | | OpenFileRequestResponsePair; 7 | 8 | export interface AppRequestResponsePair { 9 | request: Message.AppRequest; 10 | response: Message.AppResponse; 11 | } 12 | 13 | export interface ProjectRequestResponsePair { 14 | request: Message.ProjectRequest; 15 | response: Message.ProjectResponse; 16 | } 17 | 18 | export interface OpenFileRequestResponsePair { 19 | request: Message.OpenFileRequest; 20 | response: Message.OpenFileResponse; 21 | } 22 | -------------------------------------------------------------------------------- /packages/message/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [ 3 | { 4 | "path": "../util" 5 | } 6 | ], 7 | "compilerOptions": { 8 | "composite": true, 9 | "outDir": "lib", 10 | "skipLibCheck": true, 11 | "lib": ["es2017", "dom"], 12 | "experimentalDecorators": true, 13 | "moduleResolution": "node", 14 | "module": "commonjs", 15 | "rootDir": "src", 16 | "target": "es2017", 17 | "jsx": "react", 18 | "strict": true, 19 | "sourceMap": true, 20 | "declarationMap": true 21 | }, 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/model-tree/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib'; 2 | -------------------------------------------------------------------------------- /packages/model-tree/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /packages/model-tree/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meetalva/model-tree", 3 | "version": "1.0.0", 4 | "description": "Domain model tree for Alva", 5 | "scripts": { 6 | "check:dependencies": "alva-dependencies" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/meetalva/alva.git" 11 | }, 12 | "author": { 13 | "email": "hey@meetalva.io", 14 | "name": "Meet Alva Team", 15 | "url": "https://meetalva.io/" 16 | }, 17 | "license": "MIT", 18 | "homepage": "https://meetalva.github.io/", 19 | "bugs": { 20 | "url": "https://github.com/meetalva/alva/issues" 21 | }, 22 | "dependencies": { 23 | "@meetalva/model": "1.0.0", 24 | "@meetalva/types": "1.0.0" 25 | }, 26 | "devDependencies": { 27 | "@meetalva/tools": "1.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/model-tree/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './model-tree'; 2 | -------------------------------------------------------------------------------- /packages/model-tree/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [ 3 | { 4 | "path": "../types" 5 | }, 6 | { 7 | "path": "../model" 8 | } 9 | ], 10 | "compilerOptions": { 11 | "composite": true, 12 | "outDir": "lib", 13 | "skipLibCheck": true, 14 | "lib": ["es2017", "dom"], 15 | "experimentalDecorators": true, 16 | "moduleResolution": "node", 17 | "module": "commonjs", 18 | "rootDir": "src", 19 | "target": "es2017", 20 | "jsx": "react", 21 | "strict": true, 22 | "sourceMap": true, 23 | "declarationMap": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /packages/model/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib'; 2 | -------------------------------------------------------------------------------- /packages/model/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /packages/model/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meetalva/model", 3 | "version": "1.0.0", 4 | "description": "Domain model for Alva", 5 | "scripts": { 6 | "check:dependencies": "alva-dependencies" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/meetalva/alva.git" 11 | }, 12 | "author": { 13 | "email": "hey@meetalva.io", 14 | "name": "Meet Alva Team", 15 | "url": "https://meetalva.io/" 16 | }, 17 | "license": "MIT", 18 | "homepage": "https://meetalva.github.io/", 19 | "bugs": { 20 | "url": "https://github.com/meetalva/alva/issues" 21 | }, 22 | "dependencies": { 23 | "@meetalva/components": "1.0.0", 24 | "@meetalva/essentials": "1.0.0", 25 | "@meetalva/message": "1.0.0", 26 | "@meetalva/util": "1.0.0", 27 | "@meetalva/types": "1.0.0", 28 | "@types/json5": "0.0.30", 29 | "@types/lodash": "4.14.120", 30 | "@types/md5": "2.1.33", 31 | "@types/semver": "5.5.0", 32 | "@types/uuid": "3.4.4", 33 | "fuse.js": "3.3.0", 34 | "json5": "2.1.0", 35 | "lodash": "4.17.11", 36 | "md5": "2.2.1", 37 | "mobx": "5.0.3", 38 | "semver": "5.6.0", 39 | "uuid": "3.3.2" 40 | }, 41 | "devDependencies": { 42 | "@meetalva/tools": "1.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/model/src/any-model.ts: -------------------------------------------------------------------------------- 1 | import { AlvaApp } from './alva-app'; 2 | import { Element, ElementContent, ElementProperty } from './element'; 3 | import { ElementAction } from './element-action'; 4 | import { Page } from './page'; 5 | import { PatternLibrary } from './pattern-library'; 6 | import { Project } from './project'; 7 | import { AnyPatternProperty } from './pattern-property'; 8 | import { Pattern } from './pattern'; 9 | import { UserStore } from './user-store'; 10 | import { UserStoreAction } from './user-store-action'; 11 | import { UserStoreEnhancer } from './user-store-enhancer'; 12 | import { UserStoreProperty } from './user-store-property'; 13 | import { UserStoreReference } from './user-store-reference'; 14 | import * as M from '@meetalva/message'; 15 | 16 | export type AnyModel = 17 | | AlvaApp 18 | | AnyPatternProperty 19 | | Element 20 | | ElementContent 21 | | ElementAction 22 | | ElementProperty 23 | | Page 24 | | Pattern 25 | | PatternLibrary 26 | | Project 27 | | UserStore 28 | | UserStoreAction 29 | | UserStoreEnhancer 30 | | UserStoreProperty 31 | | UserStoreReference; 32 | -------------------------------------------------------------------------------- /packages/model/src/edit-history/edit-history-commit.ts: -------------------------------------------------------------------------------- 1 | import * as uuid from 'uuid'; 2 | import { ProjectUpdatePayload } from '@meetalva/message'; 3 | import { revert } from './revert'; 4 | 5 | export class EditHistoryCommit { 6 | public readonly id: string; 7 | public readonly changes: ProjectUpdatePayload[]; 8 | public readonly reverted: boolean; 9 | 10 | public constructor(init: { id: string; changes: ProjectUpdatePayload[]; reverted: boolean }) { 11 | this.id = init.id; 12 | this.changes = init.changes; 13 | this.reverted = init.reverted; 14 | } 15 | 16 | public revert(): EditHistoryCommit { 17 | return new EditHistoryCommit({ 18 | id: uuid.v4(), 19 | changes: this.changes.map(revert).reverse(), 20 | reverted: !this.reverted 21 | }); 22 | } 23 | 24 | public amend(b: EditHistoryCommit): EditHistoryCommit { 25 | return new EditHistoryCommit({ 26 | id: uuid.v4(), 27 | changes: [...b.changes, ...this.changes], 28 | reverted: !this.reverted 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/model/src/edit-history/edit-history-stage.ts: -------------------------------------------------------------------------------- 1 | import * as uuid from 'uuid'; 2 | import { ProjectUpdatePayload } from '@meetalva/message'; 3 | import { EditHistoryCommit } from './edit-history-commit'; 4 | import { revert } from './revert'; 5 | 6 | export class EditHistoryStage { 7 | public readonly id = uuid.v4(); 8 | public changes: ProjectUpdatePayload[] = []; 9 | 10 | public constructor(changes?: ProjectUpdatePayload[]) { 11 | if (changes) { 12 | this.changes = changes; 13 | } 14 | } 15 | 16 | public get length() { 17 | return this.changes.length; 18 | } 19 | 20 | public add(change: ProjectUpdatePayload): void { 21 | this.changes.push(change); 22 | } 23 | 24 | public toCommit(): EditHistoryCommit { 25 | return new EditHistoryCommit({ 26 | id: this.id, 27 | changes: this.changes, 28 | reverted: false 29 | }); 30 | } 31 | 32 | public revert(): EditHistoryStage { 33 | return new EditHistoryStage(this.changes.map(revert)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/model/src/edit-history/index.ts: -------------------------------------------------------------------------------- 1 | export * from './edit-history'; 2 | -------------------------------------------------------------------------------- /packages/model/src/element/element-property/index.ts: -------------------------------------------------------------------------------- 1 | export * from './element-property'; 2 | -------------------------------------------------------------------------------- /packages/model/src/element/index.ts: -------------------------------------------------------------------------------- 1 | export * from './element'; 2 | export * from './element-content'; 3 | export * from './element-property'; 4 | -------------------------------------------------------------------------------- /packages/model/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './alva-app'; 2 | export * from './edit-history'; 3 | export * from './element'; 4 | export * from './element-action'; 5 | export * from './page'; 6 | export * from './pattern-library'; 7 | export * from './pattern-property'; 8 | export * from './pattern'; 9 | export * from './project'; 10 | export * from './user-store'; 11 | export * from './user-store-action'; 12 | export * from './user-store-enhancer'; 13 | export * from './user-store-property'; 14 | export * from './user-store-reference'; 15 | export * from './any-model'; 16 | export * from './library-store'; 17 | export * from './library-store-item'; 18 | -------------------------------------------------------------------------------- /packages/model/src/library-data.ts: -------------------------------------------------------------------------------- 1 | interface MinimalData { 2 | name: string; 3 | version: string; 4 | } 5 | 6 | interface Data extends MinimalData { 7 | [key: string]: unknown; 8 | } 9 | 10 | export class LibraryData { 11 | private data: Data; 12 | 13 | private constructor(data: Data) { 14 | this.data = data; 15 | } 16 | 17 | public static fromPackageJson(input: unknown): LibraryData { 18 | if (typeof input !== 'object') { 19 | throw new Error(`Received type ${typeof input} as LibraryData input`); 20 | } 21 | 22 | if (input === null) { 23 | throw new Error(`Received null as LibraryData input`); 24 | } 25 | 26 | if (['input', 'name'].some(key => !input.hasOwnProperty(key))) { 27 | throw new Error(`input, name are required in LibraryData input`); 28 | } 29 | 30 | return new LibraryData(input as Data); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/model/src/page/index.ts: -------------------------------------------------------------------------------- 1 | export * from './page'; 2 | -------------------------------------------------------------------------------- /packages/model/src/pattern-library/builtin-pattern-library.ts: -------------------------------------------------------------------------------- 1 | import * as Essentials from '@meetalva/essentials'; 2 | import * as Types from '@meetalva/types'; 3 | import { PatternLibrary } from './pattern-library'; 4 | 5 | const data = Essentials.analysis as any; 6 | 7 | export const builtinPatternLibrary = (() => { 8 | // Legacy analysis persistence - Types.SerializedPatternLibrary 9 | if (data.model === Types.ModelName.PatternLibrary) { 10 | const serializedLibrary = data as Types.SerializedPatternLibrary; 11 | return PatternLibrary.from(serializedLibrary); 12 | } 13 | 14 | // Current analysis persistence - Types.LibraryAnalysis 15 | const analysis = data as Types.LibraryAnalysis; 16 | 17 | return PatternLibrary.fromAnalysis(analysis, { 18 | analyzeBuiltins: true, 19 | installType: Types.PatternLibraryInstallType.Local 20 | }); 21 | })(); 22 | -------------------------------------------------------------------------------- /packages/model/src/pattern-library/index.ts: -------------------------------------------------------------------------------- 1 | export * from './builtin-pattern-library'; 2 | export * from './pattern-library'; 3 | -------------------------------------------------------------------------------- /packages/model/src/pattern-property/index.ts: -------------------------------------------------------------------------------- 1 | export * from './asset-property'; 2 | export * from './boolean-property'; 3 | export * from './enum-property'; 4 | export * from './event-handler-property'; 5 | export * from './href-property'; 6 | export * from './number-property'; 7 | export * from './property-base'; 8 | export * from './property'; 9 | export * from './string-property'; 10 | export * from './unknown-property'; 11 | -------------------------------------------------------------------------------- /packages/model/src/pattern-search.ts: -------------------------------------------------------------------------------- 1 | import * as Fuse from 'fuse.js'; 2 | import * as Mobx from 'mobx'; 3 | import { Pattern } from './pattern'; 4 | import * as Types from '@meetalva/types'; 5 | 6 | export interface PatternSearchInit { 7 | patterns: Pattern[]; 8 | } 9 | 10 | export class PatternSearch { 11 | @Mobx.observable private fuse: Fuse; 12 | 13 | public constructor(init: PatternSearchInit) { 14 | this.fuse = new Fuse(init.patterns.map(item => item.toJSON()), { 15 | keys: ['name', 'description'] 16 | }); 17 | } 18 | 19 | public query(term: string): string[] { 20 | if (term.trim().length === 0) { 21 | // tslint:disable-next-line:no-any 22 | return (this.fuse as any).list.map((item: any) => item.id); 23 | } 24 | return this.fuse.search(term).map(match => match.id); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/model/src/pattern/index.ts: -------------------------------------------------------------------------------- 1 | export * from './pattern'; 2 | export * from './pattern-slot'; 3 | -------------------------------------------------------------------------------- /packages/model/src/project.test.ts: -------------------------------------------------------------------------------- 1 | import { Project } from './project'; 2 | import { Page } from './page'; 3 | 4 | test("project.update should apply b's page order to a", () => { 5 | const project = Project.create({ 6 | name: 'Project', 7 | draft: false, 8 | path: '/fake/path' 9 | }); 10 | 11 | const page = Page.create( 12 | { 13 | active: false, 14 | id: 'page-1', 15 | name: '' 16 | }, 17 | { project } 18 | ); 19 | 20 | project.addPage(page); 21 | 22 | const b = project.clone(); 23 | const [first, second] = b.getPages(); 24 | 25 | b.movePageAfter({ 26 | page: first, 27 | targetPage: second 28 | }); 29 | 30 | expect(b.getPages().map(p => p.getId())).toEqual([second.getId(), first.getId()]); 31 | 32 | project.update(b); 33 | 34 | expect(project.getPages().map(p => p.getId())).toEqual([second.getId(), first.getId()]); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/model/src/user-store-action/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user-store-action'; 2 | -------------------------------------------------------------------------------- /packages/model/src/user-store-property/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user-store-property'; 2 | -------------------------------------------------------------------------------- /packages/model/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [ 3 | { 4 | "path": "../essentials", 5 | }, 6 | { 7 | "path": "../message" 8 | }, 9 | { 10 | "path": "../util" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "composite": true, 15 | "outDir": "lib", 16 | "skipLibCheck": true, 17 | "lib": ["es2017", "dom"], 18 | "experimentalDecorators": true, 19 | "moduleResolution": "node", 20 | "module": "commonjs", 21 | "rootDir": "src", 22 | "target": "es2017", 23 | "jsx": "react", 24 | "strict": true, 25 | "sourceMap": true, 26 | "declarationMap": true 27 | }, 28 | "include": ["src"] 29 | } 30 | -------------------------------------------------------------------------------- /packages/site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meetalva/site", 3 | "version": "0.8.1", 4 | "private": true, 5 | "main": "./lib/site", 6 | "scripts": { 7 | "check:dependencies": "alva-dependencies --entry lib/site.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/meetalva/alva.git" 12 | }, 13 | "author": { 14 | "email": "hey@meetalva.io", 15 | "name": "Meet Alva Team", 16 | "url": "https://meetalva.io/" 17 | }, 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/meetalva/alva/issues" 21 | }, 22 | "dependencies": { 23 | "@emotion/styled": "^10.0.6", 24 | "@meetalva/alva-design": "^1.6.3", 25 | "@types/react": "16.4.11", 26 | "@types/react-helmet": "5.0.6", 27 | "react": "16.4.2", 28 | "react-dom": "16.4.2", 29 | "react-helmet": "5.2.0", 30 | "react-ga":"2.5.7" 31 | }, 32 | "devDependencies": { 33 | "@meetalva/tools": "1.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/site/src/cookie-notice.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as D from '@meetalva/alva-design'; 3 | 4 | export class CookieNotice extends React.Component { 5 | public state = { 6 | display: 7 | typeof document !== 'undefined' && document.cookie.indexOf('hidecookienotice=1') === -1 8 | }; 9 | 10 | public render() { 11 | return ( 12 | this.state.display === true && ( 13 | { 19 | document.cookie = 'hidecookienotice=1;path=/'; 20 | this.setState({ display: false }); 21 | }} 22 | /> 23 | ) 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/site/src/global-css.ts: -------------------------------------------------------------------------------- 1 | import * as GraphicWeb from './graphik-web'; 2 | 3 | export const globalCss = ` 4 | body { 5 | margin: 0; 6 | padding: 0; 7 | min-width: 320px; 8 | } 9 | 10 | @font-face { 11 | font-family: 'Graphik Web'; 12 | src: url(data:application/font-woff;charset=utf-8;base64,${GraphicWeb.GraphicWeb}) format('woff'); 13 | font-weight: 300; 14 | font-style: normal; 15 | } 16 | 17 | @font-face { 18 | font-family: 'Graphik Web'; 19 | src: url(data:application/font-woff;charset=utf-8;base64,${ 20 | GraphicWeb.GraphicWebRegular 21 | }) format('woff'); 22 | font-weight: 400; 23 | font-style: normal; 24 | } 25 | 26 | @font-face { 27 | font-family: 'Graphik Web'; 28 | src: url(data:application/font-woff;charset=utf-8;base64,${ 29 | GraphicWeb.GraphicWebBold 30 | }) format('woff'); 31 | font-weight: 500; 32 | font-style: normal; 33 | } 34 | `; 35 | -------------------------------------------------------------------------------- /packages/site/src/render.ts: -------------------------------------------------------------------------------- 1 | import { globalCss } from './global-css'; 2 | import { Helmet } from 'react-helmet'; 3 | 4 | export const render = (input: any) => { 5 | // tslint:disable-next-line:no-submodule-imports 6 | const ReactDOM = require('react-dom/server'); 7 | 8 | const html = ReactDOM.renderToString(input.default()); 9 | const helmet = Helmet.renderStatic(); 10 | 11 | return { 12 | head: [ 13 | helmet.title.toString(), 14 | helmet.meta.toString(), 15 | helmet.link.toString(), 16 | ``, 17 | helmet.style.toString() 18 | ].join('\n'), 19 | html, 20 | after: '' 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /packages/site/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "outDir": "lib", 5 | "rootDir": "src", 6 | "jsx": "react", 7 | "skipLibCheck": true, 8 | "moduleResolution": "node", 9 | "lib": ["dom", "es2017"] 10 | }, 11 | "include": ["src"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/tools/alva-dependencies.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./lib/check-dependencies'); 3 | -------------------------------------------------------------------------------- /packages/tools/alva-version.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Path = require('path'); 3 | const yargs = require('yargs-parser'); 4 | 5 | async function main(cli) { 6 | if (!cli.project) { 7 | console.log(`--project is required`); 8 | process.exit(1); 9 | } 10 | 11 | const projectPath = Path.resolve(process.cwd(), cli.project); 12 | const manifest = require(Path.join(projectPath, 'package.json')); 13 | console.log(manifest.version); 14 | } 15 | 16 | process.on('unhandledRejection', (_, error) => { 17 | console.trace(error); 18 | process.exit(1); 19 | }); 20 | 21 | main(yargs(process.argv.slice(2))).catch(err => { 22 | throw err; 23 | }); 24 | -------------------------------------------------------------------------------- /packages/tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meetalva/tools", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "check:dependencies": "node ./alva-dependencies.js -i dependency-check -i surge" 7 | }, 8 | "bin": { 9 | "alva-dependencies": "./alva-dependencies.js", 10 | "alva-deploy": "./alva-deploy.js", 11 | "alva-trigger": "./alva-trigger.js", 12 | "alva-version": "./alva-version.js", 13 | "alva-release": "./alva-release.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/meetalva/alva.git" 18 | }, 19 | "author": "", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/meetalva/alva/issues" 23 | }, 24 | "dependencies": { 25 | "@types/execa": "^0.9.0", 26 | "@types/read-pkg": "^3.0.0", 27 | "dargs": "^6.0.0", 28 | "dependency-check": "^3.3.0", 29 | "execa": "^1.0.0", 30 | "got": "^9.5.0", 31 | "lodash": "^4.17.11", 32 | "node-fetch": "2.1.2", 33 | "parse-github-url": "^1.0.2", 34 | "read-pkg": "^4.0.1", 35 | "semver": "^5.6.0", 36 | "semver-utils": "^1.1.4", 37 | "surge": "0.20.1", 38 | "yargs-parser": "10.1.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/tools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "outDir": "lib", 5 | "skipLibCheck": true, 6 | "lib": ["es2017"], 7 | "experimentalDecorators": true, 8 | "moduleResolution": "node", 9 | "module": "commonjs", 10 | "rootDir": "src", 11 | "target": "es2017", 12 | "jsx": "react", 13 | "strict": true, 14 | "sourceMap": true, 15 | "declarationMap": true 16 | }, 17 | "include": ["src"] 18 | } 19 | -------------------------------------------------------------------------------- /packages/types/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib'; 2 | -------------------------------------------------------------------------------- /packages/types/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /packages/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meetalva/types", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "check:dependencies": "alva-dependencies" 6 | }, 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/meetalva/alva.git" 10 | }, 11 | "author": { 12 | "email": "hey@meetalva.io", 13 | "name": "Meet Alva Team", 14 | "url": "https://meetalva.io/" 15 | }, 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/meetalva/alva/issues" 19 | }, 20 | "devDependencies": { 21 | "@meetalva/tools": "1.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/types/src/export.ts: -------------------------------------------------------------------------------- 1 | import * as Fs from 'fs'; 2 | 3 | export type ExportResult = ExportSuccess | ExportError; 4 | 5 | export enum ExportResultType { 6 | ExportSuccess, 7 | ExportError 8 | } 9 | 10 | export interface ExportSuccess { 11 | type: ExportResultType.ExportSuccess; 12 | fs: typeof Fs; 13 | } 14 | 15 | export interface ExportError { 16 | type: ExportResultType.ExportError; 17 | error: Error; 18 | } 19 | 20 | export enum ScreenshotType { 21 | Png 22 | } 23 | -------------------------------------------------------------------------------- /packages/types/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './export'; 2 | export * from './mobx'; 3 | export * from './serialized-model'; 4 | export * from './pattern-property'; 5 | export * from './types'; 6 | export * from './user-store'; 7 | export * from './persistence'; 8 | export * from './hosts'; 9 | export * from './server'; 10 | export * from './sender'; 11 | export * from './menu'; 12 | export * from './updater'; 13 | -------------------------------------------------------------------------------- /packages/types/src/persistence.ts: -------------------------------------------------------------------------------- 1 | export interface Persistable { 2 | // tslint:disable-next-line:no-any 3 | is(thing: any): T; 4 | toJSON(): string; 5 | } 6 | 7 | export enum PersistenceState { 8 | Error = 'error', 9 | Success = 'success' 10 | } 11 | 12 | export type PersistenceSerializeResult = PersistencePersistError | PersistencePersistSuccess; 13 | 14 | export interface PersistencePersistError { 15 | error: Error; 16 | state: PersistenceState.Error; 17 | } 18 | 19 | export interface PersistencePersistSuccess { 20 | contents: string; 21 | state: PersistenceState.Success; 22 | } 23 | 24 | export type PersistenceParseResult = PersistenceReadError | PersistenceReadSuccess; 25 | 26 | export interface PersistenceReadError { 27 | error: Error; 28 | state: PersistenceState.Error; 29 | } 30 | 31 | export interface PersistenceReadSuccess { 32 | contents: T; 33 | state: PersistenceState.Success; 34 | } 35 | -------------------------------------------------------------------------------- /packages/types/src/sender.ts: -------------------------------------------------------------------------------- 1 | export interface SenderMessage { 2 | type: any; 3 | id: string; 4 | payload: unknown; 5 | } 6 | 7 | export interface Sender { 8 | readonly id: string; 9 | 10 | match(type: T['type'], handler: (message: T) => void): Promise; 11 | 12 | unmatch(type: T['type'], handler: (message: T) => void): Promise; 13 | 14 | send(message: T): void; 15 | 16 | pass(envelope: string): void; 17 | 18 | transaction(message: T, { type }: { type: V['type'] }): Promise; 19 | 20 | setLog(log: undefined | typeof console.log): void; 21 | } 22 | -------------------------------------------------------------------------------- /packages/types/src/server.ts: -------------------------------------------------------------------------------- 1 | import { Sender, SenderMessage } from './sender'; 2 | import * as Hosts from './hosts'; 3 | import { Location } from './types'; 4 | 5 | /** 6 | * A - AlvaApp 7 | * P - Project 8 | */ 9 | export interface AlvaServer { 10 | location: Location; 11 | port: number; 12 | sender: Sender; 13 | host: Hosts.Host; 14 | dataHost: Hosts.DataHost

; 15 | start(): Promise; 16 | stop(): Promise; 17 | } 18 | -------------------------------------------------------------------------------- /packages/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "outDir": "lib", 5 | "skipLibCheck": true, 6 | "lib": ["es2017"], 7 | "experimentalDecorators": true, 8 | "moduleResolution": "node", 9 | "module": "commonjs", 10 | "rootDir": "src", 11 | "target": "es2017", 12 | "jsx": "react", 13 | "strict": true, 14 | "sourceMap": true, 15 | "declarationMap": true 16 | }, 17 | "include": ["src"] 18 | } 19 | -------------------------------------------------------------------------------- /packages/util/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib'; 2 | -------------------------------------------------------------------------------- /packages/util/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /packages/util/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meetalva/util", 3 | "version": "1.0.0", 4 | "description": "Internal utility functions for Alva", 5 | "scripts": { 6 | "check:dependencies": "alva-dependencies" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/meetalva/alva.git" 11 | }, 12 | "author": { 13 | "email": "hey@meetalva.io", 14 | "name": "Meet Alva Team", 15 | "url": "https://meetalva.io/" 16 | }, 17 | "license": "MIT", 18 | "homepage": "https://meetalva.github.io/", 19 | "bugs": { 20 | "url": "https://github.com/meetalva/alva/issues" 21 | }, 22 | "dependencies": { 23 | "@meetalva/components": "1.0.0", 24 | "url-search-params": "1.1.0", 25 | "util.promisify": "1.0.0" 26 | }, 27 | "devDependencies": { 28 | "@meetalva/tools": "1.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/util/src/drag-and-drop.ts: -------------------------------------------------------------------------------- 1 | export interface Indexable { 2 | getIndex(): number | undefined; 3 | getContainer(): unknown; 4 | } 5 | 6 | export function calculateDropIndex(init: { dragged: T; target: T }): number { 7 | const { dragged, target } = init; 8 | 9 | // We definitely know the drop target has a parent, thus an index 10 | const newIndex = target.getIndex()!; 11 | 12 | // The dragged element is dropped into another 13 | // leaf list than it was dragged from. 14 | // True for (1) new elements, (2) elements dragged to other parents 15 | if (dragged.getContainer() !== target.getContainer()) { 16 | return newIndex; 17 | } 18 | 19 | // If the dragged element has a parent, it has an index 20 | const currentIndex = dragged.getIndex()!; 21 | 22 | // The dragged element is dropped in the same leaf 23 | // list as it was dragged from. 24 | // Offset the index by the element itself missing from the new list. 25 | if (newIndex > currentIndex) { 26 | return newIndex - 1; 27 | } 28 | 29 | return newIndex; 30 | } 31 | -------------------------------------------------------------------------------- /packages/util/src/ensure-array.ts: -------------------------------------------------------------------------------- 1 | export function ensureArray(input: unknown): unknown[] { 2 | return (Array.isArray(input) ? input : [input]).filter(Boolean); 3 | } 4 | -------------------------------------------------------------------------------- /packages/util/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './compute-difference'; 2 | export * from './drag-and-drop'; 3 | export * from './ensure-array'; 4 | export * from './new-issue-url'; 5 | export * from './mkdirp'; 6 | export * from './noop'; 7 | export * from './parse-json'; 8 | export * from './set-search'; 9 | export * from './target'; 10 | export * from './to-json'; 11 | -------------------------------------------------------------------------------- /packages/util/src/mkdirp.ts: -------------------------------------------------------------------------------- 1 | import * as Fs from 'fs'; 2 | import * as Util from 'util'; 3 | import * as Path from 'path'; 4 | 5 | const promisify = require('util.promisify') as typeof Util.promisify; 6 | 7 | export async function mkdirp(dir: string, opts: { fs: typeof Fs }): Promise { 8 | const mkdir = promisify(opts.fs.mkdir).bind(opts.fs); 9 | const stat = promisify(opts.fs.stat).bind(opts.fs); 10 | 11 | try { 12 | await mkdir(dir); 13 | } catch (err) { 14 | switch (err.code) { 15 | case 'ENOENT': 16 | await mkdirp(Path.dirname(dir), opts); 17 | await mkdirp(dir, opts); 18 | break; 19 | default: 20 | try { 21 | const stats = await stat(dir); 22 | if (!stats.isDirectory()) { 23 | throw err; 24 | } 25 | } catch (statErr) { 26 | throw err; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/util/src/noop.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:no-empty 2 | export const noop = () => {}; 3 | -------------------------------------------------------------------------------- /packages/util/src/set-search.ts: -------------------------------------------------------------------------------- 1 | import * as Url from 'url'; 2 | 3 | /** 4 | * Adds an arbitrary key-value string map to a url's search string 5 | * @param src The url to modify 6 | * @param data The key value map to add to the url's search string 7 | */ 8 | export function setSearch(src: string, data: { [key: string]: string }): string { 9 | const parsed = Url.parse(src); 10 | 11 | const params = Object.entries(data).reduce((p, [key, value]) => { 12 | p.set(key, value); 13 | return p; 14 | }, new URLSearchParams(parsed.search || '')); 15 | 16 | parsed.search = params.toString(); 17 | 18 | return Url.format(parsed); 19 | } 20 | -------------------------------------------------------------------------------- /packages/util/src/to-json.ts: -------------------------------------------------------------------------------- 1 | export enum SerialializationType { 2 | Set = 'set' 3 | } 4 | 5 | export function toJSON(input: unknown): string { 6 | return JSON.stringify(input, (_, value) => { 7 | if (value instanceof Set) { 8 | return { type: SerialializationType.Set, data: Array.from(value) }; 9 | } 10 | 11 | return value; 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /packages/util/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [ 3 | { 4 | "path": "../components" 5 | } 6 | ], 7 | "compilerOptions": { 8 | "composite": true, 9 | "outDir": "lib", 10 | "skipLibCheck": true, 11 | "lib": ["es2017", "dom"], 12 | "experimentalDecorators": true, 13 | "moduleResolution": "node", 14 | "module": "commonjs", 15 | "rootDir": "src", 16 | "target": "es2017", 17 | "jsx": "react", 18 | "strict": true, 19 | "sourceMap": true, 20 | "declarationMap": true 21 | }, 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "schedule:weekly", 4 | ":maintainLockFilesWeekly" 5 | ], 6 | "labels": ["renovate"], 7 | "minor": { 8 | "groupName": "all minor" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "include": [], 4 | "references": [ 5 | { 6 | "path": "./packages/core" 7 | }, 8 | { 9 | "path": "./packages/site" 10 | }, 11 | { 12 | "path": "./packages/tools" 13 | } 14 | ], 15 | "compilerOptions": { 16 | "allowSyntheticDefaultImports": false, 17 | "composite": true, 18 | "downlevelIteration": true, 19 | "experimentalDecorators": true, 20 | "jsx": "react", 21 | "lib": ["dom", "es2017"], 22 | "module": "es2015", 23 | "moduleResolution": "node", 24 | "skipLibCheck": true, 25 | "sourceMap": true, 26 | "strict": true, 27 | "target": "es2017", 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:latest", "tslint-config-prettier"], 3 | "rules": { 4 | "array-type": false, 5 | "interface-name": false, 6 | "ordered-imports": false, 7 | "member-ordering": false, 8 | "jsdoc-format": false, 9 | "max-classes-per-file": false, 10 | "object-literal-sort-keys": false, 11 | "no-string-literal": false, 12 | "no-console": false, 13 | "variable-name": false, 14 | "no-var-requires": false, 15 | "no-implicit-dependencies": false, 16 | "no-this-assignment": false, 17 | "no-duplicate-imports": false, 18 | "no-shadowed-variable": false 19 | } 20 | } 21 | --------------------------------------------------------------------------------