From 252d3c78750aee4ad73a61d0e051b7c6c7ff45d8 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 16 Jan 2025 00:27:07 -0500 Subject: [PATCH 1/7] Save last format and document in localStorage --- apps/repl/app/routes/edit.ts | 16 ++++++++++++++++ apps/repl/app/utils/editor-text.ts | 20 ++++++++------------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/apps/repl/app/routes/edit.ts b/apps/repl/app/routes/edit.ts index 1206bc7e6..6410f8698 100644 --- a/apps/repl/app/routes/edit.ts +++ b/apps/repl/app/routes/edit.ts @@ -2,6 +2,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; import { DEFAULT_SNIPPET } from 'limber/snippets'; +import { formatFrom } from 'limber/utils/messaging'; import type RouterService from '@ember/routing/router-service'; import type Transition from '@ember/routing/transition'; @@ -40,6 +41,21 @@ export default class EditRoute extends Route { 'Assuming glimdown and using the default sample snippet.' ); + /** + * Default starting doc is + * user-configurable. + * (whatever they did last) + */ + let format = localStorage.getItem('format'); + let doc = localStorage.getItem('document'); + + if (format && doc) { + transition.abort(); + this.editor.fileURIComponent.set(doc, formatFrom(format)); + + return; + } + transition.abort(); this.editor.fileURIComponent.set(DEFAULT_SNIPPET, 'glimdown'); } else if (!hasFormat) { diff --git a/apps/repl/app/utils/editor-text.ts b/apps/repl/app/utils/editor-text.ts index b5a8fcda9..81dc1a525 100644 --- a/apps/repl/app/utils/editor-text.ts +++ b/apps/repl/app/utils/editor-text.ts @@ -1,7 +1,7 @@ /* eslint-disable ember/classic-decorator-no-classic-methods */ import { isDestroyed, isDestroying, registerDestructor } from '@ember/destroyable'; import { inject as service } from '@ember/service'; -import { buildWaiter } from '@ember/test-waiters'; +import { buildWaiter, waitForPromise } from '@ember/test-waiters'; import { isTesting, macroCondition } from '@embroider/macros'; import { compressToEncodedURIComponent } from 'lz-string'; @@ -158,12 +158,14 @@ export class FileURIComponent { return base ?? window.location.toString(); }; - #updateWaiter: unknown; #frame?: number; #qps: URLSearchParams | undefined; #updateQPs = async (rawText: string, format: Format) => { + await waitForPromise(this.#_updateQPs(rawText, format)); + }; + + #_updateQPs = async (rawText: string, format: Format) => { if (this.#frame) cancelAnimationFrame(this.#frame); - if (!this.#updateWaiter) this.#updateWaiter = queueWaiter.beginAsync(); let encoded = compressToEncodedURIComponent(rawText); let qps = new URLSearchParams(location.search); @@ -172,6 +174,9 @@ export class FileURIComponent { qps.delete('t'); qps.set('format', formatFrom(format)); + localStorage.setItem('format', formatFrom(format)); + localStorage.setItem('document', rawText); + this.#qps = { ...this.#qps, ...qps, @@ -179,9 +184,6 @@ export class FileURIComponent { this.#frame = requestAnimationFrame(async () => { if (isDestroyed(this) || isDestroying(this)) { - queueWaiter.endAsync(this.#updateWaiter); - this.#updateWaiter = null; - return; } @@ -191,15 +193,9 @@ export class FileURIComponent { await new Promise((resolve) => setTimeout(resolve, DEBOUNCE_MS)); if (isDestroyed(this) || isDestroying(this)) { - queueWaiter.endAsync(this.#updateWaiter); - this.#updateWaiter = null; - return; } - queueWaiter.endAsync(this.#updateWaiter); - this.#updateWaiter = null; - // On initial load, if we call #updateQPs, // we may not have a currentURL, because the first transition has yet to complete let base = this.router.currentURL?.split('?')[0]; From 9af42ecb50c024af238b4347de6e1a07db77affa Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 16 Jan 2025 00:57:04 -0500 Subject: [PATCH 2/7] hmm --- apps/repl/app/utils/editor-text.ts | 56 ++++++++++++---------- apps/repl/tests/application/-page/index.ts | 2 +- apps/repl/tests/test-helper.ts | 3 ++ 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/apps/repl/app/utils/editor-text.ts b/apps/repl/app/utils/editor-text.ts index 81dc1a525..5a87f0fe8 100644 --- a/apps/repl/app/utils/editor-text.ts +++ b/apps/repl/app/utils/editor-text.ts @@ -161,10 +161,11 @@ export class FileURIComponent { #frame?: number; #qps: URLSearchParams | undefined; #updateQPs = async (rawText: string, format: Format) => { - await waitForPromise(this.#_updateQPs(rawText, format)); + waitForPromise(this.#_updateQPs(rawText, format)); }; #_updateQPs = async (rawText: string, format: Format) => { + console.debug('queueing QPs'); if (this.#frame) cancelAnimationFrame(this.#frame); let encoded = compressToEncodedURIComponent(rawText); @@ -182,35 +183,42 @@ export class FileURIComponent { ...qps, }; - this.#frame = requestAnimationFrame(async () => { - if (isDestroyed(this) || isDestroying(this)) { - return; - } + return new Promise((resolve) => { + this.#frame = requestAnimationFrame(async () => { + if (isDestroyed(this) || isDestroying(this)) { + resolve(); - /** - * Debounce so we are kinder on the CPU - */ - await new Promise((resolve) => setTimeout(resolve, DEBOUNCE_MS)); + return; + } - if (isDestroyed(this) || isDestroying(this)) { - return; - } + /** + * Debounce so we are kinder on the CPU + */ + await new Promise((resolve) => setTimeout(resolve, DEBOUNCE_MS)); - // On initial load, if we call #updateQPs, - // we may not have a currentURL, because the first transition has yet to complete - let base = this.router.currentURL?.split('?')[0]; + if (isDestroyed(this) || isDestroying(this)) { + resolve(); - if (macroCondition(isTesting())) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - base ??= (this.router as any) /* private API? */?.location?.path; - } else { - base ??= window.location.pathname; - } + return; + } + + // On initial load, if we call #updateQPs, + // we may not have a currentURL, because the first transition has yet to complete + let base = this.router.currentURL?.split('?')[0]; + + if (macroCondition(isTesting())) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + base ??= (this.router as any) /* private API? */?.location?.path; + } else { + base ??= window.location.pathname; + } - let next = `${base}?${qps}`; + let next = `${base}?${qps}`; - this.router.replaceWith(next); - this.#text = rawText; + this.router.replaceWith(next); + this.#text = rawText; + resolve(); + }); }); }; } diff --git a/apps/repl/tests/application/-page/index.ts b/apps/repl/tests/application/-page/index.ts index 8fd537807..1d813fe92 100644 --- a/apps/repl/tests/application/-page/index.ts +++ b/apps/repl/tests/application/-page/index.ts @@ -52,7 +52,7 @@ export class Page extends PageObject { let url = currentURL(); - assert(`Expected an URL, got ${url}`, url); + assert(`Expected an URL -- via currentURL(), got ${url}`, url); let [, search] = url.split('?'); let query = new URLSearchParams(search); diff --git a/apps/repl/tests/test-helper.ts b/apps/repl/tests/test-helper.ts index e2db62fb4..25d9e9217 100644 --- a/apps/repl/tests/test-helper.ts +++ b/apps/repl/tests/test-helper.ts @@ -22,6 +22,9 @@ Object.assign(window, { getSettledState, getPendingWaiterState, currentURL, curr setup(QUnit.assert); +QUnit.testStart(() => { + localStorage.clear(); +}); QUnit.testDone(resetOnerror); start(); From 0a0d9469cb2800f7c931ee14372cdee81db29657 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 16 Jan 2025 01:31:09 -0500 Subject: [PATCH 3/7] yay --- apps/repl/app/utils/editor-text.ts | 64 ++++++++++++++++-------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/apps/repl/app/utils/editor-text.ts b/apps/repl/app/utils/editor-text.ts index 5a87f0fe8..1f76d2d24 100644 --- a/apps/repl/app/utils/editor-text.ts +++ b/apps/repl/app/utils/editor-text.ts @@ -160,10 +160,18 @@ export class FileURIComponent { #frame?: number; #qps: URLSearchParams | undefined; + #tokens: unknown[] = []; #updateQPs = async (rawText: string, format: Format) => { - waitForPromise(this.#_updateQPs(rawText, format)); + this.#tokens.push(queueWaiter.beginAsync()); + this.#_updateQPs(rawText, format); }; + #cleanup = () => { + this.#tokens.forEach((token) => { + queueWaiter.endAsync(token); + }); + } + #_updateQPs = async (rawText: string, format: Format) => { console.debug('queueing QPs'); if (this.#frame) cancelAnimationFrame(this.#frame); @@ -183,42 +191,40 @@ export class FileURIComponent { ...qps, }; - return new Promise((resolve) => { - this.#frame = requestAnimationFrame(async () => { - if (isDestroyed(this) || isDestroying(this)) { - resolve(); + this.#frame = requestAnimationFrame(async () => { + if (isDestroyed(this) || isDestroying(this)) { + this.#cleanup(); - return; - } + return; + } - /** - * Debounce so we are kinder on the CPU - */ - await new Promise((resolve) => setTimeout(resolve, DEBOUNCE_MS)); + /** + * Debounce so we are kinder on the CPU + */ + await new Promise((resolve) => setTimeout(resolve, DEBOUNCE_MS)); - if (isDestroyed(this) || isDestroying(this)) { - resolve(); + if (isDestroyed(this) || isDestroying(this)) { + this.#cleanup(); - return; - } + return; + } - // On initial load, if we call #updateQPs, - // we may not have a currentURL, because the first transition has yet to complete - let base = this.router.currentURL?.split('?')[0]; + // On initial load, if we call #updateQPs, + // we may not have a currentURL, because the first transition has yet to complete + let base = this.router.currentURL?.split('?')[0]; - if (macroCondition(isTesting())) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - base ??= (this.router as any) /* private API? */?.location?.path; - } else { - base ??= window.location.pathname; - } + if (macroCondition(isTesting())) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + base ??= (this.router as any) /* private API? */?.location?.path; + } else { + base ??= window.location.pathname; + } - let next = `${base}?${qps}`; + let next = `${base}?${qps}`; - this.router.replaceWith(next); - this.#text = rawText; - resolve(); - }); + this.router.replaceWith(next); + this.#text = rawText; + this.#cleanup(); }); }; } From fc7818c5888de47f0b1dfea129f91130a4eba3c6 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 16 Jan 2025 01:52:58 -0500 Subject: [PATCH 4/7] Extra test for localStorage --- apps/repl/app/routes/edit.ts | 6 ++++++ apps/repl/app/utils/editor-text.ts | 12 +++++++++-- apps/repl/tests/application/-page/index.ts | 20 ++++++++++++++++--- .../tests/application/editor-format-test.gts | 16 +++++++++++++++ 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/apps/repl/app/routes/edit.ts b/apps/repl/app/routes/edit.ts index 6410f8698..d9bcdb9e7 100644 --- a/apps/repl/app/routes/edit.ts +++ b/apps/repl/app/routes/edit.ts @@ -50,12 +50,18 @@ export default class EditRoute extends Route { let doc = localStorage.getItem('document'); if (format && doc) { + console.info(`Found format and document in localStorage. Using those.`); transition.abort(); this.editor.fileURIComponent.set(doc, formatFrom(format)); return; } + console.warn( + 'URL contained no document information in the SearchParams. ' + + 'Assuming glimdown and using the default sample snippet.' + ); + transition.abort(); this.editor.fileURIComponent.set(DEFAULT_SNIPPET, 'glimdown'); } else if (!hasFormat) { diff --git a/apps/repl/app/utils/editor-text.ts b/apps/repl/app/utils/editor-text.ts index 1f76d2d24..0ae20ae41 100644 --- a/apps/repl/app/utils/editor-text.ts +++ b/apps/repl/app/utils/editor-text.ts @@ -170,10 +170,9 @@ export class FileURIComponent { this.#tokens.forEach((token) => { queueWaiter.endAsync(token); }); - } + }; #_updateQPs = async (rawText: string, format: Format) => { - console.debug('queueing QPs'); if (this.#frame) cancelAnimationFrame(this.#frame); let encoded = compressToEncodedURIComponent(rawText); @@ -183,6 +182,15 @@ export class FileURIComponent { qps.delete('t'); qps.set('format', formatFrom(format)); + if (this.#qps?.c === qps.get('c') && this.#qps?.format === qps.get('format')) { + // no-op, we should not have gotten here + // it's a mistake to have tried to have update QPs. + // Someone should debug this. + this.#cleanup(); + + return; + } + localStorage.setItem('format', formatFrom(format)); localStorage.setItem('document', rawText); diff --git a/apps/repl/tests/application/-page/index.ts b/apps/repl/tests/application/-page/index.ts index 1d813fe92..a682a472e 100644 --- a/apps/repl/tests/application/-page/index.ts +++ b/apps/repl/tests/application/-page/index.ts @@ -22,10 +22,19 @@ export class Page extends PageObject { async expectRedirectToContent( to: string, - { c, t, format }: { t?: string; c?: string; format?: string } = {} + { + c, + t, + format, + checks, + }: { t?: string; c?: string; format?: string; checks?: { aborted?: boolean } } = {} ) { let sawExpectedError = false; + let _checks = { + aborted: checks?.aborted ?? true, + }; + try { await visit(to); } catch (e) { @@ -45,7 +54,12 @@ export class Page extends PageObject { sawExpectedError = true; } - assert(`Expected to see a TransitionAborted error, but it did not occur.`, sawExpectedError); + if (_checks.aborted) { + assert( + `Expected to see a TransitionAborted error, but it did not occur. currentURL: ${currentURL()}`, + sawExpectedError + ); + } // Allow time for transitions to settle await settled(); @@ -66,7 +80,7 @@ export class Page extends PageObject { if (c) { let lzString = query.get('c'); - assert(`Missing c query param`, lzString); + assert(`Missing c query param. currentURL: ${url}`, lzString); let value = decompressFromEncodedURIComponent(lzString); diff --git a/apps/repl/tests/application/editor-format-test.gts b/apps/repl/tests/application/editor-format-test.gts index 539237bc1..9e75cd022 100644 --- a/apps/repl/tests/application/editor-format-test.gts +++ b/apps/repl/tests/application/editor-format-test.gts @@ -54,6 +54,22 @@ module('Editor > Format', function (hooks) { assert.true(page.editor.hasText('hi'), 'has passed text as well'); }); + test('after selecting text, it loads again when visiting /', async function (assert) { + await visit(`/edit?format=gjs&t=${defaultText}`); + await page.editor.load(); + + assert.strictEqual(page.editor.format, 'gjs'); + assert.true(page.editor.hasText('hi'), 'has passed text as well'); + + await page.expectRedirectToContent(`application`, { + format: 'gjs', + t: defaultText, + checks: { aborted: false }, + }); + assert.strictEqual(page.editor.format, 'gjs'); + assert.true(page.editor.hasText('hi'), 'has passed text as well'); + }); + test('can start with glimdown, and change to gjs', async function (assert) { await page.expectRedirectToContent(`/edit`, { format: 'glimdown', From 0a61fd852ed5a0d4b41f743187b64b48dcd78ea6 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 16 Jan 2025 02:03:14 -0500 Subject: [PATCH 5/7] Lints --- apps/repl/app/routes/edit.ts | 5 ----- apps/repl/app/utils/editor-text.ts | 9 ++++++++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/repl/app/routes/edit.ts b/apps/repl/app/routes/edit.ts index d9bcdb9e7..c26f197c8 100644 --- a/apps/repl/app/routes/edit.ts +++ b/apps/repl/app/routes/edit.ts @@ -36,11 +36,6 @@ export default class EditRoute extends Route { let hasFormat = qps.format !== undefined; if (!hasCode) { - console.warn( - 'URL contained no document information in the SearchParams. ' + - 'Assuming glimdown and using the default sample snippet.' - ); - /** * Default starting doc is * user-configurable. diff --git a/apps/repl/app/utils/editor-text.ts b/apps/repl/app/utils/editor-text.ts index 0ae20ae41..9596303d8 100644 --- a/apps/repl/app/utils/editor-text.ts +++ b/apps/repl/app/utils/editor-text.ts @@ -1,7 +1,7 @@ /* eslint-disable ember/classic-decorator-no-classic-methods */ import { isDestroyed, isDestroying, registerDestructor } from '@ember/destroyable'; import { inject as service } from '@ember/service'; -import { buildWaiter, waitForPromise } from '@ember/test-waiters'; +import { buildWaiter } from '@ember/test-waiters'; import { isTesting, macroCondition } from '@embroider/macros'; import { compressToEncodedURIComponent } from 'lz-string'; @@ -224,10 +224,17 @@ export class FileURIComponent { if (macroCondition(isTesting())) { // eslint-disable-next-line @typescript-eslint/no-explicit-any base ??= (this.router as any) /* private API? */?.location?.path; + // @ts-expect-error private api + base = base.split('?')[0]; } else { base ??= window.location.pathname; } + /** + * At some point this added qps + * we don't want them though, so we'll strip them + */ + let next = `${base}?${qps}`; this.router.replaceWith(next); From c772651247991d4552172a742dc38e747d72f797 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:18:37 -0500 Subject: [PATCH 6/7] Ope --- apps/repl/app/utils/editor-text.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/repl/app/utils/editor-text.ts b/apps/repl/app/utils/editor-text.ts index 9596303d8..e5fba4b42 100644 --- a/apps/repl/app/utils/editor-text.ts +++ b/apps/repl/app/utils/editor-text.ts @@ -151,6 +151,8 @@ export class FileURIComponent { if (macroCondition(isTesting())) { // eslint-disable-next-line @typescript-eslint/no-explicit-any base ??= (this.router as any) /* private API? */?.location; + // @ts-expect-error private api + base = base.split('?')[0]; } else { base ??= window.location.toString(); } @@ -182,6 +184,7 @@ export class FileURIComponent { qps.delete('t'); qps.set('format', formatFrom(format)); + // @ts-expect-error this works if (this.#qps?.c === qps.get('c') && this.#qps?.format === qps.get('format')) { // no-op, we should not have gotten here // it's a mistake to have tried to have update QPs. From 7a42ada8d46fc4e69136cd7a7f77310c5aa1f21b Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:25:37 -0500 Subject: [PATCH 7/7] oof --- apps/repl/app/utils/editor-text.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/repl/app/utils/editor-text.ts b/apps/repl/app/utils/editor-text.ts index e5fba4b42..115fe506c 100644 --- a/apps/repl/app/utils/editor-text.ts +++ b/apps/repl/app/utils/editor-text.ts @@ -151,8 +151,6 @@ export class FileURIComponent { if (macroCondition(isTesting())) { // eslint-disable-next-line @typescript-eslint/no-explicit-any base ??= (this.router as any) /* private API? */?.location; - // @ts-expect-error private api - base = base.split('?')[0]; } else { base ??= window.location.toString(); }