├── .editorconfig ├── .gitignore ├── .netlify ├── .vscode └── settings.json ├── .watchmanconfig ├── README.md ├── config ├── environment.js ├── module-map.d.ts ├── resolver-configuration.d.ts └── targets.js ├── ember-cli-build.js ├── package.json ├── public ├── images │ ├── favicon.png │ └── logo.svg └── robots.txt ├── src ├── index.ts ├── main.ts ├── ui │ ├── components │ │ ├── EditableTitle │ │ │ ├── component.ts │ │ │ └── template.hbs │ │ ├── Main │ │ │ ├── CodeEditor │ │ │ │ ├── component.ts │ │ │ │ └── template.hbs │ │ │ ├── Sandbox │ │ │ │ ├── component.ts │ │ │ │ └── template.hbs │ │ │ ├── component.ts │ │ │ └── template.hbs │ │ ├── OverlayKeyBindings │ │ │ ├── component.ts │ │ │ └── template.hbs │ │ ├── OverlayUI │ │ │ └── template.hbs │ │ ├── RemoveButton │ │ │ ├── component.ts │ │ │ └── template.hbs │ │ ├── Share │ │ │ ├── component.ts │ │ │ └── template.hbs │ │ └── Visualizer │ │ │ ├── -utils │ │ │ ├── compiler.ts │ │ │ ├── hexdump.ts │ │ │ ├── inspect-program.ts │ │ │ └── opcode-metadata.ts │ │ │ ├── component.ts │ │ │ ├── hex │ │ │ └── helper.ts │ │ │ └── template.hbs │ ├── index.html │ └── styles │ │ ├── animations.scss │ │ ├── app.scss │ │ ├── components │ │ ├── editable-title.scss │ │ ├── glimmer-repl.scss │ │ ├── glimmer-sandbox.scss │ │ ├── glimmer-visualizer.scss │ │ ├── overlay-ui.scss │ │ └── share.scss │ │ ├── responsive.scss │ │ ├── solarized.scss │ │ ├── typography.scss │ │ ├── ui │ │ ├── badge-success.scss │ │ ├── button-confirm.scss │ │ ├── button-remove.scss │ │ ├── button.scss │ │ └── header.scss │ │ ├── variables.scss │ │ └── z-index.scss └── utils │ ├── compilers │ ├── compile-template.ts │ └── compile-typescript.ts │ ├── file-system.ts │ ├── keys.ts │ ├── monaco │ └── themes │ │ └── solarized-dark.ts │ ├── resolution-map.ts │ └── specifiers.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp 3 | dist -------------------------------------------------------------------------------- /.netlify: -------------------------------------------------------------------------------- 1 | {"site_id":"6d8dc2e9-9520-4f27-b75a-a9ce084480bf","path":"dist"} -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Glimmer Playground 2 | 3 | This project provides a an interactive development environment for people looking to try Glimmerjs. 4 | See it live at [https://try.glimmerjs.com](https://try.glimmerjs.com). 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | * [Git](https://git-scm.com/) 11 | * [Node.js](https://nodejs.org/) (with NPM) 12 | * [Yarn](https://yarnpkg.com/en/) 13 | * [Ember CLI](https://ember-cli.com/) 14 | 15 | ## Installation 16 | 17 | * `git clone ` this repository 18 | * `cd glimmer-repl` 19 | * `yarn` 20 | 21 | ## Running / Development 22 | 23 | * `ember serve` 24 | * Visit your app at [http://localhost:4200](http://localhost:4200). 25 | 26 | ### Building 27 | 28 | * `ember build` (development) 29 | * `ember build --environment production` (production) 30 | 31 | ## Further Reading / Useful Links 32 | 33 | * [glimmer.js](http://github.com/glimmerjs/glimmer.js/) 34 | * [ember-cli](https://ember-cli.com/) 35 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'glimmer-repl', 6 | environment: environment 7 | }; 8 | 9 | return ENV; 10 | }; 11 | -------------------------------------------------------------------------------- /config/module-map.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is just a placeholder file to keep TypeScript aware editors happy. At build time, 3 | * it will be replaced with a complete map of resolvable module paths => rolled up contents. 4 | */ 5 | 6 | export interface Dict { 7 | [index: string]: T; 8 | } 9 | 10 | declare let map: Dict; 11 | export default map; 12 | -------------------------------------------------------------------------------- /config/resolver-configuration.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is just a placeholder file to keep TypeScript aware editors happy. At build time, 3 | * it will be replaced with a resolver configuration composed from your application's 4 | * `config/environment.js` (and supplemented with default settings as possible). 5 | */ 6 | 7 | import { ResolverConfiguration } from '@glimmer/resolver'; 8 | declare var _default: ResolverConfiguration; 9 | export default _default; 10 | -------------------------------------------------------------------------------- /config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let browsers = [ 4 | '> 5%', 5 | 'last 2 Edge versions', 6 | 'last 2 Chrome versions', 7 | 'last 2 Firefox versions', 8 | 'last 2 Safari versions', 9 | ]; 10 | 11 | if (process.env.EMBER_ENV === 'test') { 12 | browsers = [ 13 | 'last 1 Chrome versions', 14 | 'last 1 Firefox versions' 15 | ]; 16 | } 17 | 18 | module.exports = { browsers }; 19 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const GlimmerApp = require('@glimmer/application-pipeline').GlimmerApp; 4 | const resolve = require('rollup-plugin-node-resolve'); 5 | const commonjs = require('rollup-plugin-commonjs'); 6 | const Funnel = require('broccoli-funnel'); 7 | const mergeTrees = require('broccoli-merge-trees'); 8 | const concat = require('broccoli-concat'); 9 | const { map } = require('broccoli-stew'); 10 | 11 | module.exports = function(defaults) { 12 | const isProduction = process.env.EMBER_ENV === 'production'; 13 | 14 | function handlebars() { 15 | return { 16 | resolveId(id) { 17 | if (id === 'handlebars') { 18 | return 'node_modules/handlebars/lib/handlebars/compiler/base.js'; 19 | } 20 | } 21 | }; 22 | } 23 | 24 | let app = new GlimmerApp(defaults, { 25 | rollup: { 26 | plugins: [ 27 | handlebars(), 28 | resolve({ jsnext: true, module: true, main: true }), 29 | commonjs({ 30 | namedExports: { 31 | 'node_modules/handlebars/lib/index.js': ['parse'], 32 | 'node_modules/decko/dist/decko.js': ['debounce', 'bind'] 33 | } 34 | }) 35 | ] 36 | } 37 | }); 38 | 39 | let monacoMode = isProduction ? 'min' : 'dev'; 40 | let monaco = new Funnel(`node_modules/monaco-editor/${monacoMode}/vs`, { 41 | destDir: 'vs' 42 | }); 43 | 44 | let libs = new Funnel('node_modules/@glimmer', { 45 | include: ['*/dist/**/*.d.ts', '*/package.json'] 46 | }); 47 | 48 | libs = map(libs, (content, relativePath) => { 49 | relativePath = `node_modules/@glimmer/${relativePath}`; 50 | return `typings['${relativePath}'] = ${JSON.stringify(content)};\n`; 51 | }); 52 | 53 | libs = concat(libs, { 54 | header: `window.typings = {};\n`, 55 | outputFile: 'types.js' 56 | }); 57 | 58 | return mergeTrees([app.toTree(), monaco, libs]); 59 | }; 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glimmer-repl", 3 | "version": "0.0.0", 4 | "description": "A brand new Glimmer app.", 5 | "directories": { 6 | "doc": "doc", 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "build": "ember build", 11 | "start": "ember server", 12 | "test": "ember test" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.0.0", 16 | "@glimmer/application-pipeline": "^0.14.0", 17 | "@glimmer/blueprint": "^0.14.0-alpha.3", 18 | "@types/file-saver": "^1.3.0", 19 | "@types/jszip": "^3.1.2", 20 | "broccoli-asset-rev": "^2.5.0", 21 | "ember-cli": "^3.7.1", 22 | "ember-cli-inject-live-reload": "^2.0.1", 23 | "ember-cli-sass": "^10.0.0", 24 | "ember-cli-uglify": "^2.1.0", 25 | "monaco-editor": "^0.9.0", 26 | "rollup-plugin-commonjs": "^8.0.2", 27 | "rollup-plugin-node-resolve": "^3.0.0", 28 | "sass": "^1.17.0", 29 | "typescript": "~3.2.2" 30 | }, 31 | "engines": { 32 | "node": ">= 4.0" 33 | }, 34 | "private": true, 35 | "dependencies": { 36 | "@glimmer/application": "^0.14.0-alpha.3", 37 | "@glimmer/bundle-compiler": "^0.39.2", 38 | "@glimmer/compiler": "0.39.2", 39 | "@glimmer/component": "^0.14.0-alpha.3", 40 | "@glimmer/interfaces": "^0.39.2", 41 | "@glimmer/program": "^0.39.2", 42 | "@glimmer/resolver": "^0.4.1", 43 | "@types/codemirror": "^0.0.41", 44 | "babel-plugin-glimmer-inline-precompile": "^1.2.0", 45 | "decko": "^1.2.0", 46 | "file-saver": "^1.3.3", 47 | "handlebars": "4", 48 | "lodash.debounce": "^4.0.8" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glimmerjs/glimmer-playground/ec785f58046f8c1c3e384c12c76601acbde2f1ea/public/images/favicon.png -------------------------------------------------------------------------------- /public/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import App from './main'; 2 | import { ComponentManager } from '@glimmer/component'; 3 | import { setPropertyDidChange } from '@glimmer/tracking'; 4 | import { apps } from './ui/components/Main/Sandbox/component'; 5 | 6 | const app = new App(); 7 | const containerElement = document.getElementById('app'); 8 | 9 | setPropertyDidChange(() => { 10 | app.scheduleRerender(); 11 | for (let otherApp of apps) { 12 | otherApp.scheduleRerender(); 13 | } 14 | }); 15 | 16 | app.registerInitializer({ 17 | initialize(registry) { 18 | registry.register(`component-manager:/${app.rootName}/component-managers/main`, ComponentManager); 19 | } 20 | }); 21 | 22 | app.renderComponent('Main', containerElement, null); 23 | 24 | app.boot(); 25 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Application, { DOMBuilder, RuntimeCompilerLoader, SyncRenderer } from '@glimmer/application'; 2 | import Resolver, { BasicModuleRegistry } from '@glimmer/resolver'; 3 | import moduleMap from '../config/module-map'; 4 | import resolverConfiguration from '../config/resolver-configuration'; 5 | 6 | export default class App extends Application { 7 | constructor() { 8 | let moduleRegistry = new BasicModuleRegistry(moduleMap); 9 | let resolver = new Resolver(resolverConfiguration, moduleRegistry); 10 | const element = document.body; 11 | 12 | super({ 13 | builder: new DOMBuilder({ element, nextSibling: null }), 14 | loader: new RuntimeCompilerLoader(resolver), 15 | renderer: new SyncRenderer(), 16 | resolver, 17 | rootName: resolverConfiguration.app.rootName 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ui/components/EditableTitle/component.ts: -------------------------------------------------------------------------------- 1 | import Component, { tracked } from '@glimmer/component'; 2 | 3 | const RETURN_KEY = 13; 4 | const ESCAPE_KEY = 27; 5 | 6 | export default class extends Component { 7 | _value: string; 8 | inputStyle: string; 9 | 10 | get element(): HTMLElement { 11 | return this.bounds.firstNode as HTMLElement; 12 | } 13 | 14 | didUpdate() { 15 | setTimeout(() => { 16 | let input: HTMLElement = this.element.querySelector('input'); 17 | if (input) { 18 | input.addEventListener('keyup', event => this.handleKeyUp(event)); 19 | input.focus(); 20 | } 21 | }, 0); 22 | } 23 | 24 | handleKeyUp(event: KeyboardEvent) { 25 | switch (event.keyCode) { 26 | case ESCAPE_KEY: 27 | this.value = this.args.value; 28 | this.isEditing = false; 29 | case RETURN_KEY: 30 | this.isEditing = false; 31 | break; 32 | default: 33 | let value = this.value = (event.target as HTMLInputElement).value; 34 | this.onChange(value); 35 | } 36 | } 37 | 38 | onChange(value: string) { 39 | let { onChange } = this.args; 40 | 41 | if (onChange) { 42 | onChange(value); 43 | } 44 | } 45 | 46 | @tracked isEditing = false; 47 | 48 | @tracked 49 | get value() { 50 | let val = this._value === undefined ? this.args.value : this._value; 51 | return val; 52 | } 53 | 54 | set value(value) { 55 | this._value = value; 56 | } 57 | 58 | startEditing(event: MouseEvent) { 59 | // Switch into editing mode 60 | this.isEditing = true; 61 | 62 | // Listen for clicks on the body to exit editing mode 63 | let { body } = document; 64 | let onBodyClick = () => { 65 | this.isEditing = false; 66 | body.removeEventListener('click', onBodyClick); 67 | }; 68 | 69 | body.addEventListener('click', onBodyClick); 70 | 71 | // Make sure we stop the current click event or else we'll trigger the click 72 | // event handler on the body we just added. 73 | event.stopPropagation(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/ui/components/EditableTitle/template.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{~#if isEditing ~}} 3 | 4 | {{~else~}} 5 | {{~value~}} 6 | {{~/if~}} 7 | 8 | {{~! Consume any extra whitespace ~}} 9 | -------------------------------------------------------------------------------- /src/ui/components/Main/CodeEditor/component.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Component, { tracked } from '@glimmer/component'; 4 | import { File } from '../../../../utils/file-system'; 5 | import debounce from 'lodash.debounce'; 6 | 7 | export default class CodeEditor extends Component { 8 | editor: monaco.editor.IStandaloneCodeEditor; 9 | resize: (event: Event) => void; 10 | 11 | get element(): HTMLElement { 12 | return this.bounds.firstNode as HTMLElement; 13 | } 14 | 15 | didInsertElement() { 16 | let { file } = this.args; 17 | 18 | let model = modelFor(file); 19 | 20 | let editor = this.editor = monaco.editor.create(this.element as HTMLElement, { 21 | scrollBeyondLastLine: false, 22 | theme: 'solarized-dark', 23 | minimap: { enabled: false }, 24 | model 25 | }); 26 | 27 | this.autoResize(); 28 | 29 | this.resize = debounce(() => { 30 | editor.layout(); 31 | }, 50); 32 | 33 | (window.addEventListener as any)('resize', this.resize, { passive: true }); 34 | 35 | editor.onDidChangeModelContent(event => { 36 | file.sourceText = editor.getValue(); 37 | file.didChange(); 38 | this.autoResize(); 39 | }); 40 | } 41 | 42 | willDestroyElement() { 43 | window.removeEventListener('resize', this.resize); 44 | } 45 | 46 | autoResize() { 47 | let { editor, element } = this; 48 | let lineHeight = editor.getConfiguration().fontInfo.lineHeight; 49 | let lineCount = editor.getModel().getLineCount(); 50 | let contentHeight = (lineHeight * lineCount) + 12; 51 | 52 | element.style.height = `${contentHeight}px`; 53 | editor.layout(); 54 | } 55 | } 56 | 57 | function modelFor(file: File) { 58 | let { sourceText, language, fileName } = file; 59 | let uri = monaco.Uri.parse(`file:///${fileName}`); 60 | 61 | let model = monaco.editor.getModel(uri); 62 | 63 | if (!model) { 64 | model = monaco.editor.createModel(sourceText, language, uri); 65 | 66 | model.updateOptions({ 67 | tabSize: 2, 68 | insertSpaces: true 69 | }); 70 | } 71 | 72 | return model; 73 | } 74 | -------------------------------------------------------------------------------- /src/ui/components/Main/CodeEditor/template.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /src/ui/components/Main/Sandbox/component.ts: -------------------------------------------------------------------------------- 1 | import Component, { tracked } from '@glimmer/component'; 2 | import Application, { DOMBuilder, SyncRenderer, RuntimeCompilerLoader } from '@glimmer/application'; 3 | import { precompile } from '@glimmer/compiler'; 4 | import { ComponentManager } from '@glimmer/component'; 5 | import { debounce } from 'decko'; 6 | import Resolver, { BasicModuleRegistry } from '@glimmer/resolver'; 7 | import resolverConfiguration from '../../../../../config/resolver-configuration'; 8 | 9 | interface ElementApp extends Application { 10 | vmElement: HTMLElement; 11 | } 12 | 13 | export const apps: ElementApp[] = []; 14 | 15 | export default class GlimmerSandbox extends Component { 16 | @tracked lastError: string = null; 17 | 18 | get element(): HTMLElement { 19 | return this.bounds.firstNode as HTMLElement; 20 | } 21 | 22 | didInsertElement() { 23 | let fs = this.args.fs; 24 | 25 | fs.onChange(() => { 26 | this.execute(); 27 | }); 28 | 29 | this.execute(); 30 | } 31 | 32 | @debounce(600) 33 | execute() { 34 | let fs = this.args.fs; 35 | let resolutionMap; 36 | 37 | this.lastError = null; 38 | 39 | try { 40 | resolutionMap = fs.toResolutionMap(); 41 | } catch (e) { 42 | this.lastError = e.toString(); 43 | return; 44 | } 45 | 46 | let component = this; 47 | 48 | class App extends Application { 49 | vmElement: HTMLElement; 50 | 51 | constructor() { 52 | let moduleRegistry = new BasicModuleRegistry(resolutionMap); 53 | let resolver = new Resolver(resolverConfiguration, moduleRegistry); 54 | 55 | super({ 56 | rootName: 'app', 57 | builder: new DOMBuilder({ element: document.body, nextSibling: null }), 58 | renderer: new SyncRenderer(), 59 | loader: new RuntimeCompilerLoader(resolver), 60 | resolver 61 | }); 62 | 63 | this.vmElement = document.createElement('div'); 64 | } 65 | 66 | async _render(): Promise { 67 | try { 68 | super._render(); 69 | let _rerender = this['_rerender']; 70 | 71 | this['_rerender'] = () => { 72 | try { 73 | _rerender.apply(this); 74 | } catch (e) { 75 | this.reportError(e); 76 | } 77 | return Promise.resolve(); 78 | }; 79 | } catch (e) { 80 | this.reportError(e); 81 | } 82 | } 83 | 84 | reportError(e: Error) { 85 | console.error(e); 86 | 87 | if (this.env['_transaction']) { this.env['_transaction'] = null; } 88 | this['_rerender'] = () => { return Promise.resolve(); }; 89 | 90 | component.lastError = e.toString(); 91 | 92 | let matches = e.message && e.message.match(/Could not find template for (\S+)/); 93 | if (matches) { 94 | component.args.onUnknownComponent(matches[1]); 95 | } 96 | } 97 | } 98 | 99 | const app = new App(); 100 | app.registerInitializer({ 101 | initialize(registry) { 102 | registry.register(`component-manager:/${app.rootName}/component-managers/main`, ComponentManager); 103 | } 104 | }); 105 | 106 | if (apps.length > 0) { 107 | let oldApp = apps.pop(); 108 | oldApp.vmElement.remove(); 109 | } 110 | 111 | apps.push(app); 112 | this.element.insertBefore(app.vmElement, null); 113 | app.renderComponent('GlimmerApp', app.vmElement, null); 114 | app.boot(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/ui/components/Main/Sandbox/template.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#if lastError}} 3 |
{{lastError}}
4 | {{/if}} 5 |
6 | -------------------------------------------------------------------------------- /src/ui/components/Main/component.ts: -------------------------------------------------------------------------------- 1 | import Component, { tracked } from "@glimmer/component"; 2 | import FileSystem, { File } from "../../../utils/file-system"; 3 | import JSZip from "jszip"; 4 | import SolarizedTheme from "../../../utils/monaco/themes/solarized-dark"; 5 | import { debounce } from "decko"; 6 | import { saveAs } from "file-saver"; 7 | 8 | declare function vsRequire(deps: string[], cb: any); 9 | declare var typings; 10 | 11 | let _id = 1; 12 | class ComponentFiles { 13 | id: number; 14 | isEditable = true; 15 | 16 | @tracked name: string; 17 | @tracked template: File; 18 | @tracked component: File; 19 | @tracked isVisible: boolean = true; 20 | 21 | constructor(options: Partial) { 22 | this.id = _id++; 23 | Object.assign(this, options); 24 | } 25 | 26 | toJSON() { 27 | let { name, template, component } = this; 28 | return { name, template, component }; 29 | } 30 | 31 | static fromJSON(fs: FileSystem, files) { 32 | let { template, component, helper } = files; 33 | files.template = fs.createFileFromJSON(template); 34 | files.component = component ? fs.createFileFromJSON(component) : null; 35 | return new ComponentFiles(files) 36 | } 37 | } 38 | 39 | class HelperFiles { 40 | id: number; 41 | isEditable = true; 42 | @tracked name: string; 43 | @tracked helper: File; 44 | @tracked isVisible: boolean = true; 45 | 46 | constructor(options: Partial) { 47 | this.id = _id++; 48 | Object.assign(this, options); 49 | } 50 | 51 | toJSON() { 52 | let { name, helper } = this; 53 | return { name, helper }; 54 | } 55 | 56 | static fromJSON(fs: FileSystem, files) { 57 | let { helper } = files; 58 | files.helper = helper ? fs.createFileFromJSON(helper) : null; 59 | return new this(files) 60 | } 61 | } 62 | 63 | const NEW_COMPONENT_NAMES = [ 64 | 'GlimmerApp', 65 | 'SecondComponent', 66 | 'ThirdComponent', 67 | 'FourthComponent', 68 | 'FifthComponent', 69 | 'CalmDownWithTheComponents' 70 | ]; 71 | 72 | const DEFAULT_APP = { 73 | helpers: [], 74 | components: [{ 75 | name: 'GlimmerApp', 76 | template: { 77 | fileName: pathForTemplate('GlimmerApp'), 78 | sourceText: `

Welcome to the Glimmer Playground!

79 |

You have clicked the button {{count}} times.

80 | ` 81 | }, 82 | component: { 83 | fileName: pathForComponent('GlimmerApp'), 84 | sourceText: `import Component, { tracked } from '@glimmer/component'; 85 | export default class extends Component { 86 | @tracked count = 1; 87 | increment() { 88 | this.count++; 89 | } 90 | }` 91 | } 92 | }] 93 | }; 94 | 95 | export default class GlimmerRepl extends Component { 96 | @tracked components: ComponentFiles[] = []; 97 | @tracked helpers: HelperFiles[] = []; 98 | @tracked isLoaded = false; 99 | @tracked shareIsVisible = false; 100 | @tracked shareTrigger = null; 101 | @tracked jsZipIsLoading = false; 102 | 103 | fs = new FileSystem(); 104 | lastUnknownComponent = null; 105 | 106 | didInsertElement() { 107 | waitForDependencies() 108 | .then(() => { 109 | this.isLoaded = true; 110 | this.init(); 111 | }); 112 | } 113 | 114 | init() { 115 | let { fs } = this; 116 | 117 | let { components, helpers } = this.initComponents(); 118 | this.components = components 119 | .map(json => ComponentFiles.fromJSON(fs, json)); 120 | this.components[0].isEditable = false; 121 | 122 | this.helpers = helpers.map(json => HelperFiles.fromJSON(fs, json)); 123 | 124 | fs.onChange(() => { 125 | this.serialize(); 126 | }); 127 | } 128 | 129 | initComponents() { 130 | let { searchParams } = new URL(document.location.toString()); 131 | let app = searchParams.get('app'); 132 | 133 | return app ? JSON.parse(decodeURIComponent(atob(app))) : DEFAULT_APP; 134 | } 135 | 136 | @debounce(200) 137 | serialize() { 138 | let { components, helpers } = this; 139 | let serializable = { components, helpers } 140 | let serialized = JSON.stringify(serializable); 141 | let encoded = btoa(encodeURIComponent(serialized)); 142 | history.replaceState(null, null, `?app=${encoded}`); 143 | } 144 | 145 | nameForNewComponent() { 146 | let name = this.lastUnknownComponent; 147 | if (name) { 148 | this.lastUnknownComponent = null; 149 | return name; 150 | } 151 | 152 | let count = this.components.length; 153 | name = NEW_COMPONENT_NAMES[count]; 154 | 155 | if (name) { return name; } 156 | 157 | return `Component${count+1}`; 158 | } 159 | 160 | saveUnknownComponent(componentName: string) { 161 | this.lastUnknownComponent = componentName; 162 | } 163 | 164 | createNewHelper() { 165 | let name = `helper${this.helpers.length + 1}` 166 | let helperPath = pathForHelper(name); 167 | let helper = this.fs.createFile(helperPath, `export default function helper() {}`); 168 | 169 | let files = new HelperFiles({ 170 | name, 171 | helper, 172 | }); 173 | 174 | this.helpers = [...this.helpers, files]; 175 | this.serialize(); 176 | } 177 | 178 | createNewComponent() { 179 | let name = this.nameForNewComponent(); 180 | let templatePath = pathForTemplate(name); 181 | let template = this.fs.createFile(templatePath, `
\n
`); 182 | 183 | let files = new ComponentFiles({ 184 | name, 185 | template, 186 | component: null 187 | }); 188 | 189 | this.components = [...this.components, files]; 190 | this.serialize(); 191 | } 192 | 193 | addComponentImplementation(files: ComponentFiles) { 194 | let { name } = files; 195 | let componentPath = pathForComponent(name); 196 | let component = this.fs.createFile(componentPath, `import Component, { tracked } from "@glimmer/component"; 197 | 198 | export default class extends Component { 199 | @tracked magicNumber = 42; 200 | };`); 201 | files.component = component; 202 | this.serialize(); 203 | } 204 | 205 | removeComponentImplementation(files: ComponentFiles) { 206 | files.component.remove(); 207 | files.component = null; 208 | this.components = this.components; 209 | } 210 | 211 | private nameChanged(files: ComponentFiles | HelperFiles, name) { 212 | files.name = name; 213 | this.fs.didChange(); 214 | } 215 | 216 | helperNameDidChange(files: HelperFiles, name: string) { 217 | let { helper } = files; 218 | helper.fileName = pathForHelper(name); 219 | this.nameChanged(files, name); 220 | } 221 | 222 | componentNameDidChange(files: ComponentFiles, name: string) { 223 | let { component, template } = files; 224 | 225 | template.fileName = pathForTemplate(name); 226 | if (component) { 227 | component.fileName = pathForComponent(name); 228 | } 229 | 230 | this.nameChanged(files, name); 231 | } 232 | 233 | removeHelper(files: HelperFiles) { 234 | files.helper.remove(); 235 | this.helpers = this.helpers.filter(h => h !== files); 236 | } 237 | 238 | removeComponent(files: ComponentFiles) { 239 | files.template.remove(); 240 | if (files.component) { files.component.remove(); } 241 | 242 | this.components = this.components.filter(c => c !== files); 243 | } 244 | 245 | @tracked isVisualizerShowing = false; 246 | 247 | toggleVisualizer() { 248 | this.isVisualizerShowing = !this.isVisualizerShowing; 249 | } 250 | 251 | hideShare() { 252 | this.shareIsVisible = false; 253 | 254 | if(this.shareTrigger) { 255 | this.shareTrigger.focus(); 256 | } 257 | 258 | this.shareTrigger = null; 259 | } 260 | 261 | showShare(event) { 262 | this.shareTrigger = event.target; 263 | this.shareIsVisible = true; 264 | } 265 | 266 | download() { 267 | this.jsZipIsLoading = true; 268 | 269 | loadJSZip().then((JSZip) => { 270 | this.jsZipIsLoading = false; 271 | let zip = new JSZip(); 272 | let prefix = 'glimmer-app/'; 273 | 274 | this.components.forEach(function(file) { 275 | if(file.component) { 276 | zip.file( 277 | prefix + pathForComponent(file.name), 278 | file.component.sourceText 279 | ); 280 | } 281 | 282 | zip.file( 283 | prefix + pathForTemplate(file.name), 284 | file.template.sourceText 285 | ); 286 | }); 287 | 288 | this.helpers.forEach(function(file) { 289 | zip.file( 290 | prefix + pathForHelper(file.name), 291 | file.helper.sourceText 292 | ); 293 | }); 294 | 295 | zip.generateAsync({type:'blob'}).then(function(content) { 296 | saveAs(content, 'glimmer-playground.zip'); 297 | }); 298 | }); 299 | } 300 | 301 | toggleVisibility(files: ComponentFiles | HelperFiles) { 302 | files.isVisible = !files.isVisible; 303 | } 304 | } 305 | 306 | function pathForTemplate(name) { 307 | return `src/ui/components/${name}/template.hbs`; 308 | } 309 | 310 | function pathForHelper(name) { 311 | return `src/ui/components/${name}/helper.ts`; 312 | } 313 | 314 | function pathForComponent(name) { 315 | return `src/ui/components/${name}/component.ts`; 316 | } 317 | 318 | function waitForDependencies() { 319 | return new Promise((resolve, reject) => { 320 | vsRequire(['vs/editor/editor.main', 'vs/language/typescript/lib/typescriptServices'], () => { 321 | initializeTypeScript(); 322 | resolve(); 323 | }); 324 | }); 325 | } 326 | 327 | const JSZIP_URL = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js'; 328 | 329 | function loadJSZip(): Promise { 330 | return new Promise((resolve, reject) => { 331 | vsRequire([JSZIP_URL], (script) => { 332 | resolve(script); 333 | }); 334 | }); 335 | } 336 | 337 | function initializeTypeScript() { 338 | monaco.editor.defineTheme('solarized-dark', SolarizedTheme); 339 | 340 | let ts = monaco.languages.typescript; 341 | 342 | ts.typescriptDefaults.setCompilerOptions({ 343 | target: ts.ScriptTarget.ES2017, 344 | moduleResolution: ts.ModuleResolutionKind.NodeJs, 345 | module: ts.ModuleKind.ES2015, 346 | experimentalDecorators: true, 347 | allowNonTsExtensions: true, 348 | traceResolution: true, 349 | noEmit: true 350 | }); 351 | 352 | for (let filename in typings) { 353 | let content = typings[filename]; 354 | ts.typescriptDefaults.addExtraLib(content, `file:///${filename}`); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/ui/components/Main/template.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#if isLoaded}} 3 |
4 |
5 |
6 | 9 | 12 | 15 |
16 | 17 | 18 | Guides 19 | API Docs 20 | Github 21 | 22 |
23 | {{#each components key="id" as |component|}} 24 |
25 |

26 | < 27 | {{~#if component.isEditable~}} 28 | 29 | {{~else~}} 30 | {{component.name}} 31 | {{~/if~}} 32 | > 33 | {{#if component.isEditable}} 34 | 35 | {{/if}} 36 | 37 |

38 | {{#if component.isVisible}} 39 | {{#if component.template}} 40 |
41 |

template.hbs

42 | 43 |
44 | {{/if}} 45 | 46 | {{#if component.component}} 47 |
48 |

49 | component.ts 50 | 51 |

52 | 53 |
54 | {{else}} 55 |
56 | 59 |
60 | {{/if}} 61 | {{/if}} 62 |
63 | {{/each}} 64 | 65 | {{#each helpers key="id" as |helper|}} 66 |
67 |

68 | \{{ 69 | {{~#if helper.isEditable~}} 70 | 71 | {{~else~}} 72 | {{component.name}} 73 | {{~/if~}} 74 | }} 75 | {{#if helper.isEditable}} 76 | 77 | {{/if}} 78 | 79 |

80 | {{#if helper.isVisible}} 81 | {{#if helper.helper}} 82 |
83 |

84 | helper.ts 85 |

86 | 87 |
88 | {{/if}} 89 | {{/if}} 90 |
91 | {{/each}} 92 |
93 | 94 |
95 | 96 |
97 | 98 | {{#if isVisualizerShowing}} 99 | 100 | {{/if}} 101 | 102 | 103 | {{else}} 104 |
105 |
106 | Loading the Playground... 107 |
108 |
109 |
110 |
111 | {{/if}} 112 | 113 | {{#if shareIsVisible}} 114 | 115 | 116 | 117 | 118 | 119 | {{/if}} 120 |
121 | -------------------------------------------------------------------------------- /src/ui/components/OverlayKeyBindings/component.ts: -------------------------------------------------------------------------------- 1 | import Component, { tracked } from '@glimmer/component'; 2 | import { ESCAPE, TAB } from '../../../utils/keys'; 3 | 4 | const FOCUSABLE_ELEMENTS = [ 5 | '[tabindex]', 6 | 'a', 7 | 'button:not([disabled])', 8 | 'input:not([disabled])', 9 | 'select:not([disabled])', 10 | 'textarea:not([disabled])' 11 | ]; 12 | 13 | const handleKeydown = function(e) { 14 | if (e.keyCode === ESCAPE) { 15 | this.escapeKeyPressed(); 16 | } 17 | 18 | if(e.keyCode === TAB) { 19 | this.tabKeyPressed(e); 20 | } 21 | }; 22 | 23 | const keepFocus = function(e) { 24 | if(!this.el.contains(e.target)) { 25 | this.focusableElements[0].focus(); 26 | } 27 | }; 28 | 29 | export default class extends Component { 30 | handleKeydown = null; 31 | keepFocus = null; 32 | el = null; 33 | 34 | constructor(owner, args) { 35 | super(owner, args); 36 | this.handleKeydown = handleKeydown.bind(this); 37 | this.keepFocus = keepFocus.bind(this); 38 | } 39 | 40 | didInsertElement() { 41 | let firstNode = this.bounds.firstNode; 42 | this.el = firstNode.nodeType === 3 ? firstNode.nextElementSibling : firstNode; 43 | 44 | let firstFocusable = this.focusableElements[0]; 45 | firstFocusable.focus(); 46 | 47 | this.setupEvents(); 48 | } 49 | 50 | willDestroy() { 51 | this.teardownEvents(); 52 | } 53 | 54 | setupEvents() { 55 | document.addEventListener('keydown', this.handleKeydown); 56 | document.body.addEventListener('focus', this.keepFocus, true); 57 | } 58 | 59 | teardownEvents() { 60 | document.removeEventListener('keydown', this.handleKeydown); 61 | document.body.removeEventListener('focus', this.keepFocus, true); 62 | } 63 | 64 | get focusableElements() { 65 | return Array.from( 66 | this.el.querySelectorAll(FOCUSABLE_ELEMENTS.join(',')) 67 | ); 68 | } 69 | 70 | hide() { 71 | this.teardownEvents(); 72 | this.args.hide(); 73 | } 74 | 75 | escapeKeyPressed() { 76 | this.hide(); 77 | } 78 | 79 | tabKeyPressed(event) { 80 | let elements = this.focusableElements; 81 | let index = elements.indexOf(document.activeElement); 82 | let firstElementActive = index === 0; 83 | let lastElementActive = index === elements.length - 1; 84 | 85 | if (firstElementActive && event.shiftKey) { 86 | event.preventDefault(); 87 | let el = elements[elements.length - 1]; 88 | el.focus(); 89 | } else if (lastElementActive && !event.shiftKey) { 90 | event.preventDefault(); 91 | let el = elements[0]; 92 | el.focus(); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /src/ui/components/OverlayKeyBindings/template.hbs: -------------------------------------------------------------------------------- 1 | {{yield (action hide)}} -------------------------------------------------------------------------------- /src/ui/components/OverlayUI/template.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ui/components/RemoveButton/component.ts: -------------------------------------------------------------------------------- 1 | import Component, { tracked } from '@glimmer/component'; 2 | 3 | export default class extends Component { 4 | @tracked confirm: boolean = false; 5 | 6 | toggle() { 7 | this.confirm = !this.confirm; 8 | } 9 | } -------------------------------------------------------------------------------- /src/ui/components/RemoveButton/template.hbs: -------------------------------------------------------------------------------- 1 | {{#if confirm}} 2 | 3 | 4 | {{else}} 5 | 6 | {{/if}} -------------------------------------------------------------------------------- /src/ui/components/Share/component.ts: -------------------------------------------------------------------------------- 1 | import Component, { tracked } from '@glimmer/component'; 2 | 3 | export default class extends Component { 4 | @tracked fullUrl = ''; 5 | @tracked tinyUrl = ''; 6 | @tracked fullUrlCopied = false; 7 | @tracked tinyUrlCopied = false; 8 | @tracked tinyUrlFetching = false; 9 | 10 | constructor(owner, args) { 11 | super(owner, args); 12 | this.fullUrl = window.location.href; 13 | } 14 | 15 | generateTinyurl() { 16 | this.tinyUrlFetching = true; 17 | let url = "https://glimmer-url-shortener-official.herokuapp.com/?url=" + this.fullUrl; 18 | fetch(url).then((response) => { 19 | this.tinyUrlFetching = false; 20 | return response.text(); 21 | }).then((text) => { 22 | this.tinyUrl = text; 23 | }).catch(() => { 24 | this.tinyUrlFetching = false; 25 | }); 26 | 27 | } 28 | 29 | copyToClipboard(id, successProperty) { 30 | let input = document.getElementById(id); 31 | 32 | if (input && input.select) { 33 | input.select(); 34 | 35 | try { 36 | document.execCommand('copy'); 37 | this[successProperty] = true; 38 | } 39 | catch (err) { 40 | alert('please press ctrl/cmd+c to copy'); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ui/components/Share/template.hbs: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 10 |
11 | 12 | 13 |
14 | 15 | {{#if tinyUrl}} 16 | 22 | {{else}} 23 | {{#if tinyUrlFetching}} 24 | 25 | {{else}} 26 | 27 | {{/if}} 28 | {{/if}} 29 |
30 | 31 | 32 |
33 |

34 | 41 | To run the project after downloading, open the zip file, navigate to the new directory in your terminal and execute the following command:
42 | ember init -b @glimmer/blueprint 43 |

44 | {{#if @loading}} 45 | 46 | {{else}} 47 | 48 | {{/if}} 49 |
50 | -------------------------------------------------------------------------------- /src/ui/components/Visualizer/-utils/compiler.ts: -------------------------------------------------------------------------------- 1 | import { CAPABILITIES } from "@glimmer/component"; 2 | import { BundleCompiler, BundleCompilationResult, CompilerDelegate } from "@glimmer/bundle-compiler"; 3 | import { ComponentCapabilities, ProgramSymbolTable, ModuleLocator } from "@glimmer/interfaces"; 4 | import { CompilableTemplate, CompileOptions } from "@glimmer/opcode-compiler"; 5 | import { SerializedTemplateBlock } from "@glimmer/wire-format"; 6 | 7 | export function compile(templates: {}, helpers: {}) { 8 | let delegate = new VisualizerCompilerDelegate(templates, helpers); 9 | let bundle = new BundleCompiler(delegate); 10 | 11 | for (let specifier in templates) { 12 | let [type, path] = specifier.split(':'); 13 | 14 | let { meta, block } = templates[specifier]; 15 | let source = meta.source; 16 | 17 | let locator = { name: 'default', module: path }; 18 | bundle.addTemplateSource(locator, source); 19 | } 20 | 21 | return bundle.compile(); 22 | } 23 | 24 | 25 | class VisualizerCompilerDelegate implements CompilerDelegate { 26 | constructor(private templates: {}, private helpers: {}) { 27 | } 28 | 29 | hasComponentInScope(componentName: string, referrer: ModuleLocator): boolean { 30 | let key = `template:/glimmer-repl/components/${componentName}`; 31 | return key in this.templates; 32 | } 33 | 34 | resolveComponent(componentName: string, referrer: ModuleLocator): ModuleLocator { 35 | return { module: `/glimmer-repl/components/${componentName}`, name: 'default' }; 36 | } 37 | 38 | getComponentCapabilities(specifier: ModuleLocator): ComponentCapabilities { 39 | return CAPABILITIES; 40 | } 41 | 42 | hasHelperInScope(helperName: string, referrer: ModuleLocator): boolean { 43 | if (helperName === 'action' || helperName === 'if') { 44 | return true; 45 | } 46 | 47 | let key = `helper:/glimmer-repl/components/${helperName}`; 48 | return key in this.helpers; 49 | } 50 | 51 | resolveHelper(helperName: string, referrer: ModuleLocator): ModuleLocator { 52 | return { module: `/glimmer-repl/helpers/${helperName}`, name: 'default' }; 53 | } 54 | 55 | hasModifierInScope(modifierName: string, referrer: ModuleLocator): boolean { 56 | throw new Error("Method not implemented."); 57 | } 58 | 59 | resolveModifier(modifierName: string, referrer: ModuleLocator): ModuleLocator { 60 | throw new Error("Method not implemented."); 61 | } 62 | 63 | hasPartialInScope(partialName: string, referrer: ModuleLocator): boolean { 64 | throw new Error("Method not implemented."); 65 | } 66 | 67 | resolvePartial(partialName: string, referrer: ModuleLocator): ModuleLocator { 68 | throw new Error("Method not implemented."); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/ui/components/Visualizer/-utils/hexdump.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Adapted from https://github.com/bma73/hexdump-js 3 | 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2014 bma73 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | function _fillUp(value, count, fillWith) { 28 | var l = count - value.length; 29 | var ret = ""; 30 | while (--l > -1) 31 | ret += fillWith; 32 | return ret + value; 33 | } 34 | 35 | export default function hexdump(arrayBuffer, offset = 0, length = arrayBuffer.byteLength) { 36 | let view = new DataView(arrayBuffer); 37 | 38 | let out = _fillUp("Offset", 8, " ") + " 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n\n"; 39 | let row = ""; 40 | for (let i = 0; i < length; i += 16) { 41 | row += _fillUp(offset.toString(16).toUpperCase(), 8, "0") + " "; 42 | let n = Math.min(16, length - offset); 43 | let string = ""; 44 | for (let j = 0; j < 16; ++j) { 45 | if (j < n) { 46 | let value = view.getUint8(offset); 47 | string += value >= 32 ? String.fromCharCode(value) : "."; 48 | row += _fillUp(value.toString(16).toUpperCase(), 2, "0") + " "; 49 | offset++; 50 | } 51 | else { 52 | row += " "; 53 | string += " "; 54 | } 55 | } 56 | row += " " + string + "\n"; 57 | } 58 | out += row; 59 | return out; 60 | }; 61 | -------------------------------------------------------------------------------- /src/ui/components/Visualizer/-utils/inspect-program.ts: -------------------------------------------------------------------------------- 1 | import { opcodeMetadata, Operand } from "./opcode-metadata"; 2 | import { compile } from "./compiler"; 3 | import { OpcodeSize } from "@glimmer/encoder"; 4 | import { hydrateProgram } from "@glimmer/program"; 5 | 6 | import { toHex } from '../hex/helper'; 7 | import hexdump from './hexdump'; 8 | import { CompilerArtifacts, ConstantPool } from "@glimmer/interfaces"; 9 | 10 | export function inspect(map: {}) { 11 | let templates = {}; 12 | let helpers = {}; 13 | 14 | for (let specifier in map) { 15 | let [type, path] = specifier.split(':'); 16 | if (type === 'template') { 17 | templates[specifier] = map[specifier]; 18 | } 19 | 20 | if (type === 'helper') { 21 | helpers[specifier] = map[specifier]; 22 | } 23 | } 24 | 25 | let { heap, pool: constants } = compile(templates, helpers); 26 | 27 | let buffer = Array.from(new Uint8Array(heap.buffer)) 28 | .map(n => toHex(n)); 29 | 30 | let opcodes = inspectOpcodes({ heap, constants }); 31 | 32 | return { 33 | hexdump: hexdump(heap.buffer), 34 | byteLength: heap.buffer.byteLength, 35 | buffer, 36 | opcodes, 37 | constants 38 | }; 39 | } 40 | 41 | const UNKNOWN_OP = { 42 | name: 'UNKNOWN_OP_CODE', 43 | operands: 0, 44 | ops: [] 45 | }; 46 | 47 | function inspectOpcodes(artifacts: CompilerArtifacts) { 48 | let program = hydrateProgram(artifacts); 49 | 50 | let pc = 0; 51 | let ops = []; 52 | let opcode = program.opcode(pc); 53 | let size = artifacts.heap.buffer.byteLength / 4; 54 | 55 | while (pc < size) { 56 | let op = opcodeMetadata(opcode.type, opcode.isMachine) || UNKNOWN_OP; 57 | 58 | let operands = op.ops.map((operand, offset) => { 59 | let val: number; 60 | switch (offset) { 61 | case 0: 62 | val = opcode.op1; 63 | break; 64 | case 1: 65 | val = opcode.op2; 66 | break; 67 | case 2: 68 | val = opcode.op3; 69 | break; 70 | } 71 | 72 | return operandFor(operand, val, artifacts.constants); 73 | }); 74 | 75 | ops.push({ 76 | name: op.name, 77 | opcode: opcode.type, 78 | operands 79 | }); 80 | 81 | pc += opcode.size 82 | opcode = program.opcode(pc); 83 | } 84 | 85 | return ops; 86 | } 87 | 88 | function operandFor(operand: Operand, rawValue: any, pool: ConstantPool) { 89 | let value: any = rawValue; 90 | 91 | switch (operand.type) { 92 | case 'str': 93 | value = pool.strings[rawValue] 94 | .replace(/\n/g, '\\n'); 95 | value = `"${value}"`; 96 | break; 97 | } 98 | 99 | return { 100 | type: operand.type, 101 | name: operand.name, 102 | rawValue, 103 | value 104 | }; 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/ui/components/Visualizer/-utils/opcode-metadata.ts: -------------------------------------------------------------------------------- 1 | /* This file is copied from Glimmer VM's @glimmer/debug package, which is not currently published on npm. */ 2 | 3 | import { Option, Dict } from '@glimmer/interfaces'; 4 | 5 | const tuple = (...args: T) => args; 6 | 7 | // TODO: How do these map onto constant and machine types? 8 | export const OPERAND_TYPES = tuple( 9 | 'u32', 10 | 'i32', 11 | 'handle', 12 | 'bool', 13 | 'str', 14 | 'option-str', 15 | 'str-array', 16 | 'array', 17 | 'unknown', 18 | 'primitive', 19 | 'scope', 20 | 'symbol-table', 21 | 'register', 22 | 'meta' 23 | ); 24 | 25 | function isOperandType(s: string): s is OperandType { 26 | return OPERAND_TYPES.indexOf(s as any) !== -1; 27 | } 28 | 29 | export type OperandType = typeof OPERAND_TYPES[number]; 30 | 31 | export interface Operand { 32 | type: OperandType; 33 | name: string; 34 | } 35 | 36 | export type OperandList = ([] | [Operand] | [Operand, Operand] | [Operand, Operand, Operand]) & 37 | Operand[]; 38 | 39 | export interface NormalizedMetadata { 40 | name: string; 41 | mnemonic: string; 42 | before: null; 43 | stackChange: Option; 44 | ops: OperandList; 45 | operands: number; 46 | check: boolean; 47 | } 48 | 49 | export type Stack = [string[], string[]]; 50 | 51 | export interface RawOperandMetadata { 52 | kind: 'machine' | 'syscall'; 53 | format: RawOperandFormat; 54 | skip?: true; 55 | operation: string; 56 | 'operand-stack'?: [string[], string[]]; 57 | notes?: string; 58 | } 59 | 60 | export type RawOperandFormat = string | string[]; 61 | 62 | export function normalize(key: string, input: RawOperandMetadata): NormalizedMetadata { 63 | let name; 64 | 65 | if (input.format === undefined) { 66 | throw new Error(`Missing format in ${JSON.stringify(input)}`); 67 | } 68 | 69 | if (Array.isArray(input.format)) { 70 | name = input.format[0]; 71 | } else { 72 | name = input.format; 73 | } 74 | 75 | let ops: OperandList = Array.isArray(input.format) ? operands(input.format.slice(1)) : []; 76 | 77 | return { 78 | name, 79 | mnemonic: key, 80 | before: null, 81 | stackChange: stackChange(input['operand-stack']), 82 | ops, 83 | operands: ops.length, 84 | check: input.skip === true ? false : true, 85 | }; 86 | } 87 | 88 | function stackChange(stack?: Stack): Option { 89 | if (stack === undefined) { 90 | return 0; 91 | } 92 | 93 | let before = stack[0]; 94 | let after = stack[1]; 95 | 96 | if (hasRest(before) || hasRest(after)) { 97 | return null; 98 | } 99 | 100 | return after.length - before.length; 101 | } 102 | 103 | function hasRest(input: string[]): boolean { 104 | if (!Array.isArray(input)) { 105 | throw new Error(`Unexpected stack entry: ${JSON.stringify(input)}`); 106 | } 107 | return input.some(s => s.slice(-3) === '...'); 108 | } 109 | 110 | function operands(input: string[]): OperandList { 111 | if (!Array.isArray(input)) { 112 | throw new Error(`Expected operands array, got ${JSON.stringify(input)}`); 113 | } 114 | return input.map(op) as OperandList; 115 | } 116 | 117 | function op(input: string): Operand { 118 | let [name, type] = input.split(':'); 119 | 120 | if (isOperandType(type)) { 121 | return { name, type }; 122 | } else { 123 | throw new Error(`Expected operand, found ${JSON.stringify(input)}`); 124 | } 125 | } 126 | 127 | export function normalizeAll(parsed: { 128 | machine: Dict; 129 | syscall: Dict; 130 | }): { machine: Dict; syscall: Dict } { 131 | let machine = normalizeParsed(parsed.machine); 132 | let syscall = normalizeParsed(parsed.syscall); 133 | 134 | return { machine, syscall }; 135 | } 136 | 137 | export function normalizeParsed(parsed: Dict): Dict { 138 | let out = Object.create(null) as Dict; 139 | 140 | for (let key of Object.keys(parsed)) { 141 | out[key] = normalize(key, parsed[key]); 142 | } 143 | 144 | return out; 145 | } 146 | 147 | export function buildEnum( 148 | name: string, 149 | parsed: Dict, 150 | offset: number, 151 | max?: number 152 | ): { enumString: string; predicate: string } { 153 | let e = [`export const enum ${name} {`]; 154 | 155 | let last: number; 156 | 157 | Object.keys(parsed).forEach((key, i) => { 158 | e.push(` ${parsed[key].name} = ${offset + i},`); 159 | last = i; 160 | }); 161 | 162 | e.push(` Size = ${last! + offset + 1},`); 163 | e.push('}'); 164 | 165 | let enumString = e.join('\n'); 166 | 167 | let predicate; 168 | 169 | if (max) { 170 | predicate = strip` 171 | export function is${name}(value: number): value is ${name} { 172 | return value >= ${offset} && value <= ${max}; 173 | } 174 | `; 175 | } else { 176 | predicate = strip` 177 | export function is${name}(value: number): value is ${name} { 178 | return value >= ${offset}; 179 | } 180 | `; 181 | } 182 | 183 | return { enumString, predicate }; 184 | } 185 | 186 | export function strip(strings: TemplateStringsArray, ...args: unknown[]) { 187 | let out = ''; 188 | for (let i = 0; i < strings.length; i++) { 189 | let string = strings[i]; 190 | let dynamic = args[i] !== undefined ? String(args[i]) : ''; 191 | 192 | out += `${string}${dynamic}`; 193 | } 194 | 195 | out = out.match(/^\s*?\n?([\s\S]*?)\n?\s*$/)![1]; 196 | 197 | let min = Number.MAX_SAFE_INTEGER; 198 | 199 | for (let line of out.split('\n')) { 200 | let leading = line.match(/^\s*/)![0].length; 201 | 202 | min = Math.min(min, leading); 203 | } 204 | 205 | let stripped = ''; 206 | 207 | for (let line of out.split('\n')) { 208 | stripped += line.slice(min) + '\n'; 209 | } 210 | 211 | return stripped; 212 | } 213 | 214 | export const META_KIND = tuple('METADATA', 'MACHINE_METADATA'); 215 | export type META_KIND = (typeof META_KIND)[number]; 216 | 217 | export function buildSingleMeta( 218 | kind: META_KIND, 219 | all: Dict, 220 | key: keyof typeof all 221 | ): string { 222 | let e = kind === 'MACHINE_METADATA' ? 'MachineOp' : 'Op'; 223 | return `${kind}[${e}.${all[key].name}] = ${stringify(all[key], 0)};`; 224 | } 225 | 226 | function stringify(o: unknown, pad: number): string { 227 | if (typeof o !== 'object' || o === null) { 228 | if (typeof o === 'string') { 229 | return `'${o}'`; 230 | } 231 | return JSON.stringify(o); 232 | } 233 | 234 | if (Array.isArray(o)) { 235 | return `[${o.map(v => stringify(v, pad)).join(', ')}]`; 236 | } 237 | 238 | let out = ['{']; 239 | 240 | for (let key of Object.keys(o)) { 241 | out.push(`${' '.repeat(pad + 2)}${key}: ${stringify((o as Dict)[key], pad + 2)},`); 242 | } 243 | 244 | out.push(`${' '.repeat(pad)}}`); 245 | 246 | return out.join('\n'); 247 | } 248 | 249 | export function buildMetas(kind: META_KIND, all: Dict): string { 250 | let out = []; 251 | 252 | for (let key of Object.keys(all)) { 253 | out.push(buildSingleMeta(kind, all, key)); 254 | } 255 | 256 | return out.join('\n\n'); 257 | } 258 | 259 | import { MachineOp, Op } from '@glimmer/interfaces'; 260 | import { fillNulls } from '@glimmer/util'; 261 | 262 | export function opcodeMetadata(op: MachineOp | Op, isMachine: 0 | 1): Option { 263 | let value = isMachine ? MACHINE_METADATA[op] : METADATA[op]; 264 | 265 | return value || null; 266 | } 267 | 268 | export interface NormalizedMetadata { 269 | name: string; 270 | mnemonic: string; 271 | before: null; 272 | stackChange: Option; 273 | ops: OperandList; 274 | operands: number; 275 | check: boolean; 276 | } 277 | 278 | const METADATA: Option[] = fillNulls(Op.Size); 279 | const MACHINE_METADATA: Option[] = fillNulls(MachineOp.Size); 280 | MACHINE_METADATA[MachineOp.PushFrame] = { 281 | name: 'PushFrame', 282 | mnemonic: 'pushf', 283 | before: null, 284 | stackChange: 2, 285 | ops: [], 286 | operands: 0, 287 | check: true, 288 | }; 289 | 290 | MACHINE_METADATA[MachineOp.PopFrame] = { 291 | name: 'PopFrame', 292 | mnemonic: 'popf', 293 | before: null, 294 | stackChange: -2, 295 | ops: [], 296 | operands: 0, 297 | check: false, 298 | }; 299 | 300 | MACHINE_METADATA[MachineOp.InvokeVirtual] = { 301 | name: 'InvokeVirtual', 302 | mnemonic: 'vcall', 303 | before: null, 304 | stackChange: -1, 305 | ops: [], 306 | operands: 0, 307 | check: true, 308 | }; 309 | 310 | MACHINE_METADATA[MachineOp.InvokeStatic] = { 311 | name: 'InvokeStatic', 312 | mnemonic: 'scall', 313 | before: null, 314 | stackChange: 0, 315 | ops: [ 316 | { 317 | name: 'offset', 318 | type: 'u32', 319 | }, 320 | ], 321 | operands: 1, 322 | check: true, 323 | }; 324 | 325 | MACHINE_METADATA[MachineOp.Jump] = { 326 | name: 'Jump', 327 | mnemonic: 'goto', 328 | before: null, 329 | stackChange: 0, 330 | ops: [ 331 | { 332 | name: 'to', 333 | type: 'u32', 334 | }, 335 | ], 336 | operands: 1, 337 | check: true, 338 | }; 339 | 340 | MACHINE_METADATA[MachineOp.Return] = { 341 | name: 'Return', 342 | mnemonic: 'ret', 343 | before: null, 344 | stackChange: 0, 345 | ops: [], 346 | operands: 0, 347 | check: false, 348 | }; 349 | 350 | MACHINE_METADATA[MachineOp.ReturnTo] = { 351 | name: 'ReturnTo', 352 | mnemonic: 'setra', 353 | before: null, 354 | stackChange: 0, 355 | ops: [ 356 | { 357 | name: 'offset', 358 | type: 'i32', 359 | }, 360 | ], 361 | operands: 1, 362 | check: true, 363 | }; 364 | METADATA[Op.Helper] = { 365 | name: 'Helper', 366 | mnemonic: 'ncall', 367 | before: null, 368 | stackChange: null, 369 | ops: [ 370 | { 371 | name: 'helper', 372 | type: 'handle', 373 | }, 374 | ], 375 | operands: 1, 376 | check: true, 377 | }; 378 | 379 | METADATA[Op.SetNamedVariables] = { 380 | name: 'SetNamedVariables', 381 | mnemonic: 'vsargs', 382 | before: null, 383 | stackChange: 0, 384 | ops: [ 385 | { 386 | name: 'register', 387 | type: 'u32', 388 | }, 389 | ], 390 | operands: 1, 391 | check: true, 392 | }; 393 | 394 | METADATA[Op.SetBlocks] = { 395 | name: 'SetBlocks', 396 | mnemonic: 'vbblocks', 397 | before: null, 398 | stackChange: 0, 399 | ops: [ 400 | { 401 | name: 'register', 402 | type: 'u32', 403 | }, 404 | ], 405 | operands: 1, 406 | check: true, 407 | }; 408 | 409 | METADATA[Op.SetVariable] = { 410 | name: 'SetVariable', 411 | mnemonic: 'sbvar', 412 | before: null, 413 | stackChange: -1, 414 | ops: [ 415 | { 416 | name: 'symbol', 417 | type: 'u32', 418 | }, 419 | ], 420 | operands: 1, 421 | check: true, 422 | }; 423 | 424 | METADATA[Op.SetAotBlock] = { 425 | name: 'SetAotBlock', 426 | mnemonic: 'sbblock', 427 | before: null, 428 | stackChange: -3, 429 | ops: [ 430 | { 431 | name: 'symbol', 432 | type: 'u32', 433 | }, 434 | ], 435 | operands: 1, 436 | check: true, 437 | }; 438 | 439 | METADATA[Op.SetJitBlock] = { 440 | name: 'SetJitBlock', 441 | mnemonic: 'sjblock', 442 | before: null, 443 | stackChange: -3, 444 | ops: [ 445 | { 446 | name: 'symbol', 447 | type: 'u32', 448 | }, 449 | ], 450 | operands: 1, 451 | check: true, 452 | }; 453 | 454 | METADATA[Op.GetVariable] = { 455 | name: 'GetVariable', 456 | mnemonic: 'symload', 457 | before: null, 458 | stackChange: 1, 459 | ops: [ 460 | { 461 | name: 'symbol', 462 | type: 'u32', 463 | }, 464 | ], 465 | operands: 1, 466 | check: true, 467 | }; 468 | 469 | METADATA[Op.GetProperty] = { 470 | name: 'GetProperty', 471 | mnemonic: 'getprop', 472 | before: null, 473 | stackChange: 0, 474 | ops: [ 475 | { 476 | name: 'property', 477 | type: 'str', 478 | }, 479 | ], 480 | operands: 1, 481 | check: true, 482 | }; 483 | 484 | METADATA[Op.GetBlock] = { 485 | name: 'GetBlock', 486 | mnemonic: 'blockload', 487 | before: null, 488 | stackChange: 3, 489 | ops: [ 490 | { 491 | name: 'block', 492 | type: 'u32', 493 | }, 494 | ], 495 | operands: 1, 496 | check: true, 497 | }; 498 | 499 | METADATA[Op.HasBlock] = { 500 | name: 'HasBlock', 501 | mnemonic: 'hasblockload', 502 | before: null, 503 | stackChange: 1, 504 | ops: [ 505 | { 506 | name: 'block', 507 | type: 'u32', 508 | }, 509 | ], 510 | operands: 1, 511 | check: true, 512 | }; 513 | 514 | METADATA[Op.HasBlockParams] = { 515 | name: 'HasBlockParams', 516 | mnemonic: 'hasparamsload', 517 | before: null, 518 | stackChange: -2, 519 | ops: [ 520 | { 521 | name: 'block', 522 | type: 'u32', 523 | }, 524 | ], 525 | operands: 1, 526 | check: true, 527 | }; 528 | 529 | METADATA[Op.Concat] = { 530 | name: 'Concat', 531 | mnemonic: 'concat', 532 | before: null, 533 | stackChange: null, 534 | ops: [ 535 | { 536 | name: 'count', 537 | type: 'u32', 538 | }, 539 | ], 540 | operands: 1, 541 | check: true, 542 | }; 543 | 544 | METADATA[Op.Constant] = { 545 | name: 'Constant', 546 | mnemonic: 'rconstload', 547 | before: null, 548 | stackChange: 1, 549 | ops: [ 550 | { 551 | name: 'constant', 552 | type: 'unknown', 553 | }, 554 | ], 555 | operands: 1, 556 | check: true, 557 | }; 558 | 559 | METADATA[Op.Primitive] = { 560 | name: 'Primitive', 561 | mnemonic: 'pconstload', 562 | before: null, 563 | stackChange: 1, 564 | ops: [ 565 | { 566 | name: 'constant', 567 | type: 'primitive', 568 | }, 569 | ], 570 | operands: 1, 571 | check: true, 572 | }; 573 | 574 | METADATA[Op.PrimitiveReference] = { 575 | name: 'PrimitiveReference', 576 | mnemonic: 'ptoref', 577 | before: null, 578 | stackChange: 0, 579 | ops: [], 580 | operands: 0, 581 | check: true, 582 | }; 583 | 584 | METADATA[Op.ReifyU32] = { 585 | name: 'ReifyU32', 586 | mnemonic: 'reifyload', 587 | before: null, 588 | stackChange: 1, 589 | ops: [], 590 | operands: 0, 591 | check: true, 592 | }; 593 | 594 | METADATA[Op.Dup] = { 595 | name: 'Dup', 596 | mnemonic: 'dup', 597 | before: null, 598 | stackChange: 1, 599 | ops: [ 600 | { 601 | name: 'register', 602 | type: 'u32', 603 | }, 604 | { 605 | name: 'offset', 606 | type: 'u32', 607 | }, 608 | ], 609 | operands: 2, 610 | check: true, 611 | }; 612 | 613 | METADATA[Op.Pop] = { 614 | name: 'Pop', 615 | mnemonic: 'pop', 616 | before: null, 617 | stackChange: 0, 618 | ops: [ 619 | { 620 | name: 'count', 621 | type: 'u32', 622 | }, 623 | ], 624 | operands: 1, 625 | check: false, 626 | }; 627 | 628 | METADATA[Op.Load] = { 629 | name: 'Load', 630 | mnemonic: 'put', 631 | before: null, 632 | stackChange: -1, 633 | ops: [ 634 | { 635 | name: 'register', 636 | type: 'u32', 637 | }, 638 | ], 639 | operands: 1, 640 | check: true, 641 | }; 642 | 643 | METADATA[Op.Fetch] = { 644 | name: 'Fetch', 645 | mnemonic: 'regload', 646 | before: null, 647 | stackChange: 1, 648 | ops: [ 649 | { 650 | name: 'register', 651 | type: 'u32', 652 | }, 653 | ], 654 | operands: 1, 655 | check: true, 656 | }; 657 | 658 | METADATA[Op.RootScope] = { 659 | name: 'RootScope', 660 | mnemonic: 'rscopepush', 661 | before: null, 662 | stackChange: 0, 663 | ops: [ 664 | { 665 | name: 'symbols', 666 | type: 'u32', 667 | }, 668 | ], 669 | operands: 1, 670 | check: true, 671 | }; 672 | 673 | METADATA[Op.VirtualRootScope] = { 674 | name: 'VirtualRootScope', 675 | mnemonic: 'vrscopepush', 676 | before: null, 677 | stackChange: 0, 678 | ops: [ 679 | { 680 | name: 'register', 681 | type: 'u32', 682 | }, 683 | ], 684 | operands: 1, 685 | check: true, 686 | }; 687 | 688 | METADATA[Op.ChildScope] = { 689 | name: 'ChildScope', 690 | mnemonic: 'cscopepush', 691 | before: null, 692 | stackChange: 0, 693 | ops: [], 694 | operands: 0, 695 | check: true, 696 | }; 697 | 698 | METADATA[Op.PopScope] = { 699 | name: 'PopScope', 700 | mnemonic: 'scopepop', 701 | before: null, 702 | stackChange: 0, 703 | ops: [], 704 | operands: 0, 705 | check: true, 706 | }; 707 | 708 | METADATA[Op.Text] = { 709 | name: 'Text', 710 | mnemonic: 'apnd_text', 711 | before: null, 712 | stackChange: 0, 713 | ops: [ 714 | { 715 | name: 'contents', 716 | type: 'str', 717 | }, 718 | ], 719 | operands: 1, 720 | check: true, 721 | }; 722 | 723 | METADATA[Op.Comment] = { 724 | name: 'Comment', 725 | mnemonic: 'apnd_comment', 726 | before: null, 727 | stackChange: 0, 728 | ops: [ 729 | { 730 | name: 'contents', 731 | type: 'str', 732 | }, 733 | ], 734 | operands: 1, 735 | check: true, 736 | }; 737 | 738 | METADATA[Op.AppendHTML] = { 739 | name: 'AppendHTML', 740 | mnemonic: 'apnd_dynhtml', 741 | before: null, 742 | stackChange: -1, 743 | ops: [], 744 | operands: 0, 745 | check: true, 746 | }; 747 | 748 | METADATA[Op.AppendSafeHTML] = { 749 | name: 'AppendSafeHTML', 750 | mnemonic: 'apnd_dynshtml', 751 | before: null, 752 | stackChange: -1, 753 | ops: [], 754 | operands: 0, 755 | check: true, 756 | }; 757 | 758 | METADATA[Op.AppendDocumentFragment] = { 759 | name: 'AppendDocumentFragment', 760 | mnemonic: 'apnd_dynfrag', 761 | before: null, 762 | stackChange: -1, 763 | ops: [], 764 | operands: 0, 765 | check: true, 766 | }; 767 | 768 | METADATA[Op.AppendNode] = { 769 | name: 'AppendNode', 770 | mnemonic: 'apnd_dynnode', 771 | before: null, 772 | stackChange: -1, 773 | ops: [], 774 | operands: 0, 775 | check: true, 776 | }; 777 | 778 | METADATA[Op.AppendText] = { 779 | name: 'AppendText', 780 | mnemonic: 'apnd_dyntext', 781 | before: null, 782 | stackChange: -1, 783 | ops: [], 784 | operands: 0, 785 | check: true, 786 | }; 787 | 788 | METADATA[Op.OpenElement] = { 789 | name: 'OpenElement', 790 | mnemonic: 'apnd_tag', 791 | before: null, 792 | stackChange: 0, 793 | ops: [ 794 | { 795 | name: 'tag', 796 | type: 'str', 797 | }, 798 | ], 799 | operands: 1, 800 | check: true, 801 | }; 802 | 803 | METADATA[Op.OpenDynamicElement] = { 804 | name: 'OpenDynamicElement', 805 | mnemonic: 'apnd_dyntag', 806 | before: null, 807 | stackChange: -1, 808 | ops: [], 809 | operands: 0, 810 | check: true, 811 | }; 812 | 813 | METADATA[Op.PushRemoteElement] = { 814 | name: 'PushRemoteElement', 815 | mnemonic: 'apnd_remotetag', 816 | before: null, 817 | stackChange: -3, 818 | ops: [], 819 | operands: 0, 820 | check: true, 821 | }; 822 | 823 | METADATA[Op.StaticAttr] = { 824 | name: 'StaticAttr', 825 | mnemonic: 'apnd_attr', 826 | before: null, 827 | stackChange: 0, 828 | ops: [ 829 | { 830 | name: 'name', 831 | type: 'str', 832 | }, 833 | { 834 | name: 'value', 835 | type: 'str', 836 | }, 837 | { 838 | name: 'namespace', 839 | type: 'option-str', 840 | }, 841 | ], 842 | operands: 3, 843 | check: true, 844 | }; 845 | 846 | METADATA[Op.DynamicAttr] = { 847 | name: 'DynamicAttr', 848 | mnemonic: 'apnd_dynattr', 849 | before: null, 850 | stackChange: -1, 851 | ops: [ 852 | { 853 | name: 'name', 854 | type: 'str', 855 | }, 856 | { 857 | name: 'trusting', 858 | type: 'bool', 859 | }, 860 | { 861 | name: 'namespace', 862 | type: 'option-str', 863 | }, 864 | ], 865 | operands: 3, 866 | check: true, 867 | }; 868 | 869 | METADATA[Op.ComponentAttr] = { 870 | name: 'ComponentAttr', 871 | mnemonic: 'apnd_cattr', 872 | before: null, 873 | stackChange: -1, 874 | ops: [ 875 | { 876 | name: 'name', 877 | type: 'str', 878 | }, 879 | { 880 | name: 'trusting', 881 | type: 'bool', 882 | }, 883 | { 884 | name: 'namespace', 885 | type: 'option-str', 886 | }, 887 | ], 888 | operands: 3, 889 | check: true, 890 | }; 891 | 892 | METADATA[Op.FlushElement] = { 893 | name: 'FlushElement', 894 | mnemonic: 'apnd_flushtag', 895 | before: null, 896 | stackChange: 0, 897 | ops: [], 898 | operands: 0, 899 | check: true, 900 | }; 901 | 902 | METADATA[Op.CloseElement] = { 903 | name: 'CloseElement', 904 | mnemonic: 'apnd_closetag', 905 | before: null, 906 | stackChange: 0, 907 | ops: [], 908 | operands: 0, 909 | check: true, 910 | }; 911 | 912 | METADATA[Op.PopRemoteElement] = { 913 | name: 'PopRemoteElement', 914 | mnemonic: 'apnd_closeremotetag', 915 | before: null, 916 | stackChange: 0, 917 | ops: [], 918 | operands: 0, 919 | check: true, 920 | }; 921 | 922 | METADATA[Op.Modifier] = { 923 | name: 'Modifier', 924 | mnemonic: 'apnd_modifier', 925 | before: null, 926 | stackChange: -1, 927 | ops: [ 928 | { 929 | name: 'helper', 930 | type: 'handle', 931 | }, 932 | ], 933 | operands: 1, 934 | check: true, 935 | }; 936 | 937 | METADATA[Op.BindDynamicScope] = { 938 | name: 'BindDynamicScope', 939 | mnemonic: 'setdynscope', 940 | before: null, 941 | stackChange: null, 942 | ops: [ 943 | { 944 | name: 'names', 945 | type: 'str-array', 946 | }, 947 | ], 948 | operands: 1, 949 | check: true, 950 | }; 951 | 952 | METADATA[Op.PushDynamicScope] = { 953 | name: 'PushDynamicScope', 954 | mnemonic: 'dynscopepush', 955 | before: null, 956 | stackChange: 0, 957 | ops: [], 958 | operands: 0, 959 | check: true, 960 | }; 961 | 962 | METADATA[Op.PopDynamicScope] = { 963 | name: 'PopDynamicScope', 964 | mnemonic: 'dynscopepop', 965 | before: null, 966 | stackChange: 0, 967 | ops: [], 968 | operands: 0, 969 | check: true, 970 | }; 971 | 972 | METADATA[Op.CompileBlock] = { 973 | name: 'CompileBlock', 974 | mnemonic: 'cmpblock', 975 | before: null, 976 | stackChange: 0, 977 | ops: [], 978 | operands: 0, 979 | check: true, 980 | }; 981 | 982 | METADATA[Op.PushBlockScope] = { 983 | name: 'PushBlockScope', 984 | mnemonic: 'scopeload', 985 | before: null, 986 | stackChange: 1, 987 | ops: [ 988 | { 989 | name: 'scope', 990 | type: 'scope', 991 | }, 992 | ], 993 | operands: 1, 994 | check: true, 995 | }; 996 | 997 | METADATA[Op.PushSymbolTable] = { 998 | name: 'PushSymbolTable', 999 | mnemonic: 'dsymload', 1000 | before: null, 1001 | stackChange: 1, 1002 | ops: [ 1003 | { 1004 | name: 'table', 1005 | type: 'symbol-table', 1006 | }, 1007 | ], 1008 | operands: 1, 1009 | check: true, 1010 | }; 1011 | 1012 | METADATA[Op.InvokeYield] = { 1013 | name: 'InvokeYield', 1014 | mnemonic: 'invokeyield', 1015 | before: null, 1016 | stackChange: null, 1017 | ops: [], 1018 | operands: 0, 1019 | check: true, 1020 | }; 1021 | 1022 | METADATA[Op.JumpIf] = { 1023 | name: 'JumpIf', 1024 | mnemonic: 'iftrue', 1025 | before: null, 1026 | stackChange: -1, 1027 | ops: [ 1028 | { 1029 | name: 'to', 1030 | type: 'u32', 1031 | }, 1032 | ], 1033 | operands: 1, 1034 | check: true, 1035 | }; 1036 | 1037 | METADATA[Op.JumpUnless] = { 1038 | name: 'JumpUnless', 1039 | mnemonic: 'iffalse', 1040 | before: null, 1041 | stackChange: -1, 1042 | ops: [ 1043 | { 1044 | name: 'to', 1045 | type: 'u32', 1046 | }, 1047 | ], 1048 | operands: 1, 1049 | check: true, 1050 | }; 1051 | 1052 | METADATA[Op.JumpEq] = { 1053 | name: 'JumpEq', 1054 | mnemonic: 'ifeq', 1055 | before: null, 1056 | stackChange: 0, 1057 | ops: [ 1058 | { 1059 | name: 'to', 1060 | type: 'i32', 1061 | }, 1062 | { 1063 | name: 'comparison', 1064 | type: 'i32', 1065 | }, 1066 | ], 1067 | operands: 2, 1068 | check: true, 1069 | }; 1070 | 1071 | METADATA[Op.AssertSame] = { 1072 | name: 'AssertSame', 1073 | mnemonic: 'assert_eq', 1074 | before: null, 1075 | stackChange: 0, 1076 | ops: [], 1077 | operands: 0, 1078 | check: true, 1079 | }; 1080 | 1081 | METADATA[Op.Enter] = { 1082 | name: 'Enter', 1083 | mnemonic: 'blk_start', 1084 | before: null, 1085 | stackChange: 0, 1086 | ops: [ 1087 | { 1088 | name: 'args', 1089 | type: 'u32', 1090 | }, 1091 | ], 1092 | operands: 1, 1093 | check: true, 1094 | }; 1095 | 1096 | METADATA[Op.Exit] = { 1097 | name: 'Exit', 1098 | mnemonic: 'blk_end', 1099 | before: null, 1100 | stackChange: 0, 1101 | ops: [], 1102 | operands: 0, 1103 | check: true, 1104 | }; 1105 | 1106 | METADATA[Op.ToBoolean] = { 1107 | name: 'ToBoolean', 1108 | mnemonic: 'anytobool', 1109 | before: null, 1110 | stackChange: 0, 1111 | ops: [], 1112 | operands: 0, 1113 | check: true, 1114 | }; 1115 | 1116 | METADATA[Op.EnterList] = { 1117 | name: 'EnterList', 1118 | mnemonic: 'list_start', 1119 | before: null, 1120 | stackChange: 0, 1121 | ops: [ 1122 | { 1123 | name: 'address', 1124 | type: 'u32', 1125 | }, 1126 | ], 1127 | operands: 1, 1128 | check: true, 1129 | }; 1130 | 1131 | METADATA[Op.ExitList] = { 1132 | name: 'ExitList', 1133 | mnemonic: 'list_end', 1134 | before: null, 1135 | stackChange: 0, 1136 | ops: [], 1137 | operands: 0, 1138 | check: true, 1139 | }; 1140 | 1141 | METADATA[Op.PutIterator] = { 1142 | name: 'PutIterator', 1143 | mnemonic: 'toiter', 1144 | before: null, 1145 | stackChange: 0, 1146 | ops: [], 1147 | operands: 0, 1148 | check: true, 1149 | }; 1150 | 1151 | METADATA[Op.Iterate] = { 1152 | name: 'Iterate', 1153 | mnemonic: 'iter', 1154 | before: null, 1155 | stackChange: 0, 1156 | ops: [ 1157 | { 1158 | name: 'end', 1159 | type: 'u32', 1160 | }, 1161 | ], 1162 | operands: 1, 1163 | check: false, 1164 | }; 1165 | 1166 | METADATA[Op.Main] = { 1167 | name: 'Main', 1168 | mnemonic: 'main', 1169 | before: null, 1170 | stackChange: -2, 1171 | ops: [ 1172 | { 1173 | name: 'state', 1174 | type: 'register', 1175 | }, 1176 | ], 1177 | operands: 1, 1178 | check: true, 1179 | }; 1180 | 1181 | METADATA[Op.IsComponent] = { 1182 | name: 'IsComponent', 1183 | mnemonic: 'iscomponent', 1184 | before: null, 1185 | stackChange: 0, 1186 | ops: [], 1187 | operands: 0, 1188 | check: true, 1189 | }; 1190 | 1191 | METADATA[Op.ContentType] = { 1192 | name: 'ContentType', 1193 | mnemonic: 'ctload', 1194 | before: null, 1195 | stackChange: 1, 1196 | ops: [], 1197 | operands: 0, 1198 | check: true, 1199 | }; 1200 | 1201 | METADATA[Op.CurryComponent] = { 1202 | name: 'CurryComponent', 1203 | mnemonic: 'curry', 1204 | before: null, 1205 | stackChange: null, 1206 | ops: [ 1207 | { 1208 | name: 'templateMeta', 1209 | type: 'meta', 1210 | }, 1211 | ], 1212 | operands: 1, 1213 | check: true, 1214 | }; 1215 | 1216 | METADATA[Op.PushComponentDefinition] = { 1217 | name: 'PushComponentDefinition', 1218 | mnemonic: 'cmload', 1219 | before: null, 1220 | stackChange: 1, 1221 | ops: [ 1222 | { 1223 | name: 'spec', 1224 | type: 'handle', 1225 | }, 1226 | ], 1227 | operands: 1, 1228 | check: true, 1229 | }; 1230 | 1231 | METADATA[Op.PushDynamicComponentInstance] = { 1232 | name: 'PushDynamicComponentInstance', 1233 | mnemonic: 'dciload', 1234 | before: null, 1235 | stackChange: 0, 1236 | ops: [], 1237 | operands: 0, 1238 | check: true, 1239 | }; 1240 | 1241 | METADATA[Op.PushCurriedComponent] = { 1242 | name: 'PushCurriedComponent', 1243 | mnemonic: 'curriedload', 1244 | before: null, 1245 | stackChange: 0, 1246 | ops: [], 1247 | operands: 0, 1248 | check: true, 1249 | }; 1250 | 1251 | METADATA[Op.ResolveDynamicComponent] = { 1252 | name: 'ResolveDynamicComponent', 1253 | mnemonic: 'cdload', 1254 | before: null, 1255 | stackChange: 0, 1256 | ops: [ 1257 | { 1258 | name: 'templateMeta', 1259 | type: 'meta', 1260 | }, 1261 | ], 1262 | operands: 1, 1263 | check: true, 1264 | }; 1265 | 1266 | METADATA[Op.PushArgs] = { 1267 | name: 'PushArgs', 1268 | mnemonic: 'argsload', 1269 | before: null, 1270 | stackChange: null, 1271 | ops: [ 1272 | { 1273 | name: 'names', 1274 | type: 'str-array', 1275 | }, 1276 | { 1277 | name: 'positionalCount', 1278 | type: 'u32', 1279 | }, 1280 | { 1281 | name: 'synthetic', 1282 | type: 'bool', 1283 | }, 1284 | ], 1285 | operands: 3, 1286 | check: true, 1287 | }; 1288 | 1289 | METADATA[Op.PushEmptyArgs] = { 1290 | name: 'PushEmptyArgs', 1291 | mnemonic: 'emptyargsload', 1292 | before: null, 1293 | stackChange: 1, 1294 | ops: [], 1295 | operands: 0, 1296 | check: true, 1297 | }; 1298 | 1299 | METADATA[Op.PopArgs] = { 1300 | name: 'PopArgs', 1301 | mnemonic: 'argspop', 1302 | before: null, 1303 | stackChange: null, 1304 | ops: [], 1305 | operands: 0, 1306 | check: true, 1307 | }; 1308 | 1309 | METADATA[Op.PrepareArgs] = { 1310 | name: 'PrepareArgs', 1311 | mnemonic: 'argsprep', 1312 | before: null, 1313 | stackChange: 0, 1314 | ops: [ 1315 | { 1316 | name: 'state', 1317 | type: 'register', 1318 | }, 1319 | ], 1320 | operands: 1, 1321 | check: false, 1322 | }; 1323 | 1324 | METADATA[Op.CaptureArgs] = { 1325 | name: 'CaptureArgs', 1326 | mnemonic: 'argscapture', 1327 | before: null, 1328 | stackChange: 0, 1329 | ops: [], 1330 | operands: 0, 1331 | check: true, 1332 | }; 1333 | 1334 | METADATA[Op.CreateComponent] = { 1335 | name: 'CreateComponent', 1336 | mnemonic: 'comp_create', 1337 | before: null, 1338 | stackChange: 0, 1339 | ops: [ 1340 | { 1341 | name: 'flags', 1342 | type: 'u32', 1343 | }, 1344 | { 1345 | name: 'state', 1346 | type: 'register', 1347 | }, 1348 | ], 1349 | operands: 2, 1350 | check: true, 1351 | }; 1352 | 1353 | METADATA[Op.RegisterComponentDestructor] = { 1354 | name: 'RegisterComponentDestructor', 1355 | mnemonic: 'comp_dest', 1356 | before: null, 1357 | stackChange: 0, 1358 | ops: [ 1359 | { 1360 | name: 'state', 1361 | type: 'register', 1362 | }, 1363 | ], 1364 | operands: 1, 1365 | check: true, 1366 | }; 1367 | 1368 | METADATA[Op.PutComponentOperations] = { 1369 | name: 'PutComponentOperations', 1370 | mnemonic: 'comp_elops', 1371 | before: null, 1372 | stackChange: 0, 1373 | ops: [], 1374 | operands: 0, 1375 | check: true, 1376 | }; 1377 | 1378 | METADATA[Op.GetComponentSelf] = { 1379 | name: 'GetComponentSelf', 1380 | mnemonic: 'comp_selfload', 1381 | before: null, 1382 | stackChange: 1, 1383 | ops: [ 1384 | { 1385 | name: 'state', 1386 | type: 'register', 1387 | }, 1388 | ], 1389 | operands: 1, 1390 | check: true, 1391 | }; 1392 | 1393 | METADATA[Op.GetComponentTagName] = { 1394 | name: 'GetComponentTagName', 1395 | mnemonic: 'comp_tagload', 1396 | before: null, 1397 | stackChange: 1, 1398 | ops: [ 1399 | { 1400 | name: 'state', 1401 | type: 'register', 1402 | }, 1403 | ], 1404 | operands: 1, 1405 | check: true, 1406 | }; 1407 | 1408 | METADATA[Op.GetAotComponentLayout] = { 1409 | name: 'GetAotComponentLayout', 1410 | mnemonic: 'comp_alayoutload', 1411 | before: null, 1412 | stackChange: 2, 1413 | ops: [ 1414 | { 1415 | name: 'state', 1416 | type: 'register', 1417 | }, 1418 | ], 1419 | operands: 1, 1420 | check: true, 1421 | }; 1422 | 1423 | METADATA[Op.GetJitComponentLayout] = { 1424 | name: 'GetJitComponentLayout', 1425 | mnemonic: 'comp_jlayoutload', 1426 | before: null, 1427 | stackChange: 2, 1428 | ops: [ 1429 | { 1430 | name: 'state', 1431 | type: 'register', 1432 | }, 1433 | ], 1434 | operands: 1, 1435 | check: true, 1436 | }; 1437 | 1438 | METADATA[Op.BindEvalScope] = { 1439 | name: 'BindEvalScope', 1440 | mnemonic: 'eval_scope', 1441 | before: null, 1442 | stackChange: 0, 1443 | ops: [ 1444 | { 1445 | name: 'state', 1446 | type: 'register', 1447 | }, 1448 | ], 1449 | operands: 1, 1450 | check: true, 1451 | }; 1452 | 1453 | METADATA[Op.SetupForEval] = { 1454 | name: 'SetupForEval', 1455 | mnemonic: 'eval_setup', 1456 | before: null, 1457 | stackChange: 0, 1458 | ops: [ 1459 | { 1460 | name: 'state', 1461 | type: 'register', 1462 | }, 1463 | ], 1464 | operands: 1, 1465 | check: true, 1466 | }; 1467 | 1468 | METADATA[Op.PopulateLayout] = { 1469 | name: 'PopulateLayout', 1470 | mnemonic: 'comp_layoutput', 1471 | before: null, 1472 | stackChange: -2, 1473 | ops: [ 1474 | { 1475 | name: 'state', 1476 | type: 'register', 1477 | }, 1478 | ], 1479 | operands: 1, 1480 | check: true, 1481 | }; 1482 | 1483 | METADATA[Op.InvokeComponentLayout] = { 1484 | name: 'InvokeComponentLayout', 1485 | mnemonic: 'comp_invokelayout', 1486 | before: null, 1487 | stackChange: 0, 1488 | ops: [ 1489 | { 1490 | name: 'state', 1491 | type: 'register', 1492 | }, 1493 | ], 1494 | operands: 1, 1495 | check: true, 1496 | }; 1497 | 1498 | METADATA[Op.BeginComponentTransaction] = { 1499 | name: 'BeginComponentTransaction', 1500 | mnemonic: 'comp_begin', 1501 | before: null, 1502 | stackChange: 0, 1503 | ops: [], 1504 | operands: 0, 1505 | check: true, 1506 | }; 1507 | 1508 | METADATA[Op.CommitComponentTransaction] = { 1509 | name: 'CommitComponentTransaction', 1510 | mnemonic: 'comp_commit', 1511 | before: null, 1512 | stackChange: 0, 1513 | ops: [], 1514 | operands: 0, 1515 | check: true, 1516 | }; 1517 | 1518 | METADATA[Op.DidCreateElement] = { 1519 | name: 'DidCreateElement', 1520 | mnemonic: 'comp_created', 1521 | before: null, 1522 | stackChange: 0, 1523 | ops: [ 1524 | { 1525 | name: 'state', 1526 | type: 'register', 1527 | }, 1528 | ], 1529 | operands: 1, 1530 | check: true, 1531 | }; 1532 | 1533 | METADATA[Op.DidRenderLayout] = { 1534 | name: 'DidRenderLayout', 1535 | mnemonic: 'comp_rendered', 1536 | before: null, 1537 | stackChange: 0, 1538 | ops: [ 1539 | { 1540 | name: 'state', 1541 | type: 'register', 1542 | }, 1543 | ], 1544 | operands: 1, 1545 | check: true, 1546 | }; 1547 | 1548 | METADATA[Op.InvokePartial] = { 1549 | name: 'InvokePartial', 1550 | mnemonic: 'invokepartial', 1551 | before: null, 1552 | stackChange: 1, 1553 | ops: [ 1554 | { 1555 | name: 'templateMeta', 1556 | type: 'meta', 1557 | }, 1558 | { 1559 | name: 'symbols', 1560 | type: 'str-array', 1561 | }, 1562 | { 1563 | name: 'evalInfo', 1564 | type: 'array', 1565 | }, 1566 | ], 1567 | operands: 3, 1568 | check: true, 1569 | }; 1570 | 1571 | METADATA[Op.ResolveMaybeLocal] = { 1572 | name: 'ResolveMaybeLocal', 1573 | mnemonic: 'eval_varload', 1574 | before: null, 1575 | stackChange: 1, 1576 | ops: [ 1577 | { 1578 | name: 'local', 1579 | type: 'str', 1580 | }, 1581 | ], 1582 | operands: 1, 1583 | check: true, 1584 | }; 1585 | 1586 | METADATA[Op.Debugger] = { 1587 | name: 'Debugger', 1588 | mnemonic: 'debugger', 1589 | before: null, 1590 | stackChange: 0, 1591 | ops: [ 1592 | { 1593 | name: 'symbols', 1594 | type: 'str-array', 1595 | }, 1596 | { 1597 | name: 'evalInfo', 1598 | type: 'array', 1599 | }, 1600 | ], 1601 | operands: 2, 1602 | check: true, 1603 | }; 1604 | -------------------------------------------------------------------------------- /src/ui/components/Visualizer/component.ts: -------------------------------------------------------------------------------- 1 | import Component, { tracked } from "@glimmer/component"; 2 | 3 | import FileSystem from "../../../utils/file-system"; 4 | import { inspect } from "./-utils/inspect-program"; 5 | 6 | export default class Visualizer extends Component { 7 | args: { 8 | fs: FileSystem 9 | }; 10 | 11 | @tracked 12 | get compilation() { 13 | let { fs } = this.args; 14 | 15 | let resolutionMap = fs.toResolutionMap(); 16 | 17 | return inspect(resolutionMap); 18 | } 19 | } -------------------------------------------------------------------------------- /src/ui/components/Visualizer/hex/helper.ts: -------------------------------------------------------------------------------- 1 | export function toHex(value, byteLength = 1) { 2 | let maxSize = Math.pow(2, byteLength * 8); 3 | 4 | if (value > maxSize) { 5 | throw new Error(`Cannot display value ${value} with ${byteLength} byte(s)`); 6 | } 7 | 8 | let pad = "0".repeat(byteLength * 2); 9 | 10 | return `${pad}${value.toString(16)}`.substr(byteLength * -2); 11 | } 12 | 13 | export default function(params) { 14 | return `0x${toHex(params[0], 2)}`; 15 | } -------------------------------------------------------------------------------- /src/ui/components/Visualizer/template.hbs: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{#each compilation.opcodes key="@index" as |op|}} 4 |
5 | {{hex op.opcode}} 6 | {{op.name}} 7 | {{#if op.operands}} 8 |
9 | {{#each op.operands key="@index" as |operand|}} 10 |
11 | {{#if operand.name}} 12 | 13 | {{operand.name}}: 14 | 15 | {{/if}} 16 | 17 | {{hex operand.rawValue}} 18 | 19 | 20 | {{operand.value}} 21 | 22 |
23 | {{/each}} 24 |
25 | {{/if}} 26 |
27 | {{/each}} 28 |
29 |
30 |
    Size  {{compilation.byteLength}} Bytes
31 |
32 |
33 |
{{compilation.hexdump}}
34 |
35 |
-------------------------------------------------------------------------------- /src/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Glimmer Playground 7 | 8 | 9 | 10 | 11 | {{content-for "head"}} 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 |
21 | 22 | 27 | 28 | 29 | 32 | 33 | {{content-for "body-footer"}} 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/ui/styles/animations.scss: -------------------------------------------------------------------------------- 1 | @keyframes fadeIn { 2 | from { opacity: 0; } 3 | to { opacity: 1; } 4 | } 5 | 6 | @keyframes fadeOut { 7 | from { opacity: 1; } 8 | to { opacity: 0; } 9 | } 10 | 11 | .animation-fade-in { 12 | animation: fadeIn 200ms ease-out; 13 | animation-fill-mode: forwards; 14 | } 15 | 16 | .animation-fade-out { 17 | animation: fadeOut 200ms ease-out; 18 | } 19 | 20 | /* Generated with Bounce.js. Edit at https://goo.gl/5kqqBp */ 21 | 22 | .animation-bounce-in { 23 | animation: bounce-in 1000ms linear both; 24 | } 25 | 26 | @keyframes bounce-in { 27 | 0% { transform: matrix3d(0.25, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 28 | 3.4% { transform: matrix3d(0.555, 0, 0, 0, 0, 0.555, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 29 | 6.81% { transform: matrix3d(0.92, 0, 0, 0, 0, 0.92, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 30 | 10.21% { transform: matrix3d(1.17, 0, 0, 0, 0, 1.17, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 31 | 13.61% { transform: matrix3d(1.249, 0, 0, 0, 0, 1.249, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 32 | 17.52% { transform: matrix3d(1.18, 0, 0, 0, 0, 1.18, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 33 | 21.32% { transform: matrix3d(1.052, 0, 0, 0, 0, 1.052, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 34 | 25.23% { transform: matrix3d(0.953, 0, 0, 0, 0, 0.953, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 35 | 29.03% { transform: matrix3d(0.923, 0, 0, 0, 0, 0.923, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 36 | 36.74% { transform: matrix3d(0.984, 0, 0, 0, 0, 0.984, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 37 | 44.44% { transform: matrix3d(1.024, 0, 0, 0, 0, 1.024, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 38 | 59.86% { transform: matrix3d(0.993, 0, 0, 0, 0, 0.993, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 39 | 75.28% { transform: matrix3d(1.002, 0, 0, 0, 0, 1.002, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 40 | 90.69% { transform: matrix3d(0.999, 0, 0, 0, 0, 0.999, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 41 | 100% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } 42 | } -------------------------------------------------------------------------------- /src/ui/styles/app.scss: -------------------------------------------------------------------------------- 1 | @import "./solarized"; 2 | @import "./variables"; 3 | @import "./z-index"; 4 | @import "./animations"; 5 | @import "./responsive"; 6 | @import "./typography"; 7 | 8 | @import "./components/glimmer-repl"; 9 | @import "./components/editable-title"; 10 | @import "./components/glimmer-sandbox"; 11 | @import "./components/glimmer-visualizer"; 12 | @import "./components/overlay-ui"; 13 | @import "./components/share"; 14 | 15 | @import "./ui/badge-success"; 16 | @import "./ui/button"; 17 | @import "./ui/button-confirm"; 18 | @import "./ui/button-remove"; 19 | @import "./ui/header"; 20 | 21 | * { 22 | box-sizing: border-box; 23 | } 24 | 25 | body, html { 26 | @include system-font; 27 | 28 | margin: 0; 29 | padding: 0; 30 | height: 100%; 31 | 32 | background-color: $base03; 33 | } 34 | -------------------------------------------------------------------------------- /src/ui/styles/components/editable-title.scss: -------------------------------------------------------------------------------- 1 | editable-title { 2 | > span, > input { 3 | display: inline-block; 4 | padding: 2px; 5 | margin: 0; 6 | color: $blue; 7 | } 8 | 9 | &:hover > span { 10 | background-color: $base01; 11 | } 12 | 13 | > input { 14 | border: 0; 15 | background-color: $base02; 16 | color: $blue; 17 | font-size: 1.2rem; 18 | @include monospace; 19 | outline: none; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ui/styles/components/glimmer-repl.scss: -------------------------------------------------------------------------------- 1 | glimmer-repl { 2 | .loading { 3 | position: absolute; 4 | width: 100%; 5 | height: 100%; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | } 10 | 11 | .pane { 12 | z-index: z('pane'); 13 | 14 | @include desktop { 15 | position: absolute; 16 | top: 0; 17 | right: 0; 18 | bottom: 0; 19 | left: 0; 20 | 21 | overflow: scroll; 22 | } 23 | } 24 | 25 | .vm { 26 | @include desktop { 27 | left: 50%; 28 | } 29 | 30 | @include mobile { 31 | min-height: 100vh; 32 | } 33 | 34 | padding: 26px 32px 32px 32px; 35 | } 36 | 37 | .editor { 38 | @include desktop { 39 | right: 50%; 40 | } 41 | 42 | @include solarized-dark; 43 | } 44 | 45 | .component:first-of-type h1.component-name { 46 | border-top: none; 47 | padding-top: 8px; 48 | padding-bottom: 8px; 49 | } 50 | 51 | h1.component-name, h1.helper-name { 52 | position: relative; 53 | display: flex; 54 | align-items: center; 55 | margin: 0; 56 | border-top: 1px solid $horizonal-rule-color; 57 | padding: 6px 15px 9px 15px; 58 | color: $blue; 59 | font-weight: normal; 60 | font-size: 1.2rem; 61 | @include monospace; 62 | 63 | > .bracket { 64 | color: $base0; 65 | } 66 | 67 | button.remove { 68 | top: 2px; 69 | margin-left: 10px; 70 | } 71 | 72 | button.confirm { 73 | margin-left: 15px; 74 | } 75 | 76 | .toggle { 77 | position: absolute; 78 | top: 0; 79 | right: 0; 80 | bottom: 0; 81 | width: 30px; 82 | cursor: pointer; 83 | -webkit-user-select: none; 84 | user-select: none; 85 | 86 | &:after { 87 | position: absolute; 88 | top: calc(50% - 3px); 89 | right: 15px; 90 | content: ''; 91 | width: 0; 92 | height: 0; 93 | border-left: 5px solid transparent; 94 | border-right: 5px solid transparent; 95 | border-top: 6px solid $base00; 96 | transition: all 0.2s; 97 | } 98 | 99 | &.hidden:after { 100 | right: 15px; 101 | transform: rotate(90deg); 102 | } 103 | } 104 | } 105 | 106 | @include desktop { 107 | .file { 108 | padding-left: 32px; 109 | } 110 | } 111 | 112 | @include mobile { 113 | .file { 114 | padding-left: 4px; 115 | } 116 | } 117 | 118 | .file-name { 119 | display: flex; 120 | align-items: center; 121 | min-height: 25px; 122 | font-weight: normal; 123 | font-size: 0.8rem; 124 | margin: 0 17px 15px 27px; 125 | 126 | button.remove { 127 | margin-top: 2px; 128 | margin-left: 10px; 129 | } 130 | 131 | button.confirm { 132 | margin-left: 15px; 133 | } 134 | } 135 | 136 | .add-implementation { 137 | margin-bottom: 12px; 138 | 139 | .button { 140 | margin-left: 27px; 141 | } 142 | } 143 | 144 | .toggle-visualizer { 145 | position: fixed; 146 | right: 15px; 147 | bottom: 15px; 148 | border: none; 149 | z-index: z('toggleVisualizer'); 150 | 151 | background: transparent; 152 | font-size: 18px; 153 | 154 | cursor: pointer; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/ui/styles/components/glimmer-sandbox.scss: -------------------------------------------------------------------------------- 1 | .vm { 2 | background-color: $base3; 3 | @include solarized; 4 | 5 | .error { 6 | color: $red; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/ui/styles/components/glimmer-visualizer.scss: -------------------------------------------------------------------------------- 1 | glimmer-visualizer { 2 | position: fixed; 3 | 4 | top: 10%; 5 | right: 10%; 6 | bottom: 10%; 7 | left: 10%; 8 | 9 | padding: 17px; 10 | overflow-y: auto; 11 | -webkit-overflow-scrolling: touch; 12 | 13 | color: $base3; 14 | background: $base03; 15 | font-size: 14px; 16 | font-family: Menlo, Courier, monospace; 17 | box-shadow: rgba(black, 0.4) 0 0 35px; 18 | z-index: z('visualizer'); 19 | 20 | .decompiled { 21 | .op .opcode { 22 | color: $cyan; 23 | } 24 | 25 | .op .name { 26 | color: $base2; 27 | } 28 | 29 | .operands { 30 | padding-left: 17px; 31 | } 32 | 33 | .operand .name { 34 | color: $green; 35 | } 36 | 37 | .operand .raw-value { 38 | color: $base1; 39 | } 40 | 41 | .operand .value { 42 | color: $blue; 43 | } 44 | } 45 | 46 | .hexdump { 47 | pre { 48 | font-family: Menlo, Courier, monospace; 49 | } 50 | } 51 | 52 | .byte-length { 53 | margin: 1em 0; 54 | } 55 | 56 | .bytecode { 57 | margin-top: 17px; 58 | 59 | .opcode { 60 | color: $cyan; 61 | } 62 | 63 | .operand { 64 | color: $green; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/ui/styles/components/overlay-ui.scss: -------------------------------------------------------------------------------- 1 | .overlay { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | bottom: 0; 6 | left: 0; 7 | background: rgba(0,0,0, 0.3); 8 | z-index: z('overlay'); 9 | } 10 | 11 | .overlay-window { 12 | overflow: auto; 13 | position: relative; 14 | max-height: 100%; 15 | max-width: $overlay-window-max-width; 16 | margin: 0 auto; 17 | border-radius: $overlay-border-radius; 18 | color: #111; 19 | background: #fff; 20 | box-shadow: 0 1px 10px rgba(0, 0, 0, 0.3), 0 20px 55px -8px rgba(0, 0, 0, 0.45); 21 | -webkit-overflow-scrolling: touch; 22 | } 23 | 24 | #overlay-title { 25 | outline: none; 26 | } 27 | 28 | .overlay-close { 29 | position: absolute; 30 | top: 0; 31 | right: 0; 32 | border: none; 33 | padding: 20px 20px 5px 5px; 34 | background: transparent; 35 | cursor: pointer; 36 | 37 | div { 38 | position: relative; 39 | width: 32px; 40 | height: 32px; 41 | border-radius: 50%; 42 | background-color: rgba(255,255,255, 0.8); 43 | 44 | &:before, &:after { 45 | content: ''; 46 | position: absolute; 47 | top: 5px; 48 | left: 15px; 49 | bottom: 5px; 50 | width: 2px; 51 | background: #222; 52 | } 53 | 54 | &:before { transform: rotate(45deg); } 55 | &:after { transform: rotate(-45deg); } 56 | } 57 | } 58 | 59 | @media only screen and (max-width: $overlay-window-max-width) { 60 | .overlay { 61 | padding: 8px; 62 | } 63 | 64 | #overlay-title { 65 | margin-top: 0.5rem; 66 | margin-bottom: 2rem; 67 | font-size: 27px; 68 | } 69 | } 70 | 71 | @media only screen and (min-width: $overlay-window-max-width) { 72 | .overlay { 73 | padding: 25px; 74 | } 75 | 76 | #overlay-title { 77 | margin-bottom: 3rem; 78 | font-size: 34px; 79 | } 80 | } -------------------------------------------------------------------------------- /src/ui/styles/components/share.scss: -------------------------------------------------------------------------------- 1 | .share-overlay { 2 | .label { 3 | display: block; 4 | margin-bottom: 0.5rem; 5 | color: #222; 6 | font-size: 14px; 7 | font-weight: bold; 8 | } 9 | 10 | .field { 11 | position: relative; 12 | display: flex; 13 | align-items: end; 14 | margin-bottom: 35px; 15 | 16 | p { 17 | margin: 0 25px 0 0; 18 | font-size: 16px; 19 | line-height: 130%; 20 | } 21 | 22 | kbd { 23 | color: $orange; 24 | border-radius: 3px; 25 | padding: 2px 4px; 26 | background: #eee; 27 | } 28 | 29 | input { 30 | flex-grow: 1; 31 | margin-right: 25px; 32 | border: none; 33 | border-bottom: 1px solid #ccc; 34 | padding: 0 0 0.5rem 0; 35 | @include monospace; 36 | font-size: 0.9rem; 37 | background: transparent; 38 | 39 | &:focus { 40 | outline: none; 41 | border-bottom-color: #333; 42 | } 43 | } 44 | 45 | .button { 46 | flex-basis: 170px; 47 | flex-shrink: 0; 48 | padding-top: 0.4rem; 49 | padding-bottom: 0.4rem; 50 | font-size: 0.8rem; 51 | background: $blue; 52 | } 53 | 54 | .success-badge { 55 | position: absolute; 56 | right: -27px; 57 | bottom: calc(50% - 12px); 58 | } 59 | } 60 | } 61 | 62 | .download-zip-instructions { 63 | position: relative; 64 | padding-left: 40px; 65 | 66 | svg { 67 | position: absolute; 68 | top: 3px; 69 | left: 0; 70 | } 71 | } 72 | 73 | @media only screen and (max-width: 500px) { 74 | .share-overlay .field { 75 | flex-wrap: wrap; 76 | 77 | input { 78 | min-width: 100%; 79 | margin-bottom: 0.5rem; 80 | } 81 | 82 | .button { 83 | min-width: 100%; 84 | } 85 | 86 | p { 87 | margin-bottom: 0.5rem; 88 | } 89 | } 90 | } 91 | 92 | @media only screen and (max-width: $overlay-window-max-width) { 93 | .share-overlay .overlay-window { 94 | padding: $overlay-top-padding-mobile $overlay-side-padding-mobile; 95 | } 96 | } 97 | 98 | @media only screen and (min-width: $overlay-window-max-width) { 99 | .share-overlay .overlay-window { 100 | padding: $overlay-top-padding-desktop $overlay-side-padding-desktop; 101 | 102 | .success-badge { 103 | right: -34px; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/ui/styles/responsive.scss: -------------------------------------------------------------------------------- 1 | @mixin mobile() { 2 | @media only screen and (max-width: $breakpoint-mobile) { 3 | @content; 4 | } 5 | } 6 | 7 | @mixin desktop() { 8 | @media only screen and (min-width: $breakpoint-mobile) { 9 | @content; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ui/styles/solarized.scss: -------------------------------------------------------------------------------- 1 | /* Solarized */ 2 | $base03: #002b36; 3 | $base02: #073642; 4 | $base01: #586e75; 5 | $base00: #657b83; 6 | $base0: #839496; 7 | $base1: #93a1a1; 8 | $base2: #eee8d5; 9 | $base3: #fdf6e3; 10 | $yellow: #b58900; 11 | $orange: #cb4b16; 12 | $red: #dc322f; 13 | $magenta: #d33682; 14 | $violet: #6c71c4; 15 | $blue: #268bd2; 16 | $cyan: #2aa198; 17 | $green: #859900; 18 | 19 | @mixin rebase($rebase03, $rebase02, $rebase01, $rebase00, $rebase0, $rebase1, $rebase2, $rebase3) { 20 | background-color: $rebase03; 21 | color: $rebase0; 22 | 23 | h1, h2, h3, h4, h5, h6 { 24 | color:$rebase1; 25 | } 26 | 27 | a, a:active, a:visited { 28 | color: $rebase1; 29 | } 30 | } 31 | 32 | @mixin solarized-dark { 33 | @include rebase($base03,$base02,$base01,$base00,$base0,$base1,$base2,$base3) 34 | } 35 | 36 | @mixin solarized { 37 | @include rebase($base3,$base2,$base1,$base0,$base00,$base01,$base02,$base03) 38 | } -------------------------------------------------------------------------------- /src/ui/styles/typography.scss: -------------------------------------------------------------------------------- 1 | @mixin system-font { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 3 | } 4 | 5 | @mixin monospace { 6 | font-family: CamingoCode, Fira, "Source Code Pro", Menlo, Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace; 7 | } 8 | -------------------------------------------------------------------------------- /src/ui/styles/ui/badge-success.scss: -------------------------------------------------------------------------------- 1 | .success-badge { 2 | width: 24px; 3 | height: 24px; 4 | border: 2px solid green; 5 | border-radius: 50%; 6 | background: #fff; 7 | 8 | &::before, &::after { 9 | content: ''; 10 | position: absolute; 11 | height: 2px; 12 | background: green; 13 | } 14 | 15 | &::before { 16 | top: 9px; 17 | left: 6px; 18 | width: 11px; 19 | transform: rotate(-45deg); 20 | } 21 | 22 | &::after { 23 | top: 11px; 24 | left: 4px; 25 | width: 5px; 26 | transform: rotate(45deg); 27 | } 28 | } -------------------------------------------------------------------------------- /src/ui/styles/ui/button-confirm.scss: -------------------------------------------------------------------------------- 1 | button.confirm { 2 | position: relative; 3 | border: 0; 4 | padding: 0; 5 | color: #fff; 6 | font-size: 0.7rem; 7 | @include monospace; 8 | cursor: pointer; 9 | background: transparent; 10 | 11 | &.yes { 12 | color: $red; 13 | } 14 | } -------------------------------------------------------------------------------- /src/ui/styles/ui/button-remove.scss: -------------------------------------------------------------------------------- 1 | button.remove { 2 | position: relative; 3 | display: inline-block; 4 | width: 18px; 5 | height: 18px; 6 | margin: 0; 7 | border: 0; 8 | border-radius: 50%; 9 | background-color: transparent; 10 | 11 | &:before, &:after { 12 | position: absolute; 13 | top: 3px; 14 | left: 8px; 15 | bottom: 3px; 16 | content: ''; 17 | width: 2px; 18 | background: $base00; 19 | } 20 | 21 | &:before { 22 | transform: rotate(45deg); 23 | } 24 | 25 | &:after { 26 | transform: rotate(-45deg); 27 | } 28 | 29 | &:hover { 30 | cursor: pointer; 31 | background-color: $red; 32 | 33 | &:before, &:after { 34 | background: #fff; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/ui/styles/ui/button.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | position: relative; 3 | padding: 0.35rem 1rem; 4 | border: none; 5 | border-radius: 5px; 6 | font-size: 0.8rem; 7 | font-weight: bold; 8 | color: #fff; 9 | background-color: rgba(255,255,255, 0.1); 10 | cursor: pointer; 11 | 12 | &[disabled] { 13 | cursor: default; 14 | } 15 | } -------------------------------------------------------------------------------- /src/ui/styles/ui/header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | display: flex; 3 | align-items: center; 4 | flex-wrap: wrap; 5 | min-height: $header-height; 6 | border-bottom: 1px solid $horizonal-rule-color; 7 | background-color: $base03; 8 | 9 | .button { 10 | flex-grow: 1; 11 | margin: 0 10px 10px 0; 12 | padding-top: 0.5rem; 13 | padding-bottom: 0.5rem; 14 | } 15 | 16 | .button:first-of-type { 17 | margin-left: 15px; 18 | } 19 | 20 | .button:last-of-type { 21 | margin-right: 15px; 22 | } 23 | } 24 | 25 | .editor-actions { 26 | min-width: 100%; 27 | display: flex; 28 | order: 1; 29 | justify-content: flex-end; 30 | } 31 | 32 | .glimmerjs-com { 33 | display: flex; 34 | height: 100%; 35 | margin-left: 15px; 36 | line-height: #{$header-height - 2px}; 37 | 38 | a { 39 | margin-right: 15px; 40 | color: #bbb; 41 | font-size: 0.9rem; 42 | font-weight: bold; 43 | text-decoration: none; 44 | } 45 | 46 | .logo { 47 | padding-left: 26px; 48 | background-image: url(images/logo.svg); 49 | background-position: left; 50 | background-repeat: no-repeat; 51 | background-size: 24px; 52 | } 53 | } 54 | 55 | @media only screen and (min-height: 700px) { 56 | .header { 57 | position: sticky; 58 | position: -webkit-sticky; 59 | top: 0; 60 | z-index: 1; 61 | } 62 | } 63 | 64 | @media only screen 65 | and (min-width : 630px) 66 | and (max-width : #{$breakpoint-mobile - 1px}) 67 | , (min-width : 1250px) { 68 | .glimmerjs-com { 69 | flex-grow: 1; 70 | justify-content: flex-end; 71 | } 72 | 73 | .header .button { 74 | margin: 0 0 0 10px; 75 | } 76 | 77 | .editor-actions { 78 | order: 0; 79 | min-width: auto; 80 | } 81 | } -------------------------------------------------------------------------------- /src/ui/styles/variables.scss: -------------------------------------------------------------------------------- 1 | $breakpoint-mobile: 1025px; 2 | $glimmer-orange: #F5835F; 3 | $horizonal-rule-color: lighten($base03, 4%); 4 | $header-height: 50px; 5 | $overlay-window-max-width: 700px; 6 | $overlay-side-padding: 30px; 7 | $overlay-side-padding-desktop: 50px; 8 | $overlay-side-padding-mobile: 30px; 9 | $overlay-top-padding: 20px; 10 | $overlay-top-padding-desktop: 20px; 11 | $overlay-top-padding-mobile: 20px; 12 | $overlay-border-radius: 8px; -------------------------------------------------------------------------------- /src/ui/styles/z-index.scss: -------------------------------------------------------------------------------- 1 | $z-layers: ( 2 | pane: 1000, 3 | visualizer: 1001, 4 | toggleVisualizer: 1002, 5 | overlay: 1003 6 | ); 7 | 8 | 9 | // https://www.sitepoint.com/better-solution-managing-z-index-sass 10 | 11 | @function map-has-nested-keys($map, $keys...) { 12 | @each $key in $keys { 13 | @if not map-has-key($map, $key) { 14 | @return false; 15 | } 16 | $map: map-get($map, $key); 17 | } 18 | 19 | @return true; 20 | } 21 | 22 | @function map-deep-get($map, $keys...) { 23 | @each $key in $keys { 24 | $map: map-get($map, $key); 25 | } 26 | 27 | @return $map; 28 | } 29 | 30 | @function z($layers...) { 31 | @if not map-has-nested-keys($z-layers, $layers...) { 32 | @warn "No layer found for `#{inspect($layers...)}` in $z-layers map. Property omitted."; 33 | } 34 | 35 | @return map-deep-get($z-layers, $layers...); 36 | } -------------------------------------------------------------------------------- /src/utils/compilers/compile-template.ts: -------------------------------------------------------------------------------- 1 | import { precompile } from '@glimmer/compiler'; 2 | import { File } from '../file-system'; 3 | 4 | export default function compileTemplate(specifier: string, source: string) { 5 | return JSON.parse(precompile(source, { 6 | meta: { specifier, source } 7 | })); 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/compilers/compile-typescript.ts: -------------------------------------------------------------------------------- 1 | import { File } from '../file-system'; 2 | 3 | export default function compileTypeScript(fileName: string, sourceText: string) { 4 | let compilerOptions = { 5 | target: ts.ScriptTarget.ES2016, 6 | module: ts.ModuleKind.CommonJS, 7 | experimentalDecorators: true 8 | }; 9 | 10 | return ts.transpileModule(sourceText, { 11 | compilerOptions, 12 | fileName 13 | }).outputText; 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/file-system.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import ResolutionMap from "./resolution-map"; 4 | 5 | const DEFAULT_TEMPLATE = ` 6 |
7 |

Welcome to the Glimmer Playground!

8 |

You have clicked the button {{this.count}} times.

9 | 10 |
11 | `.trim(); 12 | 13 | const DEFAULT_COMPONENT = ` 14 | import Component, { tracked } from '@glimmer/component'; 15 | export default class extends Component { 16 | @tracked count = 1; 17 | 18 | increment() { 19 | this.count++; 20 | } 21 | } 22 | `.trim(); 23 | 24 | export default class FileSystem { 25 | files: File[] = []; 26 | listeners = []; 27 | 28 | createFile(fileName: string, sourceText) { 29 | let file = new File(this, fileName, sourceText); 30 | this.files.push(file); 31 | this.didChange(); 32 | return file; 33 | } 34 | 35 | createFileFromJSON({ fileName, sourceText }) { 36 | return this.createFile(fileName, sourceText); 37 | } 38 | 39 | toResolutionMap() { 40 | return new ResolutionMap(this.files).toJSON(); 41 | } 42 | 43 | didChange() { 44 | this.listeners.forEach(cb => cb()); 45 | } 46 | 47 | onChange(cb: () => void) { 48 | this.listeners.push(cb); 49 | } 50 | } 51 | 52 | export class File { 53 | fs: FileSystem; 54 | fileName: string; 55 | sourceText: string; 56 | 57 | constructor(fs: FileSystem, fileName: string, sourceText = '') { 58 | this.fs = fs; 59 | this.fileName = fileName; 60 | this.sourceText = sourceText; 61 | } 62 | 63 | get language() { 64 | let { fileName } = this; 65 | let ext = fileName.substr(fileName.lastIndexOf('.')); 66 | return ext === '.ts' ? 'typescript' : 'handlebars'; 67 | } 68 | 69 | didChange() { 70 | this.fs.didChange(); 71 | } 72 | 73 | toJSON() { 74 | let { fileName, sourceText } = this; 75 | return { fileName, sourceText }; 76 | } 77 | 78 | remove() { 79 | let { files } = this.fs; 80 | files.splice(files.indexOf(this), 1); 81 | this.didChange(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/utils/keys.ts: -------------------------------------------------------------------------------- 1 | const ENTER = 13; 2 | const ESCAPE = 27; 3 | const TAB = 9; 4 | 5 | export { ENTER, ESCAPE, TAB }; -------------------------------------------------------------------------------- /src/utils/monaco/themes/solarized-dark.ts: -------------------------------------------------------------------------------- 1 | const base03 = '002b36'; 2 | const base02 = '073642'; 3 | const base01 = '586e75'; 4 | const base00 = '657b83'; 5 | const base0 = '839496'; 6 | const base1 = '93a1a1'; 7 | const base2 = 'eee8d5'; 8 | const base3 = 'fdf6e3'; 9 | const yellow = 'b58900'; 10 | const orange = 'cb4b16'; 11 | const red = 'dc322f'; 12 | const magenta = 'd33682'; 13 | const violet = '6c71c4'; 14 | const blue = '268bd2'; 15 | const cyan = '2aa198'; 16 | const green = '859900'; 17 | 18 | const SolarizedTheme: monaco.editor.IStandaloneThemeData = { 19 | base: 'vs-dark', 20 | inherit: true, 21 | colors: { 22 | 'editor.foreground': `#${base3}`, 23 | 'editor.background': `#${base03}`, 24 | 'editor.lineHighlightBackground': `#${base02}` 25 | }, 26 | rules: [{ 27 | token: '', 28 | foreground: base3 29 | }, { 30 | token: 'comment', 31 | foreground: base00, 32 | background: green, 33 | fontStyle: 'italic' 34 | }, { 35 | token: 'string', 36 | foreground: cyan 37 | }, { 38 | token: 'string.regexp', 39 | foreground: magenta 40 | }, { 41 | token: 'constant.numeric', 42 | foreground: magenta 43 | }, { 44 | token: 'variable', 45 | foreground: blue 46 | }, { 47 | token: 'keyword', 48 | foreground: green 49 | }, { 50 | token: 'storage', 51 | foreground: base1, 52 | fontStyle: 'bold' 53 | }, { 54 | token: 'entity.name.class', 55 | foreground: orange 56 | }, { 57 | token: 'entity.name.type', 58 | foreground: orange 59 | }, { 60 | token: 'entity.name.function', 61 | foreground: blue 62 | }, { 63 | token: 'punctuation.definition.variable', 64 | foreground: green 65 | }, { 66 | token: 'punctuation.section.embedded.begin', 67 | foreground: red 68 | }, { 69 | token: 'punctuation.section.embedded.end', 70 | foreground: red 71 | }, { 72 | token: 'constant.language', 73 | foreground: yellow 74 | }, { 75 | token: 'meta.preprocessor', 76 | foreground: yellow 77 | }, { 78 | token: 'support.function.construct', 79 | foreground: orange 80 | }, { 81 | token: 'keyword.other.new', 82 | foreground: orange 83 | }, { 84 | token: 'constant.character', 85 | foreground: orange 86 | }, { 87 | token: 'constant.other', 88 | foreground: orange 89 | }, { 90 | token: 'entity.other.inherited-class', 91 | foreground: violet 92 | }, { 93 | token: 'variable.parameter' 94 | }, { 95 | token: 'entity.name.tag', 96 | foreground: blue 97 | }, { 98 | token: 'variable.parameter.handlebars', 99 | foreground: base2 100 | }, { 101 | token: 'identifier', 102 | foreground: blue 103 | }, { 104 | token: 'attribute.name', 105 | foreground: base0 106 | }, { 107 | token: 'attribute.value', 108 | foreground: cyan 109 | }, { 110 | token: 'delimiter.html', 111 | foreground: base01 112 | }, { 113 | token: 'tag', 114 | foreground: blue 115 | }] 116 | } 117 | 118 | export default SolarizedTheme; 119 | -------------------------------------------------------------------------------- /src/utils/resolution-map.ts: -------------------------------------------------------------------------------- 1 | import { File } from './file-system'; 2 | import * as GlimmerComponent from '@glimmer/component'; 3 | import compileTypeScript from './compilers/compile-typescript'; 4 | import compileTemplate from './compilers/compile-template'; 5 | import { specifierForTemplate, specifierForComponent, specifierForHelper } from "./specifiers"; 6 | 7 | export default class ResolutionMap { 8 | files: File[]; 9 | 10 | constructor(files: File[]) { 11 | this.files = files; 12 | } 13 | 14 | toJSON() { 15 | let map = {}; 16 | 17 | for (let { fileName, language, sourceText } of this.files) { 18 | let specifier; 19 | 20 | switch (language) { 21 | case 'handlebars': 22 | specifier = specifierForTemplate(fileName); 23 | map[specifier] = compileTemplate(specifier, sourceText); 24 | break; 25 | case 'typescript': 26 | if (fileName.indexOf('helper.ts') > -1) { 27 | specifier = specifierForHelper(fileName); 28 | } else { 29 | specifier = specifierForComponent(fileName); 30 | } 31 | let code = compileTypeScript(fileName, sourceText); 32 | let mod = evalTypeScript(code); 33 | if (!mod || !mod.default) { throw new Error(`${fileName} did not export a default export.`); } 34 | map[specifier] = mod.default; 35 | break; 36 | default: 37 | throw new Error(`Unknown language ${language}`); 38 | } 39 | } 40 | 41 | return map; 42 | } 43 | } 44 | 45 | let packages = { 46 | '@glimmer/component': GlimmerComponent 47 | }; 48 | 49 | const requireSym = Symbol() 50 | 51 | function evalTypeScript(source: string) { 52 | let require = function(pkgName) { 53 | return pkgName && packages[pkgName]; 54 | }; 55 | // Required to prevent Rollup from removing the require function, as it is only 56 | // invoked inside the eval. 57 | window[requireSym] = require; 58 | 59 | let tsExports = eval(`(function(exports) { ${source}; return exports; })({})`); 60 | 61 | return tsExports; 62 | } 63 | -------------------------------------------------------------------------------- /src/utils/specifiers.ts: -------------------------------------------------------------------------------- 1 | export function specifierForTemplate(fileName: string) { 2 | let segments = fileName.split('/') 3 | let name = segments[segments.length-2]; 4 | return `template:/glimmer-repl/components/${name}`; 5 | } 6 | 7 | export function specifierForComponent(fileName: string) { 8 | let segments = fileName.split('/') 9 | let name = segments[segments.length-2]; 10 | return `component:/glimmer-repl/components/${name}`; 11 | } 12 | 13 | export function specifierForHelper(fileName: string) { 14 | let segments = fileName.split('/') 15 | let name = segments[segments.length-2]; 16 | return `helper:/glimmer-repl/components/${name}`; 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es2015", 5 | "inlineSourceMap": true, 6 | "inlineSources": true, 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "allowSyntheticDefaultImports": true 10 | }, 11 | "exclude": [ 12 | "node_modules", 13 | "tmp", 14 | "dist" 15 | ] 16 | } 17 | --------------------------------------------------------------------------------