mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-23 05:41:20 -08:00
refactor(docs): [SITE-03] migrate desktop editor and integrate typed generation
This commit is contained in:
1 parent
a468cb2c39
commit
2f130c58f1
37 files changed
+1921
-305
No files matched your search
@@ -23,10 +23,11 @@ use their package README, architecture notes and linked editor examples;
|
||||
navigation links alone are not complete API documentation.
|
||||
|
||||
Site-owned browser behavior now lives in `browser/`; its README maps the shared
|
||||
loader, mobile entry and Run Code/language navigation. `docs/public/main.js` is a
|
||||
loader, desktop editor, mobile entry and Run Code/language navigation. `docs/public/main.js` is a
|
||||
generated input to VitePress. Run root `yarn build:site-assets` after changes, or
|
||||
`yarn check:site-assets` for drift. The existing desktop common.js UI remains a
|
||||
separate source pending SITE-03 migration; no player package APIs move here.
|
||||
`yarn check:site-assets` for drift. Desktop common.js and bootstrap.js are generated
|
||||
from typed modules, with Monaco models/emit, Run ordering, file imports, preferences
|
||||
and page cleanup separated by responsibility. No player package APIs move here.
|
||||
|
||||
## Related tooling
|
||||
|
||||
@@ -87,7 +88,7 @@ Generated `main.js` installs one document click listener under
|
||||
editor and recognizes localhost, 127.0.0.1 and IPv6 loopback. First-language
|
||||
navigation preserves existing English counterparts and explicit later choices;
|
||||
unavailable storage keeps the requested page. See `browser/README.md` for the
|
||||
current rules and verified scope. Full VitePress lifecycle remains SITE-03/05.
|
||||
current rules and verified scope. Full site/search acceptance remains SITE-05.
|
||||
|
||||
## Delivery and remaining acceptance
|
||||
|
||||
|
||||
@@ -11,13 +11,24 @@ Node 24.21.0 / Yarn 1.22.22 toolchain from the repository root.
|
||||
| `navigation.ts` | Run Code DOM delegation, URL encoding, loopback targets and first-language navigation |
|
||||
| `../../../scripts/build-site-assets.mjs` | Deterministic ES2020 bundles, available English routes and read-only drift check |
|
||||
|
||||
Generated outputs are `docs/assets/js/loader.js`, `docs/assets/js/mobile.js` and
|
||||
this workspace's `docs/public/main.js`. Do not hand-edit them. `docs/index.html`
|
||||
loads the desktop bridge before the old core/Monaco/common bootstrap. Mobile
|
||||
Generated outputs are `docs/assets/js/bootstrap.js`, `common.js`, `loader.js`,
|
||||
`mobile.js` and this workspace's `docs/public/main.js`. Do not hand-edit them.
|
||||
`docs/index.html` loads the console bridge and loader before the typed bootstrap. Mobile
|
||||
bundles the same loader module locally; it does not depend on the desktop bridge.
|
||||
`docs/assets/js/common.js` remains the desktop UI source and owns Monaco, controls,
|
||||
file import, editor state and instance cleanup. Its remaining TS/UI migration is
|
||||
tracked under SITE-03; this task does not claim that whole file was migrated.
|
||||
`common.js` is now generated from `editor.ts`. Desktop responsibilities are:
|
||||
|
||||
- `bootstrap.ts` and `editor-preferences.ts`: core/AMD/editor boot order, denied
|
||||
storage defaults and the exact prod/ts/code/log preference keys.
|
||||
- `editor.ts` and `editor-host.ts`: DOM bindings, page lifetime and the explicit
|
||||
player/AMD/console boundary. Monaco globals come from the HTML bootstrap.
|
||||
- `editor-monaco.ts`: model/declaration ownership, AMD language readiness and TS emit.
|
||||
- `editor-session.ts`: latest-run ordering, source priority and classic-script execution.
|
||||
- `editor-files.ts`: serialized JS/CSS imports, FileReader errors/cancellation and nodes.
|
||||
- `editor-libraries.ts`: generated declaration URLs, authored through build:ts.
|
||||
|
||||
Root `monaco-editor@0.30.1` is a development type dependency matching the existing
|
||||
browser vendor version. It is imported only as types, never bundled to replace
|
||||
the browser assets. It does not complete the separate vendor provenance review.
|
||||
|
||||
The loader serializes all batches in one page. Each dependency completes before
|
||||
the next is inserted; successful absolute URLs are cached even if a later resource
|
||||
@@ -41,6 +52,28 @@ acceptance remains a separate environment check, not inferred from the branch.
|
||||
Desktop request generations prevent a late example fetch from overwriting a newer
|
||||
Run. Existing `artplayer:example:cleanup`, instance destruction and controls remain.
|
||||
|
||||
Successful preference changes keep their old true/false strings and reload behavior;
|
||||
code/log visibility and URL pushState/Ctrl/Cmd-S remain. Denied storage defaults to
|
||||
false, while failed writes report an error and restore the checkbox. Run is unavailable
|
||||
until initialization finishes. Actual JS/TS AMD modules are awaited before example
|
||||
dependencies can mask define; the old arbitrary one-second delay is removed.
|
||||
|
||||
TypeScript mode now compiles an immutable temporary model before Run. Syntax failures
|
||||
keep the current player; semantic diagnostics are visible but are not a runtime gate.
|
||||
This is a classic-script editor, not a module bundler or npm import resolver. JavaScript
|
||||
uses a fresh non-strict function scope: globals work and top-level var stays local to
|
||||
that Run. Access to the old private common.js closure was not a supported interface.
|
||||
Late compilation cannot replace a newer Run; temporary models are disposed on settlement
|
||||
and page disposal. Monaco worker RPCs have no invented cancellation API.
|
||||
|
||||
Imports preserve sequential JS/CSS insertion and the last lowercased `[name]` label.
|
||||
Unsupported extensions are ignored; local labels are not persistable dependency URLs.
|
||||
File errors now reject, cancellation removes handlers, and batches are serialized.
|
||||
On non-persisted pagehide the editor cleans listeners, models, declaration libraries,
|
||||
FileReaders, imported nodes and players. Executed script globals cannot be undone by
|
||||
removing script nodes. Persisted pagehide keeps state; synthetic lifecycle tests do
|
||||
not certify physical-device BFCache behavior.
|
||||
|
||||
Query parsing preserves raw values (including literal `+`) for one explicit decode;
|
||||
the last duplicate wins and fragments are not part of query values. Example still
|
||||
takes priority over code; mobile/index remain their original defaults. Example HTTP
|
||||
@@ -62,14 +95,16 @@ yarn build:site-assets
|
||||
yarn check:site-assets
|
||||
yarn typecheck:site-assets
|
||||
yarn typecheck:docs-tools
|
||||
yarn test:site-editor
|
||||
node --test test/site-loading.test.js
|
||||
yarn test:browser test/browser/site-loading.spec.js --workers=1
|
||||
```
|
||||
|
||||
Root build:docs regenerates site assets first. ci:check checks their types and drift;
|
||||
root lint includes the TS modules and legacy common.js. Direct workspace VitePress
|
||||
root lint includes the TS sources; common.js is checked by generated asset comparison. Direct workspace VitePress
|
||||
commands still require regenerating these assets after source edits. The full
|
||||
VitePress build/deploy, i18n and remote translation/LLM tooling remain SITE-03/05.
|
||||
VitePress build/i18n tools are in scripts/site-build and offline LLM/draft translation
|
||||
tools are in scripts/documentation. Full site/search and release acceptance remain separate.
|
||||
Tests use real local pages, Monaco and controlled media, with unrelated remote
|
||||
analytics/ads fulfilled inertly in test routes. They do not validate those services.
|
||||
Keep browser verification serial with expensive compiler baselines when local
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { readPreference } from './editor-preferences.ts'
|
||||
|
||||
function loadScript(url: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script')
|
||||
const clear = () => {
|
||||
script.onload = null
|
||||
script.onerror = null
|
||||
}
|
||||
script.onload = () => {
|
||||
clear()
|
||||
resolve()
|
||||
}
|
||||
script.onerror = () => {
|
||||
clear()
|
||||
script.remove()
|
||||
reject(new Error(`Editor bootstrap failed: ${url}`))
|
||||
}
|
||||
script.src = url
|
||||
document.body.appendChild(script)
|
||||
})
|
||||
}
|
||||
|
||||
async function bootstrap(): Promise<void> {
|
||||
await loadScript(
|
||||
readPreference('prod')
|
||||
? './compiled/artplayer.js'
|
||||
: './uncompiled/artplayer/index.js',
|
||||
)
|
||||
await loadScript('./assets/js/vs/loader.js')
|
||||
await loadScript('./assets/js/common.js')
|
||||
}
|
||||
|
||||
void bootstrap().catch(error => console.error(error))
|
||||
@@ -0,0 +1,82 @@
|
||||
export function readEditorFile(
|
||||
file: File,
|
||||
signal: AbortSignal,
|
||||
createReader: () => FileReader = () => new FileReader(),
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = createReader()
|
||||
function clear() {
|
||||
reader.onload = null
|
||||
reader.onerror = null
|
||||
reader.onabort = null
|
||||
signal.removeEventListener('abort', cancel)
|
||||
}
|
||||
function fail(error: unknown) {
|
||||
clear()
|
||||
reject(error)
|
||||
}
|
||||
function cancel() {
|
||||
reader.abort()
|
||||
fail(new Error(`File import cancelled: ${file.name}`))
|
||||
}
|
||||
reader.onload = () => {
|
||||
if (typeof reader.result !== 'string') {
|
||||
fail(new Error(`File is not text: ${file.name}`))
|
||||
return
|
||||
}
|
||||
const value = reader.result
|
||||
clear()
|
||||
resolve(value)
|
||||
}
|
||||
reader.onerror = () =>
|
||||
fail(reader.error || new Error(`Reading file failed: ${file.name}`))
|
||||
reader.onabort = () =>
|
||||
fail(new Error(`File import cancelled: ${file.name}`))
|
||||
signal.addEventListener('abort', cancel, { once: true })
|
||||
try {
|
||||
if (signal.aborted)
|
||||
cancel()
|
||||
else reader.readAsText(file)
|
||||
}
|
||||
catch (error) {
|
||||
fail(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function createFileImports(
|
||||
document: Document,
|
||||
label: (name: string) => void,
|
||||
) {
|
||||
const controller = new AbortController()
|
||||
const elements: HTMLElement[] = []
|
||||
let queue: Promise<unknown> = Promise.resolve()
|
||||
return {
|
||||
add(files: File[]): Promise<void> {
|
||||
const work = queue.then(async () => {
|
||||
for (const file of files) {
|
||||
const name = file.name.toLowerCase().trim()
|
||||
if (!name.endsWith('.js') && !name.endsWith('.css'))
|
||||
continue
|
||||
const text = await readEditorFile(file, controller.signal)
|
||||
if (controller.signal.aborted)
|
||||
return
|
||||
const element = document.createElement(
|
||||
name.endsWith('.css') ? 'style' : 'script',
|
||||
)
|
||||
element.textContent = text
|
||||
document.body.appendChild(element)
|
||||
elements.push(element)
|
||||
label(name)
|
||||
}
|
||||
})
|
||||
queue = work.catch(() => {})
|
||||
return work
|
||||
},
|
||||
dispose(): void {
|
||||
controller.abort()
|
||||
for (const element of elements) element.remove()
|
||||
elements.length = 0
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type * as Monaco from 'monaco-editor'
|
||||
import type {
|
||||
createLibraryLoader,
|
||||
exampleSource,
|
||||
parameters,
|
||||
} from './loader.ts'
|
||||
|
||||
export interface EditorPlayer {
|
||||
destroy: (removeHtml?: boolean) => unknown
|
||||
}
|
||||
export interface AmdLoader {
|
||||
(
|
||||
modules: string[],
|
||||
ready: () => void,
|
||||
failed: (error: unknown) => void,
|
||||
): void
|
||||
config: (options: { paths: { vs: string } }) => void
|
||||
}
|
||||
export type EditorHost = Window & {
|
||||
console: Console
|
||||
Artplayer: {
|
||||
DEBUG: boolean
|
||||
utils: { isMobile: boolean }
|
||||
instances: EditorPlayer[]
|
||||
}
|
||||
monaco: typeof Monaco
|
||||
require: AmdLoader
|
||||
consoleLog: (element: HTMLElement) => void
|
||||
ArtplayerDocsLoader: ReturnType<typeof createLibraryLoader> & {
|
||||
exampleSource: typeof exampleSource
|
||||
parameters: typeof parameters
|
||||
}
|
||||
art: EditorPlayer | undefined
|
||||
}
|
||||
|
||||
export type Editor = Monaco.editor.IStandaloneCodeEditor
|
||||
@@ -0,0 +1,25 @@
|
||||
// Generated by yarn build:ts. Do not edit.
|
||||
export const libUris = [
|
||||
'./assets/ts/artplayer-plugin-ads.d.ts',
|
||||
'./assets/ts/artplayer-plugin-ambilight.d.ts',
|
||||
'./assets/ts/artplayer-plugin-asr.d.ts',
|
||||
'./assets/ts/artplayer-plugin-audio-track.d.ts',
|
||||
'./assets/ts/artplayer-plugin-auto-thumbnail.d.ts',
|
||||
'./assets/ts/artplayer-plugin-chapter.d.ts',
|
||||
'./assets/ts/artplayer-plugin-chromecast.d.ts',
|
||||
'./assets/ts/artplayer-plugin-danmuku-mask.d.ts',
|
||||
'./assets/ts/artplayer-plugin-danmuku.d.ts',
|
||||
'./assets/ts/artplayer-plugin-dash-control.d.ts',
|
||||
'./assets/ts/artplayer-plugin-document-pip.d.ts',
|
||||
'./assets/ts/artplayer-plugin-hls-control.d.ts',
|
||||
'./assets/ts/artplayer-plugin-jassub.d.ts',
|
||||
'./assets/ts/artplayer-plugin-multiple-subtitles.d.ts',
|
||||
'./assets/ts/artplayer-plugin-vast.d.ts',
|
||||
'./assets/ts/artplayer-plugin-vtt-thumbnail.d.ts',
|
||||
'./assets/ts/artplayer-proxy-canvas.d.ts',
|
||||
'./assets/ts/artplayer-proxy-mediabunny.d.ts',
|
||||
'./assets/ts/artplayer-tool-iframe.d.ts',
|
||||
'./assets/ts/artplayer-tool-thumbnail.d.ts',
|
||||
'./assets/ts/artplayer.d.ts',
|
||||
'./assets/ts/artplayer-i18n.d.ts',
|
||||
]
|
||||
@@ -0,0 +1,130 @@
|
||||
import type { Editor, EditorHost } from './editor-host.ts'
|
||||
import { libUris } from './editor-libraries.ts'
|
||||
|
||||
export async function createMonaco(
|
||||
host: EditorHost,
|
||||
container: HTMLElement,
|
||||
typescript: boolean,
|
||||
signal: AbortSignal,
|
||||
) {
|
||||
host.require.config({ paths: { vs: './assets/js/vs' } })
|
||||
await new Promise<void>((resolve, reject) =>
|
||||
host.require(['vs/editor/editor.main'], resolve, reject),
|
||||
)
|
||||
await new Promise<void>((resolve, reject) =>
|
||||
host.require(
|
||||
[
|
||||
'vs/basic-languages/typescript/typescript',
|
||||
'vs/basic-languages/javascript/javascript',
|
||||
'vs/language/typescript/tsMode',
|
||||
],
|
||||
resolve,
|
||||
reject,
|
||||
),
|
||||
)
|
||||
if (signal.aborted)
|
||||
throw new Error('Editor initialization cancelled')
|
||||
const api = host.monaco
|
||||
const types = api.languages.typescript
|
||||
const resources: { dispose: () => void }[] = []
|
||||
const temporary = new Set<ReturnType<typeof api.editor.createModel>>()
|
||||
let editor: Editor | undefined
|
||||
let disposed = false
|
||||
const dispose = () => {
|
||||
if (disposed)
|
||||
return
|
||||
disposed = true
|
||||
signal.removeEventListener('abort', dispose)
|
||||
editor?.dispose()
|
||||
editor = undefined
|
||||
for (const model of temporary) model.dispose()
|
||||
temporary.clear()
|
||||
for (const resource of resources.splice(0).reverse()) resource.dispose()
|
||||
}
|
||||
signal.addEventListener('abort', dispose, { once: true })
|
||||
try {
|
||||
for (const defaults of [
|
||||
types.javascriptDefaults,
|
||||
types.typescriptDefaults,
|
||||
]) {
|
||||
defaults.setDiagnosticsOptions({
|
||||
noSemanticValidation: defaults === types.javascriptDefaults,
|
||||
noSyntaxValidation: false,
|
||||
})
|
||||
defaults.setCompilerOptions({
|
||||
target: types.ScriptTarget.ES2015,
|
||||
module: types.ModuleKind.None,
|
||||
allowNonTsExtensions: true,
|
||||
noEmit: false,
|
||||
})
|
||||
}
|
||||
for (const uri of libUris) {
|
||||
const response = await host.fetch(uri, { signal })
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Loading editor declaration failed: ${uri} (${response.status})`,
|
||||
)
|
||||
}
|
||||
const source = await response.text()
|
||||
if (signal.aborted)
|
||||
throw new Error('Editor initialization cancelled')
|
||||
resources.push(
|
||||
types.javascriptDefaults.addExtraLib(source, uri),
|
||||
types.typescriptDefaults.addExtraLib(source, uri),
|
||||
)
|
||||
resources.push(
|
||||
api.editor.createModel(source, 'typescript', api.Uri.parse(uri)),
|
||||
)
|
||||
}
|
||||
const model = api.editor.createModel(
|
||||
'var art = new Artplayer({\n\tcontainer: \'.artplayer-app\',\n\turl: \'/assets/sample/video.mp4\',\n});',
|
||||
typescript ? 'typescript' : 'javascript',
|
||||
)
|
||||
resources.push(model)
|
||||
editor = api.editor.create(container, {
|
||||
theme: 'vs-dark',
|
||||
folding: true,
|
||||
automaticLayout: true,
|
||||
quickSuggestions: { other: true, comments: true, strings: true },
|
||||
model,
|
||||
})
|
||||
return {
|
||||
editor,
|
||||
async compile(source: string): Promise<string> {
|
||||
if (disposed)
|
||||
throw new Error('Editor was disposed')
|
||||
if (!typescript)
|
||||
return source
|
||||
const model = api.editor.createModel(source, 'typescript')
|
||||
temporary.add(model)
|
||||
try {
|
||||
const worker = await (await types.getTypeScriptWorker())(model.uri)
|
||||
const file = model.uri.toString()
|
||||
const syntax = await worker.getSyntacticDiagnostics(file)
|
||||
if (syntax.length) {
|
||||
throw new Error(
|
||||
`TypeScript syntax error: ${syntax.map(item => (typeof item.messageText === 'string' ? item.messageText : item.messageText.messageText)).join('; ')}`,
|
||||
)
|
||||
}
|
||||
const output = await worker.getEmitOutput(file)
|
||||
const script = output.outputFiles.find(file =>
|
||||
file.name.endsWith('.js'),
|
||||
)
|
||||
if (output.emitSkipped || !script)
|
||||
throw new Error('TypeScript worker did not emit JavaScript')
|
||||
return script.text
|
||||
}
|
||||
finally {
|
||||
temporary.delete(model)
|
||||
if (!model.isDisposed())
|
||||
model.dispose()
|
||||
}
|
||||
},
|
||||
dispose,
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
dispose()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
export type Preference = 'prod' | 'ts' | 'code' | 'log'
|
||||
|
||||
export function readPreference(
|
||||
key: Preference,
|
||||
storage: () => Storage = () => localStorage,
|
||||
): boolean {
|
||||
try {
|
||||
return storage().getItem(key) === 'true'
|
||||
}
|
||||
catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function writePreference(
|
||||
key: Preference,
|
||||
value: boolean,
|
||||
storage: () => Storage = () => localStorage,
|
||||
): boolean {
|
||||
try {
|
||||
storage().setItem(key, value ? 'true' : 'false')
|
||||
return true
|
||||
}
|
||||
catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
interface SessionOptions {
|
||||
loadLibraries: (encoded: string) => Promise<unknown>
|
||||
exampleSource: (name: string) => Promise<string>
|
||||
writeCode: (source: string) => void
|
||||
compile: (source: string) => Promise<string>
|
||||
execute: (code: string) => void
|
||||
}
|
||||
|
||||
export function createEditorSession(options: SessionOptions) {
|
||||
let generation = 0
|
||||
let disposed = false
|
||||
return {
|
||||
async run({
|
||||
libs = '',
|
||||
code,
|
||||
example,
|
||||
}: {
|
||||
libs?: string
|
||||
code?: string
|
||||
example?: string
|
||||
}): Promise<void> {
|
||||
const current = ++generation
|
||||
const active = () => !disposed && current === generation
|
||||
if (!active())
|
||||
return
|
||||
await options.loadLibraries(libs)
|
||||
if (!active())
|
||||
return
|
||||
const source = example
|
||||
? await options.exampleSource(example)
|
||||
: code
|
||||
? decodeURIComponent(code).trim()
|
||||
: await options.exampleSource('index')
|
||||
if (!active())
|
||||
return
|
||||
options.writeCode(source)
|
||||
const executable = await options.compile(source)
|
||||
if (active())
|
||||
options.execute(executable)
|
||||
},
|
||||
dispose(): void {
|
||||
disposed = true
|
||||
generation++
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function executeClassic(code: string): void {
|
||||
// eslint-disable-next-line no-new-func -- The existing editor explicitly executes user-entered classic script on Run.
|
||||
new Function(code)()
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import type { EditorHost } from './editor-host.ts'
|
||||
import { createFileImports } from './editor-files.ts'
|
||||
import { createMonaco } from './editor-monaco.ts'
|
||||
import { readPreference, writePreference } from './editor-preferences.ts'
|
||||
import { createEditorSession, executeClassic } from './editor-session.ts'
|
||||
|
||||
export async function installEditor(host: EditorHost): Promise<void> {
|
||||
const document = host.document
|
||||
const element = <T extends HTMLElement>(selector: string): T => {
|
||||
const node = document.querySelector<T>(selector)
|
||||
if (!node)
|
||||
throw new Error(`Missing editor element: ${selector}`)
|
||||
return node
|
||||
}
|
||||
host.Artplayer.DEBUG = true
|
||||
if (host.Artplayer.utils.isMobile) {
|
||||
host.location.href = `./mobile.html${host.location.search}`
|
||||
return
|
||||
}
|
||||
const run = element<HTMLButtonElement>('.run')
|
||||
const libs = element<HTMLTextAreaElement>('.libsInput')
|
||||
const file = element<HTMLInputElement>('#file')
|
||||
const popup = element<HTMLDivElement>('.popups')
|
||||
const output = element<HTMLDivElement>('.console')
|
||||
host.consoleLog(output)
|
||||
const controller = new AbortController()
|
||||
const cleanups: (() => void)[] = []
|
||||
const listen = (
|
||||
target: EventTarget,
|
||||
type: string,
|
||||
callback: EventListener,
|
||||
) => {
|
||||
target.addEventListener(type, callback)
|
||||
cleanups.push(() => target.removeEventListener(type, callback))
|
||||
}
|
||||
const report = (error: unknown) => {
|
||||
if (!controller.signal.aborted)
|
||||
host.console.error(error)
|
||||
}
|
||||
listen(host, 'error', event => host.console.error(event))
|
||||
listen(host, 'unhandledrejection', event => host.console.error(event))
|
||||
for (const key of ['prod', 'ts', 'code', 'log'] as const) {
|
||||
const checkbox = element<HTMLInputElement>(`#${key}`)
|
||||
checkbox.checked = readPreference(key, () => host.localStorage)
|
||||
listen(checkbox, 'change', () => {
|
||||
if (writePreference(key, checkbox.checked, () => host.localStorage)) {
|
||||
host.location.reload()
|
||||
}
|
||||
else {
|
||||
checkbox.checked = readPreference(key, () => host.localStorage)
|
||||
report(
|
||||
new Error('Browser storage is unavailable; preference was not saved'),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (element<HTMLInputElement>('#code').checked)
|
||||
element('#editor').style.display = 'none'
|
||||
if (element<HTMLInputElement>('#log').checked)
|
||||
output.style.display = 'none'
|
||||
const imports = createFileImports(document, (name) => {
|
||||
libs.value = `[${name}]`
|
||||
})
|
||||
cleanups.push(() => imports.dispose())
|
||||
cleanups.push(() => {
|
||||
host.dispatchEvent(new Event('artplayer:example:cleanup'))
|
||||
for (const art of host.Artplayer.instances.slice()) {
|
||||
try {
|
||||
art.destroy(true)
|
||||
}
|
||||
catch (error) {
|
||||
host.console.error(error)
|
||||
}
|
||||
}
|
||||
host.art = undefined
|
||||
})
|
||||
listen(file, 'change', () => {
|
||||
void imports.add(Array.from(file.files || [])).catch(report)
|
||||
})
|
||||
listen(popup, 'click', (event) => {
|
||||
if (event.target === popup)
|
||||
popup.style.display = 'none'
|
||||
})
|
||||
const onPageHide = (event: PageTransitionEvent) => {
|
||||
if (event.persisted)
|
||||
return
|
||||
controller.abort()
|
||||
for (const cleanup of cleanups.reverse()) {
|
||||
try {
|
||||
cleanup()
|
||||
}
|
||||
catch (error) {
|
||||
host.console.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
host.addEventListener('pagehide', onPageHide)
|
||||
cleanups.push(() => host.removeEventListener('pagehide', onPageHide))
|
||||
run.setAttribute('aria-disabled', 'true')
|
||||
try {
|
||||
const monaco = await createMonaco(
|
||||
host,
|
||||
element('.codeMirrorWrap'),
|
||||
element<HTMLInputElement>('#ts').checked,
|
||||
controller.signal,
|
||||
)
|
||||
const session = createEditorSession({
|
||||
loadLibraries: (encoded) => {
|
||||
libs.value = decodeURIComponent(encoded)
|
||||
return host.ArtplayerDocsLoader.loadLibraries(encoded)
|
||||
},
|
||||
exampleSource: name => host.ArtplayerDocsLoader.exampleSource(name),
|
||||
writeCode: code => monaco.editor.setValue(code),
|
||||
compile: monaco.compile,
|
||||
execute: (code) => {
|
||||
host.dispatchEvent(new Event('artplayer:example:cleanup'))
|
||||
for (const art of host.Artplayer.instances.slice()) art.destroy(true)
|
||||
executeClassic(code)
|
||||
host.art = host.Artplayer.instances[0]
|
||||
},
|
||||
})
|
||||
cleanups.push(() => session.dispose(), monaco.dispose)
|
||||
function restart() {
|
||||
const code = encodeURIComponent(monaco.editor.getValue())
|
||||
const dependencies = encodeURIComponent(libs.value)
|
||||
host.history.pushState(
|
||||
null,
|
||||
'',
|
||||
`${host.location.origin}${host.location.pathname}?libs=${dependencies}&code=${code}`,
|
||||
)
|
||||
void session.run({ libs: dependencies, code }).catch(report)
|
||||
}
|
||||
listen(run, 'click', restart)
|
||||
listen(document, 'keydown', (event) => {
|
||||
const key = event as KeyboardEvent
|
||||
if ((key.ctrlKey || key.metaKey) && key.key === 's') {
|
||||
key.preventDefault()
|
||||
restart()
|
||||
}
|
||||
})
|
||||
run.removeAttribute('aria-disabled')
|
||||
await session.run(host.ArtplayerDocsLoader.parameters(host.location.href))
|
||||
}
|
||||
catch (error) {
|
||||
report(error)
|
||||
}
|
||||
}
|
||||
|
||||
// HTML bootstraps the existing player, Monaco AMD loader and console bridge before this entry.
|
||||
void installEditor(window as unknown as EditorHost).catch(error =>
|
||||
console.error(error),
|
||||
)
|
||||
Reference in new issue
Block a user