-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds project save/load functionality to :play #37
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -150,10 +150,12 @@ export class PlayNewPenButton extends LitElement { | |
<div class="container"> | ||
<button | ||
class="new-pen" | ||
@click=${() => | ||
@click=${() => { | ||
this.dispatchEvent(Bubble<string>('new-project', '')) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would you mind updating the HTMLElementEventMap at the top of the file? same thing for other events in the patch. |
||
this.dispatchEvent( | ||
Bubble<string>('edit-src', this.srcByLabel?.Default || '') | ||
)} | ||
) | ||
}} | ||
title="New pen" | ||
> | ||
<play-icon | ||
|
@@ -180,6 +182,7 @@ export class PlayNewPenButton extends LitElement { | |
<play-list-item | ||
label=${label} | ||
@click=${() => { | ||
this.dispatchEvent(Bubble<string>('new-project', label)) | ||
this.dispatchEvent(Bubble<string>('edit-src', src)) | ||
}} | ||
>${label}</play-list-item | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,23 +6,31 @@ import { | |
type TemplateResult | ||
} from 'lit' | ||
import {customElement, property, query} from 'lit/decorators.js' | ||
import {ifDefined} from 'lit/directives/if-defined.js' | ||
import {ProjectManager} from '../storage/project-manager.js' | ||
import {defaultSettings} from '../storage/settings-save.js' | ||
import {Bubble} from '../utils/bubble.js' | ||
import {cssReset} from '../utils/css-reset.js' | ||
import {openURL} from '../utils/open-url.js' | ||
import {type AssetsState, emptyAssetsState} from './play-assets/play-assets.js' | ||
import type {PlayAssetsDialog} from './play-assets-dialog.js' | ||
import type {PlayExportDialog} from './play-export-dialog.js' | ||
import type {PlayProjectSaveDialog} from './play-project-save-dialog.js' | ||
import type {PlaySettingsDialog} from './play-settings-dialog.js' | ||
import type {PlayAssetsDialog} from './play-assets-dialog.js' | ||
import type {PlayToast} from './play-toast.js' | ||
import type {PlayProjectLoadDialog} from './play-project-load-dialog.js' | ||
|
||
import './play-assets-dialog.js' | ||
import './play-button.js' | ||
import './play-export-dialog.js' | ||
import './play-icon/play-icon.js' | ||
import './play-logo/play-logo.js' | ||
import './play-new-pen-button.js' | ||
import './play-project-button.js' | ||
import './play-project-load-dialog.js' | ||
import './play-project-save-dialog.js' | ||
import './play-resizable-text-input.js' | ||
import './play-settings-dialog.js' | ||
import './play-assets-dialog.js' | ||
import {type AssetsState, emptyAssetsState} from './play-assets/play-assets.js' | ||
|
||
declare global { | ||
interface HTMLElementEventMap { | ||
|
@@ -97,6 +105,9 @@ export class PlayPenHeader extends LitElement { | |
@property({attribute: 'sandbox-app', type: Boolean}) | ||
sandboxApp: boolean = false | ||
|
||
@property() | ||
src: string = '' | ||
|
||
@property() | ||
url: string = '' | ||
|
||
|
@@ -121,8 +132,49 @@ export class PlayPenHeader extends LitElement { | |
@query('play-export-dialog') | ||
private _export!: PlayExportDialog | ||
|
||
@query('play-project-save-dialog') | ||
private _saveDialog!: PlayProjectSaveDialog | ||
|
||
@query('play-project-load-dialog') | ||
private _loadDialog!: PlayProjectLoadDialog | ||
|
||
@query('play-settings-dialog') | ||
private _settings!: PlaySettingsDialog | ||
private _settingsDialog!: PlaySettingsDialog | ||
|
||
@query('play-toast') | ||
private _toast!: PlayToast | ||
|
||
@query('.toast-content') | ||
private _toastContent!: HTMLDivElement | ||
|
||
@property({attribute: 'project-manager', type: ProjectManager}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this type works? suggest attribute: false. |
||
projectManager!: ProjectManager | ||
Comment on lines
+150
to
+151
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would you mind moving this near the other publics properties at the top? also, can we make it optional? here's the thinking: it's ok to assert what we do inside the class but safer not to assert what's done outside. |
||
|
||
private openSaveDialog(): void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the current repo convention is to use ES private fields like #openSaveDialog() where possible. it's not possible on decorators. |
||
let projectName = this.name | ||
if (this.projectManager.getCurrentProject() === undefined) { | ||
this._saveDialog.open(this.name) | ||
return | ||
} | ||
this.saveProject(projectName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we await this? |
||
} | ||
|
||
private async saveProject(projectName: string): Promise<void> { | ||
this._saveDialog.close() | ||
try { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would you mind shrinking the try block to just what is expected to throw? |
||
await this.projectManager.saveProject(projectName, this.src) | ||
this._toastContent.textContent = 'Project saved' | ||
this._toast.open() | ||
this.dispatchEvent(Bubble<string>('edit-name', projectName)) | ||
} catch (e) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔕 suggest |
||
this._toastContent.textContent = `Error: ${e}` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this will likely print Error twice. you can check with:
|
||
this._toast.open() | ||
} | ||
} | ||
|
||
private loadProject(): void { | ||
this._loadDialog.open() | ||
} | ||
|
||
protected override render(): TemplateResult { | ||
return html` | ||
|
@@ -140,6 +192,7 @@ export class PlayPenHeader extends LitElement { | |
<play-new-pen-button | ||
size="small" | ||
.srcByLabel=${this.srcByLabel} | ||
@new-project=${() => this.projectManager.clearCurrentProject()} | ||
></play-new-pen-button | ||
><play-button | ||
appearance="bordered" | ||
|
@@ -149,14 +202,14 @@ export class PlayPenHeader extends LitElement { | |
label="Docs" | ||
@click=${() => openURL('https://developers.reddit.com/docs')} | ||
></play-button | ||
><play-button | ||
appearance="bordered" | ||
><play-project-button | ||
size="small" | ||
icon="download-outline" | ||
title="Export Pen" | ||
label="Export" | ||
@click=${() => this._export.open()} | ||
></play-button | ||
.srcByLabel=${this.srcByLabel} | ||
@open-export-dialog=${() => this._export.open()} | ||
@save-project=${() => this.openSaveDialog()} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔕 fat arrow is optional here due to automatic this binding. |
||
@load-project=${() => this.loadProject()} | ||
></play-project-button | ||
></play-button | ||
><play-button | ||
appearance="bordered" | ||
size="small" | ||
|
@@ -170,7 +223,7 @@ export class PlayPenHeader extends LitElement { | |
size="small" | ||
title=":play Settings" | ||
label="Settings" | ||
@click=${() => this._settings.open()} | ||
@click=${() => this._settingsDialog.open()} | ||
></play-button | ||
><play-button | ||
appearance="orangered" | ||
|
@@ -180,14 +233,19 @@ export class PlayPenHeader extends LitElement { | |
label="Share" | ||
@click=${() => | ||
this.dispatchEvent(Bubble<undefined>('share', undefined))} | ||
></play-button> | ||
></play-button | ||
><slot name="account-button"></slot> | ||
</div> | ||
</header> | ||
<play-assets-dialog | ||
.assetsState=${this.assetsState} | ||
?enable-local-assets=${this.enableLocalAssets} | ||
></play-assets-dialog> | ||
<play-export-dialog url=${this.url}></play-export-dialog> | ||
<play-project-save-dialog | ||
src=${ifDefined(this.src)} | ||
@save-dialog-submit=${(ev: CustomEvent<string>) => this.saveProject(ev.detail)}></play-project-save-dialog> | ||
<play-project-load-dialog .projectManager=${this.projectManager}></play-project-load-dialog> | ||
<play-settings-dialog | ||
?allow-storage=${this.allowStorage} | ||
remote-runtime-origin=${this.remoteRuntimeOrigin} | ||
|
@@ -200,6 +258,7 @@ export class PlayPenHeader extends LitElement { | |
?use-remote-runtime=${this.useRemoteRuntime} | ||
?enable-local-assets=${this.enableLocalAssets} | ||
></play-settings-dialog> | ||
<play-toast><div class="toast-content"></div></play-toast> | ||
` | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,6 +58,9 @@ import '../play-pen-header.js' | |
import '../play-preview-controls.js' | ||
import '../play-preview.js' | ||
import '../play-toast.js' | ||
import type {ProjectStorageClient} from '../../storage/project-storage-client.js' | ||
import {LocalProjectStorageClient} from '../../storage/local-project-storage-client.js' | ||
import {ProjectManager} from '../../storage/project-manager.js' | ||
|
||
declare global { | ||
interface HTMLElementTagNameMap { | ||
|
@@ -147,6 +150,9 @@ export class PlayPen extends LitElement { | |
SVG: svg | ||
} | ||
|
||
@property() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this need to be a property? |
||
projectStorageClient: ProjectStorageClient = new LocalProjectStorageClient() | ||
|
||
/** Program executable. */ | ||
@state() private _assetsFilesystemType: AssetsFilesystemType = 'virtual' | ||
@state() private _assetsState: AssetsState = emptyAssetsState() | ||
|
@@ -169,6 +175,7 @@ export class PlayPen extends LitElement { | |
@query('play-editor') private _editor!: PlayEditor | ||
@query('play-toast') private _toast!: PlayToast | ||
#bundleStore?: BundleStore | undefined | ||
_projectManager?: ProjectManager | undefined | ||
readonly #env: VirtualTypeScriptEnvironment = newTSEnv() | ||
@state() _uploaded: Promise<Empty> = Promise.resolve({}) | ||
/** Try to ensure the bundle hostname is unique. See compute-util. */ | ||
|
@@ -206,6 +213,10 @@ export class PlayPen extends LitElement { | |
// bundle is loaded. | ||
} | ||
|
||
if (!this._projectManager) { | ||
this._projectManager = new ProjectManager(this.projectStorageClient) | ||
} | ||
|
||
let pen | ||
if (this.allowURL) pen = loadPen(location) | ||
if (this.allowStorage) pen ??= loadPen(localStorage) | ||
|
@@ -235,9 +246,11 @@ export class PlayPen extends LitElement { | |
><play-pen-header | ||
name=${this._name} | ||
remote-runtime-origin=${this._remoteRuntimeOrigin} | ||
src=${ifDefined(this._src)} | ||
url=${this.#shareURL().toString()} | ||
.assetsState=${this._assetsState} | ||
.srcByLabel=${this.srcByLabel} | ||
.projectManager=${this._projectManager} | ||
?allow-storage=${this.allowStorage} | ||
?runtime-debug-logging=${this._runtimeDebugLogging} | ||
?sandbox-app=${this._sandboxApp} | ||
|
@@ -285,6 +298,7 @@ export class PlayPen extends LitElement { | |
ev: CustomEvent<AssetsVirtualFileChange> | ||
) => this._assets.onVirtualFileChange(ev.detail)} | ||
@share=${this.#onShare} | ||
><div slot="account-button"><slot name="account-button"></slot></div | ||
></play-pen-header> | ||
<main> | ||
<play-editor | ||
|
@@ -418,7 +432,7 @@ export class PlayPen extends LitElement { | |
} | ||
|
||
/** Save to LocalStorage and URL as allowed. */ | ||
#save(): void { | ||
#autosave(): void { | ||
savePen( | ||
this.allowURL ? location : undefined, | ||
this.allowStorage ? localStorage : undefined, | ||
|
@@ -428,7 +442,7 @@ export class PlayPen extends LitElement { | |
|
||
#setName(name: string, save: boolean): void { | ||
this._name = name | ||
if (save) this.#save() | ||
if (save) this.#autosave() | ||
} | ||
|
||
#setSrc(src: string, save: boolean): void { | ||
|
@@ -456,7 +470,7 @@ export class PlayPen extends LitElement { | |
webviewAssets: this._assetsState.map | ||
} | ||
) | ||
if (save) this.#save() | ||
if (save) this.#autosave() | ||
this.#upload() | ||
}, 500) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import {assert} from '@esm-bundle/chai' | ||
import {PlayProjectButton} from './play-project-button.js' | ||
|
||
test('tag is defined', () => { | ||
const el = document.createElement('play-project-button') | ||
assert.instanceOf(el, PlayProjectButton) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a pretty big bump. does this PR really add 200 KB?