├── src ├── SCEvent.tsx ├── AppContext.tsx ├── index.ts ├── Popover.tsx ├── VizTabs.tsx ├── StateChartGuard.tsx ├── EventName.tsx ├── LayoutButton.tsx ├── Button.tsx ├── Loader.tsx ├── InitialEdge.tsx ├── logo.tsx ├── StatePanel.tsx ├── App.js ├── tracker.ts ├── EventPanel.tsx ├── utils.tsx ├── examples.ts ├── StateChartVisualization.tsx ├── StateChartAction.tsx ├── Notifications.tsx ├── StateChart.tsx ├── Edge.tsx └── StateChartNode.tsx ├── .gitignore ├── public ├── src │ ├── react-app-env.d.ts │ ├── App.css │ ├── App.test.tsx │ ├── index.css │ ├── Dropdown.js │ ├── logo.svg │ ├── Logo.js │ ├── App.js │ ├── index.js │ └── examples.ts ├── extension-consumer │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── index.html │ ├── src │ │ ├── App.test.js │ │ ├── App.css │ │ ├── index.css │ │ ├── index.js │ │ ├── App.js │ │ └── serviceWorker.js │ ├── .gitignore │ ├── package.json │ └── README.md ├── public │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-150x150.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-256x256.png │ ├── browserconfig.xml │ ├── site.webmanifest │ ├── safari-pinned-tab.svg │ └── index.html ├── extension │ ├── assets │ │ ├── 16x16.png │ │ ├── 48x48.png │ │ └── 128x128.png │ ├── devtools │ │ ├── devtools.js │ │ └── devtools.html │ ├── content │ │ ├── contentInject.js │ │ └── contentCommunicate.js │ ├── manifest.json │ ├── hot-reload.js │ ├── injected │ │ └── injected.js │ └── background.js ├── .gitignore ├── tsconfig.json ├── moveBuild.js ├── package.json └── README.md ├── .vscode └── settings.json ├── docs ├── badge-google-chrome-340x96.png └── devtools-panel-instruction.png ├── .prettierrc ├── tsconfig.json ├── rollup.config.js ├── LICENSE ├── package.json └── README.md /src/SCEvent.tsx: -------------------------------------------------------------------------------- 1 | // 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | gist 4 | -------------------------------------------------------------------------------- /public/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /public/extension-consumer/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /public/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/public/favicon.ico -------------------------------------------------------------------------------- /public/extension/assets/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/extension/assets/16x16.png -------------------------------------------------------------------------------- /public/extension/assets/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/extension/assets/48x48.png -------------------------------------------------------------------------------- /public/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/public/mstile-150x150.png -------------------------------------------------------------------------------- /docs/badge-google-chrome-340x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/docs/badge-google-chrome-340x96.png -------------------------------------------------------------------------------- /docs/devtools-panel-instruction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/docs/devtools-panel-instruction.png -------------------------------------------------------------------------------- /public/extension/assets/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/extension/assets/128x128.png -------------------------------------------------------------------------------- /public/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/public/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/public/android-chrome-256x256.png -------------------------------------------------------------------------------- /public/extension-consumer/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/extension-consumer/public/favicon.ico -------------------------------------------------------------------------------- /public/extension-consumer/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/extension-consumer/public/logo192.png -------------------------------------------------------------------------------- /public/extension-consumer/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amitnovick/xstate-devtools/HEAD/public/extension-consumer/public/logo512.png -------------------------------------------------------------------------------- /public/extension/devtools/devtools.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.panels.create( 2 | 'XState DevTools', 3 | null, 4 | 'devtools/build/index.html', 5 | () => {}, 6 | ); 7 | -------------------------------------------------------------------------------- /src/AppContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const AppContext = React.createContext({ machine: null, state: null }); 4 | 5 | export default AppContext; 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { StateChart } from './StateChart'; 2 | import { App } from './App'; 3 | import { Notifications, notificationsMachine } from './Notifications'; 4 | 5 | export { StateChart, Notifications, notificationsMachine, App }; 6 | -------------------------------------------------------------------------------- /public/src/App.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: border-box; 5 | position: relative; 6 | } 7 | 8 | html, 9 | body { 10 | height: 100%; 11 | width: 100%; 12 | margin: 0; 13 | padding: 0; 14 | } 15 | 16 | #root { 17 | height: 100%; 18 | } 19 | -------------------------------------------------------------------------------- /public/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /public/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/extension-consumer/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /public/extension/content/contentInject.js: -------------------------------------------------------------------------------- 1 | const script = chrome.extension.getURL('injected/injected.js'); 2 | 3 | const s = document.createElement('script'); 4 | s.type = 'text/javascript'; 5 | s.src = script; 6 | 7 | // eslint-disable-next-line func-names 8 | s.onload = function() { 9 | this.parentNode.removeChild(this); 10 | }; 11 | (document.head || document.documentElement).appendChild(s); 12 | -------------------------------------------------------------------------------- /public/extension/devtools/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | XState DevTools 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/extension-consumer/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | } 8 | 9 | .App-header { 10 | background-color: #282c34; 11 | min-height: 100vh; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | font-size: calc(10px + 2vmin); 17 | color: white; 18 | } 19 | 20 | .App-link { 21 | color: #09d3ac; 22 | } 23 | -------------------------------------------------------------------------------- /public/extension-consumer/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /public/extension-consumer/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /public/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /src/Popover.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import React from 'react'; 3 | 4 | export const StyledPopover = styled.aside` 5 | position: absolute; 6 | bottom: 100%; 7 | left: 0; 8 | background: black; 9 | color: white; 10 | border-radius: var(--radius); 11 | pointer-events: none; 12 | opacity: 0; 13 | `; 14 | 15 | export const Popover: React.SFC = ({ children }) => { 16 | return {children}; 17 | }; 18 | -------------------------------------------------------------------------------- /public/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | /extension/devtools/build 26 | 27 | extension.zip 28 | -------------------------------------------------------------------------------- /public/extension/content/contentCommunicate.js: -------------------------------------------------------------------------------- 1 | // listening to messages from `xstate` package 2 | window.addEventListener('message', msg => { 3 | const { type } = msg.data; 4 | switch (type) { 5 | case 'connect': { 6 | chrome.runtime.sendMessage(msg.data); 7 | return; 8 | } 9 | case 'update': { 10 | chrome.runtime.sendMessage(msg.data); 11 | return; 12 | } 13 | case 'disconnect': { 14 | chrome.runtime.sendMessage(msg.data); 15 | return; 16 | } 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /public/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-256x256.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "target": "es5", 5 | "skipLibCheck": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": false, 9 | "forceConsistentCasingInFileNames": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "allowJs": true, 14 | "lib": [ 15 | "dom", 16 | "es7" 17 | ], 18 | "outDir": "./lib", 19 | }, 20 | "include": [ 21 | "src" 22 | ] 23 | } -------------------------------------------------------------------------------- /public/extension-consumer/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | // setTimeout(() => { 8 | ReactDOM.render(, document.getElementById('root')); 9 | // }, 2000); 10 | 11 | // If you want your app to work offline and load faster, you can change 12 | // unregister() to register() below. Note this comes with some pitfalls. 13 | // Learn more about service workers: https://bit.ly/CRA-PWA 14 | serviceWorker.unregister(); 15 | -------------------------------------------------------------------------------- /public/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": false, 9 | "forceConsistentCasingInFileNames": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | "lib": [ 17 | "dom", 18 | "dom.iterable", 19 | "esnext" 20 | ] 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } -------------------------------------------------------------------------------- /public/extension-consumer/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/moveBuild.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const rimraf = require('rimraf'); 4 | 5 | const craBuildDir = path.join(__dirname, 'build'); 6 | const targetBuildDir = path.join(__dirname, 'extension', 'devtools', 'build'); 7 | 8 | rimraf(targetBuildDir, () => { 9 | console.log('Cleaned previous target build directory'); 10 | fs.rename(craBuildDir, targetBuildDir, err => { 11 | if (err) { 12 | console.log( 13 | 'Received error before managed to move build dir to target destination:', 14 | err 15 | ); 16 | } else { 17 | console.log(`Moved build to ${targetBuildDir}`); 18 | } 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/VizTabs.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useMemo } from 'react'; 2 | import { Interpreter, Machine } from 'xstate'; 3 | import { StateChartVisualization } from './StateChartVisualization'; 4 | import styled from 'styled-components'; 5 | 6 | interface StateChartContainerProps {} 7 | 8 | export const StyledStateChartContainer = styled.section` 9 | display: grid; 10 | grid-column-gap: 1rem; 11 | grid-row-gap: 1rem; 12 | padding: 0 1rem; 13 | 14 | &[data-child] { 15 | grid-template-columns: 1fr 1fr; 16 | } 17 | `; 18 | 19 | export const StateChartContainer: React.SFC< 20 | StateChartContainerProps 21 | > = ({}) => { 22 | return ( 23 | 24 | 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /public/extension-consumer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extension-consumer", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@xstate/react": "^0.7.1", 7 | "react": "^16.9.0", 8 | "react-dom": "^16.9.0", 9 | "react-scripts": "3.1.2", 10 | "xstate": "^4.6.7" 11 | }, 12 | "scripts": { 13 | "start": "BROWSER=none react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test", 16 | "eject": "react-scripts eject" 17 | }, 18 | "eslintConfig": { 19 | "extends": "react-app" 20 | }, 21 | "browserslist": { 22 | "production": [ 23 | ">0.2%", 24 | "not dead", 25 | "not op_mini all" 26 | ], 27 | "development": [ 28 | "last 1 chrome version", 29 | "last 1 firefox version", 30 | "last 1 safari version" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript2'; 2 | import { terser } from 'rollup-plugin-terser'; 3 | import rollupReplace from 'rollup-plugin-replace'; 4 | import fileSize from 'rollup-plugin-filesize'; 5 | 6 | const createConfig = ({ input, output }) => ({ 7 | input, 8 | output, 9 | plugins: [ 10 | rollupReplace({ 11 | 'process.env.NODE_ENV': JSON.stringify('production') 12 | }), 13 | typescript({ 14 | clean: true, 15 | tsconfigOverride: { 16 | compilerOptions: { 17 | declaration: false 18 | } 19 | } 20 | }), 21 | terser(), 22 | fileSize() 23 | ] 24 | }); 25 | 26 | export default [ 27 | createConfig({ 28 | input: 'src/index.ts', 29 | output: { 30 | file: 'dist/xstate-viz.js', 31 | format: 'umd', 32 | name: 'XStateViz' 33 | } 34 | }) 35 | ]; 36 | -------------------------------------------------------------------------------- /src/StateChartGuard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Guard, State } from 'xstate'; 3 | import styled from 'styled-components'; 4 | 5 | const StyledSCGuard = styled.div` 6 | padding: 0 0.5rem; 7 | `; 8 | 9 | export const StateChartGuard: React.SFC<{ 10 | guard: Guard; 11 | state: State; 12 | }> = ({ guard, state }) => { 13 | const valid = 14 | guard.predicate && typeof guard.predicate === 'function' 15 | ? guard.predicate(state.context, state.event, { cond: guard }) 16 | : undefined; 17 | 18 | return ( 19 | 29 | [{guard.type === 'xstate.guard' ? (guard as any).name : guard.type}] 30 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /public/extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "XState DevTools", 3 | "author": "Amit Novick", 4 | "version": "0.5.3", 5 | "devtools_page": "devtools/devtools.html", 6 | "description": "A Chrome extension for inspecting XState machines running in your app.", 7 | "manifest_version": 2, 8 | "content_security_policy": "script-src 'self' 'unsafe-eval' ; object-src 'self'", 9 | "background": { 10 | "scripts": ["hot-reload.js", "background.js"], 11 | "persistent": false 12 | }, 13 | "icons": { 14 | "16": "assets/16x16.png", 15 | "48": "assets/48x48.png", 16 | "128": "assets/128x128.png" 17 | }, 18 | "content_scripts": [ 19 | { 20 | "matches": [""], 21 | "js": ["content/contentCommunicate.js", "content/contentInject.js"], 22 | "run_at": "document_start" 23 | } 24 | ], 25 | "web_accessible_resources": ["injected/injected.js"], 26 | "permissions": ["tabs", ""] 27 | } 28 | -------------------------------------------------------------------------------- /public/src/Dropdown.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Select from 'react-select'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const Dropdown = ({ items, selectedItem, setSelectedItem }) => { 6 | return ( 7 |