diff --git a/apps/repl/app/components/compiler.gts b/apps/repl/app/components/compiler.gts index 21db71e4c..e90962935 100644 --- a/apps/repl/app/components/compiler.gts +++ b/apps/repl/app/components/compiler.gts @@ -14,7 +14,7 @@ interface Signature { export default class Compiler extends Component { @@ -22,7 +22,7 @@ export default class Compiler extends Component { @service declare editor: EditorService; get formatQP() { - return this.editor.format; + return this.editor.renderInput.format; } get formatQPParts() { diff --git a/apps/repl/app/controllers/application.ts b/apps/repl/app/controllers/application.ts index 49334bc1b..4030bec3e 100644 --- a/apps/repl/app/controllers/application.ts +++ b/apps/repl/app/controllers/application.ts @@ -72,6 +72,10 @@ export default class ApplicationController extends Controller { // or force it to not be rendered in to a shadow-dom if falsey value is passed 'shadowdom', + // When falsey ("off"), the output only compiles when the "Render" button is clicked, + // instead of on every edit. + 'autorender', + // Disable shiki highlighting on page load // this is primarily an optimization for tests 'nohighlight', diff --git a/apps/repl/app/services/editor.ts b/apps/repl/app/services/editor.ts index 26299ef7e..7e663fb1b 100644 --- a/apps/repl/app/services/editor.ts +++ b/apps/repl/app/services/editor.ts @@ -1,14 +1,22 @@ import { tracked } from '@glimmer/tracking'; +import { registerDestructor } from '@ember/destroyable'; import Service, { service } from '@ember/service'; +import { castToBoolean } from 'ember-primitives/qp'; import { link } from 'reactiveweb/link'; import { FileURIComponent } from 'limber/utils/editor-text'; import type { DemoEntry } from '../snippets'; +import type Owner from '@ember/owner'; import type RouterService from '@ember/routing/router-service'; import type { FormatQP } from '#app/languages.gts'; +interface RenderInput { + text: EditorService['text']; + format: FormatQP; +} + export default class EditorService extends Service { @service declare router: RouterService; @@ -38,6 +46,51 @@ export default class EditorService extends Service { return this.fileURIComponent.format; } + /** + * The file in the output pane while `?autorender=off` pauses compiling. + * Rendering is automatic while this is undefined. + * + * A getter on the router would compile again on each keystroke, + * because every edit writes the URL. + */ + @tracked pausedFile: RenderInput | undefined; + + constructor(owner: Owner) { + super(owner); + + this.router.on('routeDidChange', this.#syncAutoRender); + registerDestructor(this, () => this.router.off('routeDidChange', this.#syncAutoRender)); + } + + #syncAutoRender = () => { + const value = this.router.currentRoute?.queryParams?.['autorender']; + const autoRender = typeof value === 'string' ? castToBoolean(value) : true; + + // Every edit writes the URL, so a new pausedFile here would compile each keystroke. + if (autoRender === this.autoRender) return; + + this.pausedFile = autoRender ? undefined : this.#file; + }; + + get autoRender() { + return this.pausedFile === undefined; + } + + render = () => { + this.pausedFile = this.#file; + }; + + get #file(): RenderInput { + return { text: this.text, format: this.format }; + } + + /** + * The text and format for the output pane. + */ + get renderInput(): RenderInput { + return this.pausedFile ?? this.#file; + } + get nohighlight() { return (this.router.currentRoute?.queryParams ?? {}).nohighlight; } diff --git a/apps/repl/app/templates/edit/layout/controls/auto-render.gts b/apps/repl/app/templates/edit/layout/controls/auto-render.gts new file mode 100644 index 000000000..2319f1da3 --- /dev/null +++ b/apps/repl/app/templates/edit/layout/controls/auto-render.gts @@ -0,0 +1,46 @@ +import Component from '@glimmer/component'; +import { service } from '@ember/service'; + +import FaIcon from '@fortawesome/ember-fontawesome/components/fa-icon'; +import { faForwardStep, faPause, faPlay } from '@fortawesome/free-solid-svg-icons'; + +import { Button } from './button.gts'; + +import type RouterService from '@ember/routing/router-service'; +import type EditorService from 'limber/services/editor'; + +export class AutoRender extends Component { + @service declare editor: EditorService; + @service declare router: RouterService; + + toggle = () => { + const url = new URL(this.router.currentURL ?? '/edit', location.origin); + + if (this.editor.autoRender) { + url.searchParams.set('autorender', 'off'); + } else { + url.searchParams.delete('autorender'); + } + + this.router.replaceWith(url.pathname + url.search); + }; + + +} diff --git a/apps/repl/app/templates/edit/layout/controls/index.gts b/apps/repl/app/templates/edit/layout/controls/index.gts index 031103c26..d4c5e45f1 100644 --- a/apps/repl/app/templates/edit/layout/controls/index.gts +++ b/apps/repl/app/templates/edit/layout/controls/index.gts @@ -17,6 +17,7 @@ import { castToBoolean, qp, withQP } from 'ember-primitives/qp'; import currentURL from 'limber/helpers/current-url'; +import { AutoRender } from './auto-render.gts'; import { Button } from './button.gts'; import { FormatDocument } from './format-document.gts'; @@ -70,6 +71,7 @@ export const Controls: TOC<{ {{/if}} +