32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/packages/demoboard-ui/src/components/input/input.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import { rgba } from 'polished'
9 | import React, { useCallback } from 'react'
10 | import styled from 'styled-components'
11 |
12 | import { beaconRing, colors, easings, shadows } from '../../constants'
13 |
14 | const StyledInputOutline = styled.div``
15 | const StyledInputWrapper = styled.div`
16 | position: relative;
17 | background-color: ${colors.white};
18 | border-radius: 99px;
19 | display: flex;
20 | flex: 1;
21 | z-index: 1;
22 | `
23 | const StyledInput = styled.input`
24 | appearance: none;
25 | background-color: transparent;
26 | border: none;
27 | border-radius: 99px;
28 | box-sizing: border-box;
29 | box-shadow: ${shadows.sunk()}, ${shadows.drop()};
30 | color: ${colors.darkerGrey};
31 | display: block;
32 | font-size: 12px;
33 | padding: 0 1rem;
34 | transition: background-color 200ms ${easings.easeOut};
35 | outline: none;
36 | width: 100%;
37 |
38 | :hover {
39 | background-color: ${rgba(colors.lightGrey, 0.15)};
40 | }
41 |
42 | ${beaconRing(` + ${StyledInputOutline}`, '99px')}
43 | `
44 |
45 | export interface InputProps
46 | extends Omit, 'onChange'> {
47 | onChange?: (value: string) => void
48 | }
49 |
50 | export const Input = ({ onChange, ...rest }: InputProps) => (
51 |
52 | ) => {
56 | if (onChange) {
57 | onChange(event.target.value)
58 | }
59 | },
60 | [onChange],
61 | )}
62 | />
63 |
64 |
65 | )
66 |
--------------------------------------------------------------------------------
/packages/demoboard-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@frontarm/demoboard-ui",
3 | "version": "0.1.29",
4 | "description": "Embeddable code editor",
5 | "author": "James K Nelson ",
6 | "license": "Apache-2.0",
7 | "main": "dist/commonjs/index.js",
8 | "module": "dist/es/index.js",
9 | "types": "dist/types/index.d.ts",
10 | "scripts": {
11 | "clean": "rimraf dist",
12 | "build": "cross-env NODE_ENV=development rollup -c",
13 | "build:watch": "yarn run build --watch",
14 | "lint": "eslint --ext js,ts,tsx src",
15 | "prepare": "yarn run clean && yarn run build",
16 | "test": "jest",
17 | "test:watch": "jest --watch",
18 | "storybook": "start-storybook -p 6006",
19 | "build-storybook": "build-storybook"
20 | },
21 | "dependencies": {
22 | "@frontarm/demoboard-core": "^0.1.29",
23 | "codemirror": "^5.49.2",
24 | "exenv": "^1.2.2",
25 | "json-stringify-safe": "^5.0.1",
26 | "polished": "^3.4.2",
27 | "reakit": "^1.0.0-beta.9",
28 | "tslib": "1.10.0",
29 | "use-codemirror": "^0.2.0"
30 | },
31 | "peerDependencies": {
32 | "@types/codemirror": "^0.0.79",
33 | "@types/react": "^16.9.0",
34 | "@types/styled-components": "^4.1.8",
35 | "react": "^16.9.0",
36 | "styled-components": "^4.4.1"
37 | },
38 | "publishConfig": {
39 | "access": "public"
40 | },
41 | "gitHead": "c95bc9be5e02686208977653cabb28e7d7b1bd75",
42 | "devDependencies": {
43 | "@babel/core": "^7.7.2",
44 | "@babel/plugin-proposal-export-default-from": "^7.5.2",
45 | "@storybook/addon-actions": "^5.2.6",
46 | "@storybook/addon-info": "^5.2.6",
47 | "@storybook/addon-links": "^5.2.6",
48 | "@storybook/addons": "^5.2.6",
49 | "@storybook/react": "^5.2.6",
50 | "@svgr/webpack": "^4.3.3",
51 | "awesome-typescript-loader": "^5.2.1",
52 | "babel-loader": "^8.0.6",
53 | "react-docgen-typescript-loader": "^3.3.0"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/demoboard-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@frontarm/demoboard-core",
3 | "version": "0.1.29",
4 | "description": "Embeddable code editor",
5 | "author": "James K Nelson ",
6 | "license": "Apache-2.0",
7 | "main": "dist/commonjs/index.js",
8 | "module": "dist/es/index.js",
9 | "types": "dist/types/index.d.ts",
10 | "scripts": {
11 | "clean": "rimraf dist/**",
12 | "build": "cross-env NODE_ENV=development rollup -c",
13 | "build:production": "cross-env NODE_ENV=production rollup -c",
14 | "build:watch": "yarn run build --watch",
15 | "lint": "eslint --ext js,ts,tsx src",
16 | "prepare": "yarn run clean && yarn run build:production",
17 | "test": "env-cmd -f .env.test jest",
18 | "test:watch": "yarn test --watch"
19 | },
20 | "dependencies": {
21 | "@frontarm/demoboard-messaging": "^0.1.20",
22 | "@frontarm/demoboard-runtime": "^0.1.29",
23 | "@frontarm/demoboard-worker": "^0.1.26",
24 | "automerge": "^0.12.1",
25 | "comlink": "^4.0.5",
26 | "exenv": "^1.2.2",
27 | "file-saver": "^2.0.0",
28 | "jszip": "^3.1.5",
29 | "react-hot-keys": "^1.2.2",
30 | "react-visibility-sensor": "^4.1.2",
31 | "resize-observer-polyfill": "^1.5.0",
32 | "source-map": "^0.7.3",
33 | "tslib": "1.10.0"
34 | },
35 | "devDependencies": {
36 | "@testing-library/jest-dom": "^4.2.3",
37 | "@testing-library/react": "^9.3.2",
38 | "@testing-library/react-hooks": "^3.2.1",
39 | "env-cmd": "^10.0.1",
40 | "file-loader": "^2.0.0"
41 | },
42 | "peerDependencies": {
43 | "@types/codemirror": "^0.0.77",
44 | "@types/exenv": "^1.2.0",
45 | "@types/jszip": "^3.1.6",
46 | "@types/react": "^16.9.0",
47 | "@types/react-dom": "^16.9.0",
48 | "react": "^16.8.6",
49 | "react-dom": "^16.8.6"
50 | },
51 | "publishConfig": {
52 | "access": "public"
53 | },
54 | "gitHead": "c95bc9be5e02686208977653cabb28e7d7b1bd75"
55 | }
56 |
--------------------------------------------------------------------------------
/packages/demoboard-core/test/useDemoboardBuild.test.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import * as React from 'react'
9 | import TestRenderer from 'react-test-renderer'
10 | import { useDemoboardBuild } from '../src'
11 |
12 | const act = TestRenderer.act
13 |
14 | describe('useDemoboardBuild', () => {
15 | test('initially outputs null, and outputs success once complete', async () => {
16 | function Test() {
17 | let project = useDemoboardBuild('testid', {
18 | entryPathname: '/index.js',
19 | sources: {
20 | '/index.js': `console.log("hello, world")`,
21 | },
22 | })
23 |
24 | return <>{project && project.status}>
25 | }
26 |
27 | let component: any
28 | await act(async () => {
29 | component = TestRenderer.create()
30 | expect(component.toJSON()).toEqual(null)
31 | })
32 | expect(component.toJSON()).toEqual('success')
33 | })
34 |
35 | test('transforms mdx files', async () => {
36 | let build: any
37 |
38 | function Test() {
39 | build = useDemoboardBuild('testid', {
40 | entryPathname: '/README.mdx',
41 | sources: {
42 | '/README.mdx': `# Hello world\n\nI'm a markdown file`,
43 | },
44 | })
45 |
46 | return <>{build && build.status}>
47 | }
48 |
49 | let component: any
50 | await act(async () => {
51 | component = TestRenderer.create()
52 | })
53 |
54 | expect(component.toJSON()).toEqual('success')
55 |
56 | let transformedSource =
57 | build &&
58 | build.transformedModules &&
59 | build.transformedModules['/README.mdx'].transformedSource
60 |
61 | expect(transformedSource).toMatch(`"Hello world"`)
62 | expect(transformedSource).toMatch(`function MDXContent`)
63 | })
64 | })
65 |
--------------------------------------------------------------------------------
/packages/demoboard-core/src/instance/DemoboardInstanceIFrame.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import * as React from 'react'
9 | import { DemoboardInstance } from '../types'
10 |
11 | export interface DemoboardInstanceIFrameProps
12 | extends React.IframeHTMLAttributes {
13 | instance: DemoboardInstance
14 | }
15 |
16 | export function DemoboardInstanceIFrame({
17 | children,
18 | instance,
19 | ...rest
20 | }: DemoboardInstanceIFrameProps) {
21 | let status = instance.status
22 | if (status === 'error' || status === 'empty') {
23 | return
24 | } else if (status === 'external') {
25 | return
26 | } else {
27 | return (
28 |
39 | )
40 | }
41 | }
42 |
43 | interface StaticIFrameProps {
44 | props: React.IframeHTMLAttributes
45 | forwardRef: React.Ref
46 | }
47 |
48 | /**
49 | * Re-rendering an iframe can cause it to refresh even if the source hasn't,
50 | * so we want manual control over re-renders.
51 | */
52 | class StaticIFrame extends React.Component {
53 | shouldComponentUpdate(nextProps: StaticIFrameProps) {
54 | return nextProps.forwardRef !== this.props.forwardRef
55 | }
56 |
57 | render() {
58 | return (
59 |
64 | )
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/packages/demoboard-messaging/src/replicator/index.ts:
--------------------------------------------------------------------------------
1 | import Replicator from 'replicator'
2 |
3 | import arithmeticTransform from './arithmeticTransform'
4 | import functionTransform from './functionTransform'
5 | import htmlTransform from './htmlTransform'
6 | import mapTransform from './mapTransform'
7 | import promiseTransform from './promiseTransform'
8 |
9 | const transforms = [
10 | htmlTransform,
11 | functionTransform,
12 | arithmeticTransform,
13 | mapTransform,
14 | ]
15 |
16 | let replicator
17 | function getReplicator() {
18 | if (!replicator) {
19 | replicator = new Replicator()
20 | replicator.addTransforms(transforms)
21 | }
22 | return replicator
23 | }
24 | export function encode(value: any): string {
25 | return getReplicator().encode(value)
26 | }
27 |
28 | /**
29 | * This function finds promises as it encodes the json, and adds them to the
30 | * promise transform's `outcomeStoredPromises` property. If you run the
31 | * encode again after any of these promises change, it'll output a value with
32 | * the promise's result.
33 | *
34 | * It's ugly... but it allows promise results to be shown in the console.
35 | */
36 | let replicatorWithPromises
37 | function getReplicatorWithPromises() {
38 | if (!replicatorWithPromises) {
39 | replicatorWithPromises = new Replicator()
40 | replicatorWithPromises.addTransforms(
41 | (transforms as any).concat(promiseTransform),
42 | )
43 | }
44 | return replicatorWithPromises
45 | }
46 |
47 | export interface EncodedValueWithPromises {
48 | json: string
49 | promises: Promise[]
50 | }
51 | export function encodeWithPromises(value: any): EncodedValueWithPromises {
52 | let json = getReplicatorWithPromises().encode(value)
53 | let promises = promiseTransform.outcomeStoredPromises.slice(0)
54 | promiseTransform.outcomeStoredPromises.length = 0
55 | return {
56 | json,
57 | promises,
58 | }
59 | }
60 |
61 | export function decode(value: any) {
62 | return getReplicatorWithPromises().decode(value)
63 | }
64 |
--------------------------------------------------------------------------------
/packages/demoboard-worker/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@frontarm/demoboard-worker",
3 | "version": "0.1.26",
4 | "description": "Embeddable code editor",
5 | "author": "James K Nelson ",
6 | "license": "Apache-2.0",
7 | "main": "dist/umd/index.js",
8 | "module": "dist/es/index.js",
9 | "types": "dist/types/index.d.ts",
10 | "scripts": {
11 | "clean": "rimraf dist/**",
12 | "build": "cross-env NODE_OPTIONS=\"--max-old-space-size=4096\" NODE_ENV=development rollup -c",
13 | "build:production": "cross-env NODE_ENV=production rollup -c",
14 | "build:watch": "yarn run build --watch",
15 | "build:watch:no-umd": "cross-env UMD=exclude yarn run build --watch",
16 | "build:watch:umd-only": "cross-env UMD=only yarn run build --watch",
17 | "lint": "eslint --ext js,ts,tsx src",
18 | "prepare": "yarn run clean && yarn run build:production",
19 | "test": "jest",
20 | "test:watch": "jest --watch"
21 | },
22 | "dependencies": {
23 | "@babel/standalone": "^7.4.5",
24 | "@frontarm/demoboard-messaging": "^0.1.20",
25 | "babel-plugin-dynamic-import-node": "2.2.0",
26 | "babel-plugin-styled-components": "1.10.0",
27 | "comlink": "^4.0.5",
28 | "hast-util-to-html": "^5.0.0",
29 | "hast-util-to-string": "^1.0.1",
30 | "lru-cache": "^5.1.1",
31 | "postcss": "^7.0.7",
32 | "postcss-modules": "^1.4.1",
33 | "prettier": "^1.16.4",
34 | "refractor": "^2.6.2",
35 | "remark-emoji": "^2.0.2",
36 | "remark-images": "^0.16.0",
37 | "remark-slug": "^5.1.0",
38 | "remark-textr": "^3.0.2",
39 | "sass.js": "^0.10.12",
40 | "semver": "^6.3.0",
41 | "typographic-base": "^1.0.4",
42 | "unist-util-visit": "^1.4.0"
43 | },
44 | "peerDependencies": {
45 | "@mdx-js/mdx": "^1.4.5",
46 | "@types/exenv": "^1.2.0",
47 | "@types/jszip": "^3.1.6",
48 | "@types/lru-cache": "^5.1.0"
49 | },
50 | "publishConfig": {
51 | "access": "public"
52 | },
53 | "gitHead": "c95bc9be5e02686208977653cabb28e7d7b1bd75"
54 | }
55 |
--------------------------------------------------------------------------------
/packages/demoboard-core/src/worker/getWorkerByFetch.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import * as Comlink from 'comlink'
9 | import { DemoboardWorker } from '@frontarm/demoboard-worker'
10 |
11 | export interface DemoboardWorkerURLs {
12 | worker: string
13 | transformBase?: string
14 | transformOverrides?: { [name: string]: string }
15 | }
16 |
17 | async function fetchWorker(
18 | urls: DemoboardWorkerURLs,
19 | ): Promise {
20 | const res = await fetch(urls.worker, {
21 | credentials: 'same-origin',
22 | })
23 | if (!res.ok) {
24 | throw new Error("Couldn't load demoboard-worker")
25 | }
26 | const source = await res.text()
27 | const blob = new Blob([source], { type: 'text/javascript' })
28 | const workerURL = URL.createObjectURL(blob)
29 | return Comlink.wrap(new Worker(workerURL))
30 | }
31 |
32 | export default function getWorkerByFetch(
33 | urls: DemoboardWorkerURLs,
34 | ): DemoboardWorker {
35 | let workerPromise =
36 | typeof Worker === 'undefined'
37 | ? Promise.resolve({} as any)
38 | : fetchWorker(urls)
39 | return {
40 | fetchDependency: async options => {
41 | const worker = await workerPromise
42 | return worker.fetchDependency(options)
43 | },
44 | clearBuildCache: async id => {
45 | const worker = await workerPromise
46 | return worker.clearBuildCache(id)
47 | },
48 | build: async options => {
49 | const worker = await workerPromise
50 | return worker.build({
51 | ...options,
52 | transformFetchOptions: {
53 | // A worker using fetch needs these URLs, as it can't access the
54 | // transforms using `import()`
55 | baseURL: urls.transformBase,
56 | overrideURLs: urls.transformOverrides,
57 |
58 | ...options.transformFetchOptions,
59 | },
60 | })
61 | },
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/packages/demoboard-core/src/types/DemoboardBuild.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import {
9 | DemoboardWorkerBuildRule,
10 | DemoboardWorkerTransformFetchOptions,
11 | DemoboardWorkerTransformedModule,
12 | } from '@frontarm/demoboard-worker'
13 |
14 | export type DemoboardBuildStatus = 'busy' | 'error' | 'success'
15 |
16 | export interface DemoboardBuild {
17 | config: DemoboardBuildConfig
18 | status: DemoboardBuildStatus
19 | version: number
20 |
21 | id: string
22 |
23 | containerURL: string
24 | runtimeURL: string
25 |
26 | /**
27 | * Indicates that newer build config is available, but it hasn't been built
28 | * as the build is currently paused.
29 | */
30 | stale: boolean
31 |
32 | error: null | any
33 | html: null | string
34 | transformedModules: null | {
35 | [name: string]: DemoboardWorkerTransformedModule
36 | }
37 | }
38 |
39 | export interface DemoboardBuildConfig {
40 | baseURL?: string
41 |
42 | buildRules?: DemoboardWorkerBuildRule[]
43 |
44 | containerURL?: string
45 |
46 | /**
47 | * The number of milliseconds to debounce changes before rebuilding.
48 | */
49 | debounce?: number
50 |
51 | dependencies?: {
52 | [packageName: string]: string
53 | }
54 |
55 | entryPathname: string
56 |
57 | /**
58 | * Specify packages/modules that should be mocked with other packages/modules.
59 | */
60 | mocks?: { [module: string]: string }
61 |
62 | /**
63 | * If true, the build will be paused without removing the cache, and
64 | * the build will be considered `busy`.
65 | *
66 | * This should always be set to true when rendering server-side. It also
67 | * can be set to true while the demoboard is off screen.
68 | */
69 | pause?: boolean
70 |
71 | runtimeURL?: string
72 |
73 | sources: { [pathname: string]: string }
74 |
75 | transformFetchOptions?: DemoboardWorkerTransformFetchOptions
76 | }
77 |
--------------------------------------------------------------------------------
/packages/demoboard-worker/src/transforms/babel/babel-plugin-prevent-infinite-loops.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | * Copyright (c) 2017, Amjad Masad
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | */
8 |
9 | // Based on https://repl.it/site/blog/infinite-loops.
10 |
11 | const MAX_ITERATIONS = 10001
12 |
13 | export default ({ types: t, template }: any) => {
14 | // We set a global so that we can later fail the test
15 | // even if the error ends up being caught by the code.
16 | const buildGuard = template(`
17 | if (!global.allowInfiniteLoops && ITERATOR++ > MAX_ITERATIONS) {
18 | if (!global.infiniteLoopError) {
19 | global.infiniteLoopError = new RangeError(
20 | 'Potential infinite loop: exceeded ' +
21 | MAX_ITERATIONS +
22 | ' iterations. To disable this error, set "window.allowInfiniteLoops = true".'
23 | );
24 | }
25 | throw global.infiniteLoopError;
26 | }
27 | `)
28 |
29 | return {
30 | visitor: {
31 | 'WhileStatement|ForStatement|DoWhileStatement': (
32 | path: any,
33 | file: any,
34 | ) => {
35 | // An iterator that is incremented with each iteration
36 | const iterator = path.scope.parent.generateUidIdentifier('loopIt')
37 | const iteratorInit = t.numericLiteral(0)
38 | path.scope.parent.push({
39 | id: iterator,
40 | init: iteratorInit,
41 | })
42 | // If statement and throw error if it matches our criteria
43 | const guard = buildGuard({
44 | ITERATOR: iterator,
45 | MAX_ITERATIONS: t.numericLiteral(MAX_ITERATIONS),
46 | })
47 | // No block statment e.g. `while (1) 1;`
48 | if (!path.get('body').isBlockStatement()) {
49 | const statement = path.get('body').node
50 | path.get('body').replaceWith(t.blockStatement([guard, statement]))
51 | } else {
52 | path.get('body').unshiftContainer('body', guard)
53 | }
54 | },
55 | },
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
--------------------------------------------------------------------------------
/packages/demoboard-worker/src/transforms/babel/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import dynamicImportPlugin from 'babel-plugin-dynamic-import-node'
9 | import styledComponentsPlugin from 'babel-plugin-styled-components'
10 |
11 | import register from '../register'
12 | import preventInfiniteLoopsPlugin from './babel-plugin-prevent-infinite-loops'
13 |
14 | register(
15 | 'babel',
16 | ({ babelDetective, babelTransform, errors }) =>
17 | async function demoboardBabelTransform({ originalSource, pathname }) {
18 | try {
19 | const babelOutput = babelTransform(originalSource, {
20 | filename: pathname,
21 |
22 | presets: ['es2015', 'es2016', 'es2017', 'react', 'stage-3'],
23 | plugins: [
24 | 'syntax-object-rest-spread',
25 | 'proposal-object-rest-spread',
26 | preventInfiniteLoopsPlugin,
27 | dynamicImportPlugin,
28 | [
29 | styledComponentsPlugin,
30 | {
31 | // Don't attempt to make use of the filesystem
32 | fileName: false,
33 | ssr: false,
34 | cssProp: true,
35 | },
36 | ],
37 | babelDetective,
38 | ],
39 |
40 | // This keeps comments on the correct line
41 | retainLines: true,
42 |
43 | sourceMaps: true,
44 | sourceType: 'module',
45 | })
46 |
47 | return {
48 | css: null,
49 | dependencies: babelOutput.metadata.requires || [],
50 | map: babelOutput.map,
51 | originalSource,
52 | pathname,
53 | transformedSource: babelOutput.code,
54 | }
55 | } catch (e) {
56 | console.error(e)
57 |
58 | const positionMatch = e.message.match(/\((\d+):(\d+)\)/)
59 |
60 | throw new errors.DemoboardTransformError({
61 | sourceFile: pathname,
62 | message: e.message,
63 | lineNumber: positionMatch && positionMatch[1],
64 | charNumber: positionMatch && positionMatch[2],
65 | })
66 | }
67 | },
68 | )
69 |
--------------------------------------------------------------------------------
/packages/demoboard-ui/src/components/Icon.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import React from 'react'
9 | import styled, { css } from 'styled-components'
10 | import * as Icons from '../icons/components'
11 |
12 | import { colors } from '../constants'
13 | import addDefaultPixelUnits from '../utils/addDefaultPixelUnits'
14 |
15 | export type IconGlyph = keyof typeof Icons
16 |
17 | const DEFAULT_ICON_SIZE = '16px'
18 |
19 | interface StyledIconContainerProps {
20 | inline: boolean
21 | }
22 |
23 | const StyledIconContainer = styled.div`
24 | display: ${props => (props.inline ? 'inline-flex' : 'flex')};
25 | text-align: center;
26 | `
27 |
28 | interface StyledIconProps {
29 | size: string
30 | viewBox?: string
31 | }
32 |
33 | const StyledIcon = styled.div`
34 | display: block;
35 | margin: 0 auto;
36 | height: ${props => props.size};
37 | width: ${props => props.size};
38 |
39 | ${props =>
40 | props.color &&
41 | css`
42 | fill: ${props.color};
43 | `}
44 | `
45 |
46 | export interface IconProps extends React.HTMLAttributes {
47 | accessibilityLabel?: string
48 | color?: string
49 | glyph: IconGlyph
50 | inline?: boolean
51 | testID?: string
52 | size?: string | number
53 | }
54 |
55 | export const Icon = React.forwardRef(
56 | (
57 | {
58 | accessibilityLabel,
59 | color = colors.darkGrey,
60 | glyph,
61 | inline = true,
62 | size = DEFAULT_ICON_SIZE,
63 | testID,
64 | ...props
65 | },
66 | ref,
67 | ) => {
68 | const IconComponent = Icons[glyph]
69 | const sizeWithUnits = addDefaultPixelUnits(size)
70 |
71 | return (
72 |
73 |
83 |
84 | )
85 | },
86 | )
87 |
--------------------------------------------------------------------------------
/packages/demoboard-core/src/types/DemoboardLayout.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import { DemoboardPanelType } from './DemoboardPanelType'
9 |
10 | export type DemoboardLayoutMode = 'mobile' | 'single' | 'double'
11 | export type DemoboardLayoutSide = 'left' | 'right'
12 |
13 | export interface DemoboardLayout<
14 | PanelType extends DemoboardPanelType = DemoboardPanelType
15 | > {
16 | actions: DemoboardLayoutActions
17 |
18 | // Initially based on the number of lines. Can also be passed in via props,
19 | // and resized by the user.
20 | height?: number
21 |
22 | // Initially set to 0.5. Can also be passed in vai props and resized by the
23 | // user.
24 | editorProportion: number
25 | leftPanelProportion: number
26 | rightPanelProportion: number
27 | sidebarWidth: number
28 |
29 | // Based on layout state and props
30 | fullScreen: boolean
31 |
32 | // In mobile and single modes, only one of the sides can be displayed at a
33 | // time. This will be `null` when the mode is double or triple.
34 | side: DemoboardLayoutSide | null
35 |
36 | // Based on the available width.
37 | mode: DemoboardLayoutMode
38 |
39 | // Based on panel the project's panelPriorityOrder, along with the width
40 | // and height of the screen.
41 | sidebarPanel: PanelType | null
42 | leftPanel: PanelType | null
43 | rightPanel: PanelType | null
44 |
45 | // Based on layout state, the height of the screen, and whether the panel
46 | // that would normally appear above the panels has any priority.
47 | leftPanelMaximized: boolean
48 | rightPanelMaximized: boolean
49 | }
50 |
51 | export type DemoboardLayoutActions = {
52 | changeDimensions: (options: { width: number; height: number }) => void
53 |
54 | setSidebarWidth: (proportion: number) => void
55 | setEditorProportion: (proportion: number) => void
56 | setLeftPanelProportion: (proportion: number) => void
57 | setRightPanelProportion: (proportion: number) => void
58 |
59 | toggleFullScreen: () => void
60 | toggleLeftPanelMaximized: () => void
61 | toggleRightPanelMaximized: () => void
62 |
63 | showLeftSide: () => void
64 | showRightSide: () => void
65 | }
66 |
--------------------------------------------------------------------------------
/packages/demoboard-core/src/build/DemoboardBuildErrors.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | /**
9 | * These errors don't extend `Error` as we don't need the stack trace, and
10 | * extending `Error` prevents them from being passed out of the worker anyway.
11 | */
12 | export class DemoboardBuildError {
13 | name: string
14 | message: string
15 | sourceFile: string | null
16 |
17 | isDemoboardBuildError = true
18 |
19 | constructor(name: string, sourceFile: string | null, message: string) {
20 | // Not using this as the minifier breaks it
21 | // this.name = new.target.name
22 | this.name = name
23 |
24 | this.message = message
25 | this.sourceFile = sourceFile
26 | }
27 |
28 | toString() {
29 | return `${this.name}: ${this.message}`
30 | }
31 | }
32 |
33 | export class DemoboardFileNotFoundError extends DemoboardBuildError {
34 | request: string
35 |
36 | constructor(details: { request: string; sourceFile: string }) {
37 | super(
38 | 'FileNotFoundError',
39 | details.sourceFile,
40 | `The import "${details.request}" could not be found" (in "${
41 | details.sourceFile
42 | }").`,
43 | )
44 | Object.assign(this, details)
45 | }
46 | }
47 |
48 | export class DemoboardFetchFailedError extends DemoboardBuildError {
49 | request?: string
50 | url: string
51 | status: number
52 |
53 | constructor(details: {
54 | request?: string
55 | url: string
56 | status: string
57 | sourceFile: string
58 | }) {
59 | super(
60 | 'FetchFailedError',
61 | details.sourceFile,
62 | `The import "${details.request}" could not be fetched from "${
63 | details.url
64 | }"; the server returned "${details.status}" (in "${
65 | details.sourceFile
66 | }").`,
67 | )
68 | Object.assign(this, details)
69 | }
70 | }
71 |
72 | export class DemoboardTransformError extends DemoboardBuildError {
73 | lineNumber?: number
74 | charNumber?: number
75 |
76 | constructor(details: {
77 | message: string
78 | lineNumber?: number
79 | charNumber?: number
80 | sourceFile: string
81 | }) {
82 | super(
83 | 'TransformError',
84 | details.sourceFile,
85 | `The file "${details.sourceFile}" could not be compiled. ` +
86 | details.message,
87 | )
88 | Object.assign(this, details)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/packages/demoboard-worker/src/DemoboardBuildErrors.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | /**
9 | * These errors don't extend `Error` as we don't need the stack trace, and
10 | * extending `Error` prevents them from being passed out of the worker anyway.
11 | */
12 | export class DemoboardBuildError {
13 | name: string
14 | message: string
15 | sourceFile: string | null
16 |
17 | isDemoboardBuildError = true
18 |
19 | constructor(name: string, sourceFile: string | null, message: string) {
20 | // Not using this as the minifier breaks it
21 | // this.name = new.target.name
22 | this.name = name
23 |
24 | this.message = message
25 | this.sourceFile = sourceFile
26 | }
27 |
28 | toString() {
29 | return `${this.name}: ${this.message}`
30 | }
31 | }
32 |
33 | export class DemoboardFileNotFoundError extends DemoboardBuildError {
34 | request: string
35 |
36 | constructor(details: { request: string; sourceFile: string }) {
37 | super(
38 | 'FileNotFoundError',
39 | details.sourceFile,
40 | `The import "${details.request}" could not be found" (in "${
41 | details.sourceFile
42 | }").`,
43 | )
44 | Object.assign(this, details)
45 | }
46 | }
47 |
48 | export class DemoboardFetchFailedError extends DemoboardBuildError {
49 | request?: string
50 | url: string
51 | status: number
52 |
53 | constructor(details: {
54 | request?: string
55 | url: string
56 | status: string
57 | sourceFile: string
58 | }) {
59 | super(
60 | 'FetchFailedError',
61 | details.sourceFile,
62 | `The import "${details.request}" could not be fetched from "${
63 | details.url
64 | }"; the server returned "${details.status}" (in "${
65 | details.sourceFile
66 | }").`,
67 | )
68 | Object.assign(this, details)
69 | }
70 | }
71 |
72 | export class DemoboardTransformError extends DemoboardBuildError {
73 | lineNumber?: number
74 | charNumber?: number
75 |
76 | constructor(details: {
77 | message: string
78 | lineNumber?: number
79 | charNumber?: number
80 | sourceFile: string
81 | }) {
82 | super(
83 | 'TransformError',
84 | details.sourceFile,
85 | `The file "${details.sourceFile}" could not be compiled. ` +
86 | details.message,
87 | )
88 | Object.assign(this, details)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "demoboard",
4 | "description": "Demoboard",
5 | "license": "Apache 2.0",
6 | "scripts": {
7 | "build": "lerna run --stream build",
8 | "build:watch": "lerna run --stream build:watch",
9 | "publish": "lerna publish",
10 | "test": "lerna run --stream test",
11 | "test:watch": "lerna run --stream test:watch"
12 | },
13 | "dependencies": {
14 | "@babel/core": "^7.6.4",
15 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
16 | "@babel/preset-react": "^7.6.3",
17 | "@mdx-js/mdx": "^1.4.5",
18 | "@svgr/rollup": "^4.3.3",
19 | "@types/codemirror": "^0.0.79",
20 | "@types/exenv": "^1.2.0",
21 | "@types/jest": "^24.0.18",
22 | "@types/jszip": "^3.1.6",
23 | "@types/lru-cache": "^5.1.0",
24 | "@types/node": "12.7.5",
25 | "@types/react": "^16.9.2",
26 | "@types/react-dom": "^16.9.0",
27 | "@types/react-test-renderer": "^16.9.0",
28 | "@types/semver": "^5.5.0",
29 | "@types/styled-components": "4.1.8",
30 | "@typescript-eslint/eslint-plugin": "2.3.0",
31 | "@typescript-eslint/parser": "2.3.0",
32 | "aws-sdk": "2.560.0",
33 | "babel-plugin-styled-components": "^1.10.6",
34 | "cross-env": "^6.0.0",
35 | "env-cmd": "^10.0.1",
36 | "eslint": "^6.4.0",
37 | "eslint-config-react-app": "^5.0.1",
38 | "eslint-import-resolver-typescript": "^1.1.1",
39 | "eslint-plugin-flowtype": "^2.50.3",
40 | "eslint-plugin-header": "^3.0.0",
41 | "eslint-plugin-import": "^2.18.0",
42 | "eslint-plugin-jsx-a11y": "^6.2.3",
43 | "eslint-plugin-react": "^7.14.3",
44 | "eslint-plugin-react-hooks": "^1.7.0",
45 | "jest": "^24.9.0",
46 | "lerna": "^3.15.0",
47 | "react": "^16.11.0",
48 | "react-dom": "^16.11.0",
49 | "react-test-renderer": "^16.11.0",
50 | "rimraf": "^3.0.0",
51 | "rollup": "^1.26.2",
52 | "rollup-plugin-babel": "^4.3.3",
53 | "rollup-plugin-commonjs": "^10.1.0",
54 | "rollup-plugin-json": "^4.0.0",
55 | "rollup-plugin-node-builtins": "^2.1.2",
56 | "rollup-plugin-node-globals": "^1.4.0",
57 | "rollup-plugin-node-resolve": "^5.2.0",
58 | "rollup-plugin-replace": "^2.2.0",
59 | "rollup-plugin-terser": "^5.1.2",
60 | "rollup-plugin-typescript2": "^0.24.3",
61 | "styled-components": "^4.4.1",
62 | "ts-jest": "^24.1.0",
63 | "typescript": "^3.7.2",
64 | "typescript-plugin-styled-components": "^1.4.3",
65 | "universal-react-scripts": "3.2.1"
66 | },
67 | "workspaces": [
68 | "examples",
69 | "packages/*"
70 | ]
71 | }
72 |
--------------------------------------------------------------------------------
/packages/demoboard-worker/src/transforms/mdx/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import register from '../register'
9 | import rehypePrism from './rehype-prism'
10 |
11 | import emoji from 'remark-emoji'
12 | import images from 'remark-images'
13 | import textr from 'remark-textr'
14 | import slug from 'remark-slug'
15 | import typographicBase from 'typographic-base'
16 | import mdx from '@mdx-js/mdx'
17 |
18 | register(
19 | 'mdx',
20 | ({ babelDetective, babelTransform, errors }) =>
21 | async function transpileMDX({ originalSource, pathname }) {
22 | try {
23 | const jsx = mdx
24 | .sync(originalSource, {
25 | transformSync: babelTransform,
26 | remarkPlugins: [
27 | slug,
28 | images,
29 | emoji,
30 | [textr, { plugins: [typographicBase] }],
31 | ],
32 |
33 | // FIXME: This currently seems to break the UMD build
34 | // rehypePlugins: [rehypePrism],
35 | })
36 | .trim()
37 |
38 | let originalReactImport = originalSource.match(
39 | /import\s+React\s+from (?:'|")react(@.*)?(?:'|")/,
40 | )
41 | let imports = `import { mdx } from '@mdx-js/react'\n`
42 | if (!originalReactImport) {
43 | imports += `import React from 'react'\n`
44 | }
45 |
46 | const babelOutput = babelTransform(imports + jsx, {
47 | filename: pathname,
48 |
49 | presets: ['es2015', 'react'],
50 | plugins: [
51 | 'syntax-object-rest-spread',
52 | 'proposal-object-rest-spread',
53 | babelDetective,
54 | ],
55 |
56 | compact: false,
57 | sourceMaps: true,
58 | sourceType: 'module',
59 | })
60 |
61 | return {
62 | css: null,
63 | transformedSource: babelOutput.code,
64 | originalSource,
65 | map: babelOutput.map,
66 | pathname,
67 | dependencies: babelOutput.metadata.requires || [],
68 | }
69 | } catch (e) {
70 | const positionMatch = e.message.match(/\((\d+):(\d+)\)/)
71 |
72 | throw new errors.DemoboardTransformError({
73 | sourceFile: pathname,
74 | message: e.message,
75 | lineNumber: positionMatch && positionMatch[1],
76 | charNumber: positionMatch && positionMatch[2],
77 | })
78 | }
79 | },
80 | )
81 |
--------------------------------------------------------------------------------
/packages/demoboard-ui/rollup.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present Dan Abramov
3 | * This is based on the rollup config from Redux
4 | */
5 |
6 | import svgr from '@svgr/rollup'
7 | import babel from 'rollup-plugin-babel'
8 | import commonjs from 'rollup-plugin-commonjs'
9 | import nodeBuiltins from 'rollup-plugin-node-builtins'
10 | import nodeResolve from 'rollup-plugin-node-resolve'
11 | import replace from 'rollup-plugin-replace'
12 | import { terser } from 'rollup-plugin-terser'
13 | import typescript from 'rollup-plugin-typescript2'
14 |
15 | const CSSPattern = /\.css$/
16 |
17 | function cssToString(opts = {}) {
18 | return {
19 | name: 'cssToString',
20 | transform(code, id) {
21 | if (CSSPattern.test(id)) {
22 | return {
23 | code: `export default ${JSON.stringify(code)};`,
24 | map: { mappings: '' },
25 | }
26 | }
27 | },
28 | }
29 | }
30 |
31 | const env = process.env.NODE_ENV
32 | const config = {
33 | input: 'src/index.ts',
34 |
35 | output: [
36 | {
37 | dir: 'dist/es',
38 | format: 'esm',
39 | sourcemap: true,
40 | },
41 | {
42 | dir: 'dist/commonjs',
43 | format: 'cjs',
44 | sourcemap: true,
45 | },
46 | ],
47 |
48 | external(id) {
49 | if ((/^\w/.test(id) || id[0] === '@') && !CSSPattern.test(id)) {
50 | return true
51 | }
52 | },
53 |
54 | plugins: [
55 | svgr(),
56 | nodeBuiltins(),
57 | nodeResolve({
58 | mainFields: ['module', 'main', 'jsnext:main'],
59 | }),
60 | cssToString(),
61 | typescript({
62 | abortOnError: false,
63 | clean: true,
64 | module: 'ESNext',
65 | typescript: require('typescript'),
66 | objectHashIgnoreUnknownHack: true,
67 | useTsconfigDeclarationDir: true,
68 | }),
69 | babel({
70 | extensions: ['.ts', '.tsx', '.js', '.jsx'],
71 | exclude: 'node_modules/**',
72 | presets: ['@babel/preset-react'],
73 | plugins: [
74 | '@babel/plugin-syntax-dynamic-import',
75 | 'babel-plugin-styled-components',
76 | ],
77 | }),
78 | commonjs({
79 | ignore: [
80 | // CodeMirror shouldn't be loaded on the server, so it needs to be
81 | // left as a require so it can be conditionally loaded.
82 | 'codemirror',
83 | ],
84 | }),
85 | replace({
86 | // Don't set the env unless building for production, as it will cause
87 | // rollup to shake out the minified runtime.
88 | 'process.env.NODE_ENV': JSON.stringify(env),
89 | }),
90 | ],
91 | }
92 |
93 | if (env === 'production') {
94 | config.plugins.push(terser())
95 | }
96 |
97 | export default config
98 |
--------------------------------------------------------------------------------
/packages/demoboard-ui/src/components/openTabList/openTabList.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import React from 'react'
9 | import { useRoverState } from 'reakit/Rover'
10 | import { useId } from 'reakit-utils'
11 | import styled, { css } from 'styled-components'
12 | import { Tab, TabList } from 'reakit/Tab'
13 | import { colors, fonts, shadows, easings } from '../../constants'
14 |
15 | const StyledTabList = styled(TabList)`
16 | align-items: center;
17 | display: flex;
18 | width: 100%;
19 | `
20 |
21 | const StyledTab = styled(Tab)`
22 | background-color: transparent;
23 | border: none;
24 | box-shadow: ${shadows.drop()};
25 | color: ${colors.black};
26 | cursor: pointer;
27 | cursor: pointer;
28 | display: block;
29 | flex: 1 0 0;
30 | font-family: ${fonts.sansSerif};
31 | font-size: 13px;
32 | line-height: 40px;
33 | margin-bottom: 0;
34 | opacity: 0.5;
35 | outline: none;
36 | overflow: hidden;
37 | padding: 0px 10px;
38 | position: relative;
39 | transition: background-color 200ms ${easings.easeOut},
40 | opacity 200ms ${easings.easeOut};
41 | user-select: none;
42 | white-space: nowrap;
43 | width: 0;
44 |
45 | &:first-child {
46 | padding-left: 20px;
47 | }
48 |
49 | &:hover {
50 | background-color: ${colors.lighterGrey};
51 | }
52 |
53 | ${props =>
54 | props.selectedId === props.stopId &&
55 | css`
56 | background-color: ${colors.lighterGrey};
57 | cursor: default;
58 | opacity: 1;
59 | overflow: visible;
60 | padding-right: 32px;
61 | width: 100%;
62 | `}
63 | `
64 |
65 | export interface OpenTabListProps {
66 | onClose: (pathname: string) => void
67 | onSelect: (pathname: string | null) => void
68 | selected: string | null
69 | tabs: string[]
70 | }
71 |
72 | export function OpenTabList({
73 | onSelect,
74 | onClose,
75 | selected,
76 | tabs,
77 | }: OpenTabListProps) {
78 | const baseId = useId('open-tab-')
79 | const rover = useRoverState({
80 | loop: true,
81 | currentId: selected,
82 | })
83 | const tab = {
84 | ...rover,
85 | unstable_baseId: baseId,
86 | selectedId: selected,
87 | select: onSelect,
88 | }
89 |
90 | return (
91 |
92 | {tabs.map(pathname => (
93 |
94 | {pathname.slice(1)}
95 |
96 | ))}
97 |
98 | )
99 | }
100 |
--------------------------------------------------------------------------------
/packages/demoboard-ui/src/components/panelTabList/panelTabList.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import React, { useCallback } from 'react'
9 | import styled, { css } from 'styled-components'
10 | import { useRoverState } from 'reakit/Rover'
11 | import { useId } from 'reakit-utils'
12 | import { Tab, TabList } from 'reakit/Tab'
13 | import { colors, dimensions, fonts, radii, shadows } from '../../constants'
14 | import { DemoboardProject } from '@frontarm/demoboard-core'
15 |
16 | const StyledTabList = styled(TabList)`
17 | align-items: center;
18 | justify-content: flex-start;
19 | display: flex;
20 | width: 100%;
21 | `
22 |
23 | const StyledTab = styled(Tab)`
24 | background-color: transparent;
25 | box-shadow: ${shadows.drop()};
26 | box-sizing: border-box;
27 | display: block;
28 | padding: 0px 20px;
29 | height: ${dimensions.footerHeight};
30 | line-height: calc(${dimensions.footerHeight} - 2px);
31 | outline: none;
32 | cursor: pointer;
33 | opacity: 0.5;
34 | font-size: 11px;
35 | white-space: nowrap;
36 | position: relative;
37 | user-select: none;
38 |
39 | margin-bottom: 0;
40 | cursor: pointer;
41 | user-select: none;
42 | font-family: ${fonts.sansSerif};
43 |
44 | border: none;
45 | color: ${colors.black};
46 |
47 | &:hover {
48 | background-color: hsla(0, 0%, 100%, 0.5);
49 | opacity: 0.75;
50 | }
51 |
52 | ${props =>
53 | props.selectedId === props.stopId &&
54 | css`
55 | opacity: 1 !important;
56 | background-color: hsla(0, 0%, 100%, 0.5);
57 | border-top: 2px solid ${colors.purple};
58 | cursor: default;
59 | `}
60 | `
61 |
62 | export interface PanelTabListProps {
63 | project: DemoboardProject
64 | }
65 |
66 | export function PanelTabList({ project }: PanelTabListProps) {
67 | const {
68 | dispatch,
69 | state: { view },
70 | } = project
71 | const selectedTab = 'Console'
72 | const baseId = useId('panel-tab-')
73 | const rover = useRoverState({
74 | loop: true,
75 | currentId: selectedTab,
76 | })
77 | const handleSelectTab = useCallback((pathname: string | null) => {
78 | // TODO
79 | }, [])
80 | const tab = {
81 | ...rover,
82 | unstable_baseId: baseId,
83 | selectedId: selectedTab,
84 | select: handleSelectTab,
85 | }
86 |
87 | return (
88 |
89 |
90 | Instructions
91 |
92 |
93 | Console
94 |
95 |
96 | )
97 | }
98 |
--------------------------------------------------------------------------------
/packages/demoboard-runtime/src/index.ts:
--------------------------------------------------------------------------------
1 | import 'regenerator-runtime/runtime.js'
2 | import { Polestar, FetchResult } from 'polestar'
3 | import { createHost } from '@frontarm/demoboard-messaging'
4 | import { captureAnchorClicks } from './captureAnchorClicks'
5 | import { captureConsole } from './captureConsole'
6 | import { captureErrors } from './captureErrors'
7 | import { createWindowWithStubbedNavigation } from './createWindowWithStubbedNavigation'
8 |
9 | export default function setupDemoboardRuntime(
10 | id: string,
11 | initialLocation: any,
12 | version: number,
13 | env: object = {},
14 | ) {
15 | let host = createHost(id, version)
16 |
17 | captureAnchorClicks(host)
18 | captureConsole(window.console, host)
19 | captureErrors(host)
20 |
21 | let process = { env }
22 | let demoboard = {
23 | id,
24 | runtime: this,
25 | worker: host.worker,
26 | }
27 | let windowWithStubbedNavigation = createWindowWithStubbedNavigation(
28 | host,
29 | window,
30 | initialLocation,
31 | )
32 | let globals = {
33 | demoboard,
34 | window: windowWithStubbedNavigation,
35 | history: windowWithStubbedNavigation.history,
36 | location: windowWithStubbedNavigation.location,
37 | global: windowWithStubbedNavigation,
38 | process,
39 | }
40 | ;(window as any).demoboard = demoboard
41 | ;(window as any).process = process
42 |
43 | let loadingModules = {} as {
44 | [url: string]: [(result: FetchResult) => void, (error) => void]
45 | }
46 | let loadError = null
47 |
48 | host.subscribeTo('module', payload => {
49 | let url = payload.url
50 | if (loadingModules[url]) {
51 | loadingModules[url][0](payload)
52 | delete loadingModules[url]
53 | }
54 | })
55 | host.subscribeTo('module-failure', payload => {
56 | let url = payload.url
57 | if (loadingModules[url]) {
58 | loadError = payload.error
59 | loadingModules[url][1](payload.error)
60 | delete loadingModules[url]
61 | }
62 | })
63 |
64 | let polestar = new Polestar({
65 | globals,
66 | moduleThis: windowWithStubbedNavigation,
67 | fetcher: (url: string, meta) =>
68 | new Promise((resolve, reject) => {
69 | host.dispatch('module-required', {
70 | url: url,
71 | requiredById: meta.requiredById,
72 | originalRequest: meta.originalRequest,
73 | })
74 | loadingModules[url] = [resolve, reject]
75 | }),
76 | onEntry: () => {
77 | host.dispatch('init', {})
78 | },
79 | onError: error => {
80 | if (error !== loadError) {
81 | window.console['native'].error(error)
82 | host.dispatch('error', error)
83 | }
84 | },
85 | })
86 |
87 | return {
88 | ...globals,
89 | evaluate: polestar.evaluate.bind(polestar),
90 | require: polestar.require.bind(polestar),
91 | host,
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/examples/src/index.node.tsx:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk'
2 | import finalhandler from 'finalhandler'
3 | import fs from 'fs'
4 | import http from 'http'
5 | import path from 'path'
6 | import React from 'react'
7 | import { renderToString } from 'react-dom/server'
8 | import serveStatic from 'serve-static'
9 | import { ServerStyleSheet } from 'styled-components/macro'
10 | import App from './App'
11 | import { DemoboardGlobalStyles } from '@frontarm/demoboard'
12 |
13 | const renderer = async (request: any, response: any) => {
14 | let sheet: ServerStyleSheet | undefined
15 |
16 | // Read in a HTML template, into which we'll substitute React's rendered
17 | // content, styles, and Navi's route state.
18 | let template = fs.readFileSync(process.env.HTML_TEMPLATE_PATH!, 'utf8')
19 | let [header, footer] = template.split('
%RENDERED_CONTENT%')
20 |
21 | try {
22 | sheet = new ServerStyleSheet()
23 |
24 | let body = renderToString(
25 | sheet!.collectStyles(
26 | <>
27 |
28 |
29 | >,
30 | ),
31 | )
32 |
33 | // Generate stylesheets containing the minimal CSS necessary to render the
34 | // page. The rest of the CSS will be loaded at runtime.
35 | let styleTags = sheet.getStyleTags()
36 |
37 | // Generate the complete HTML
38 | let html = header + styleTags + '
' + body + footer
39 |
40 | // The route status defaults to `200`, but can be set to other statuses by
41 | // passing a `status` option to `route()`
42 | response.status(200).send(html)
43 | } catch (error) {
44 | // Log an error, but only render it in development mode.
45 | let html
46 | console.error(error)
47 | if (process.env.NODE_ENV === 'production') {
48 | html = `
500 Error - Something went wrong.
`
49 | } else {
50 | html = `
500 Error
${String(error)}
` + header + footer
51 | }
52 | response.status(500).send(html)
53 | } finally {
54 | if (sheet) {
55 | sheet.seal()
56 | }
57 | }
58 | }
59 |
60 | export default renderer
61 |
62 | // ---
63 |
64 | function serveDependencies() {
65 | // Serve the demoboard runtime on a separate origin
66 | const demoboardContainerPort = 5000
67 | const demoboardRuntimeDistPath = path.dirname(
68 | require.resolve('@frontarm/demoboard-runtime'),
69 | )
70 | console.log(
71 | chalk.cyan(
72 | `Serving demoboard runtime on port ${demoboardContainerPort}...`,
73 | ),
74 | )
75 | var serve = serveStatic(demoboardRuntimeDistPath)
76 | var server = http.createServer(function onRequest(req: any, res: any) {
77 | serve(req, res, finalhandler(req, res))
78 | })
79 | server.listen(demoboardContainerPort)
80 | }
81 |
82 | if (!(global as any).hasStartedContainerServer) {
83 | ;(global as any).hasStartedContainerServer = true
84 | serveDependencies()
85 | }
86 |
--------------------------------------------------------------------------------
/packages/demoboard-ui/src/components/Spinner.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Seven Stripes Kabushiki Kaisha
3 | *
4 | * This source code is licensed under the Apache License, Version 2.0, found
5 | * in the LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import React from 'react'
9 | import { css, keyframes } from 'styled-components'
10 | import { colors } from '../constants'
11 |
12 | export interface SpinnerProps extends React.ComponentProps<'div'> {
13 | color?: string
14 | }
15 |
16 | export function Spinner({ color = colors.black, ...rest }: SpinnerProps) {
17 | return (
18 |