diff --git a/src/btn.ts b/src/btn.ts index 63b30c6..4d3e162 100644 --- a/src/btn.ts +++ b/src/btn.ts @@ -1,5 +1,6 @@ import { loadMscore, WebMscore } from './mscore' +import { useTimeout } from './utils' import i18n from './i18n' // @ts-ignore import btnListCss from './btn.css' @@ -159,13 +160,13 @@ export namespace BtnAction { }) } - export const download = (url: UrlInput): BtnAction => { - return process(async (): Promise => { + export const download = (url: UrlInput, fallback?: () => Promisable, timeout?: number): BtnAction => { + return process(async (): Promise => { const _url = await normalizeUrlInput(url) const a = document.createElement('a') a.href = _url a.dispatchEvent(new MouseEvent('click')) - }) + }, fallback, timeout) } export const mscoreWindow = (fn: (w: Window, score: WebMscore, processingTextEl: ChildNode) => any): BtnAction => { @@ -199,7 +200,7 @@ export namespace BtnAction { } } - export const process = (fn: () => any): BtnAction => { + export const process = (fn: () => any, fallback?: () => Promisable, timeout = Infinity): BtnAction => { return async (name, btn, setText): Promise => { const _onclick = btn.onclick @@ -207,11 +208,17 @@ export namespace BtnAction { setText(i18n('PROCESSING')()) try { - await fn() + await useTimeout(fn(), timeout) setText(name) } catch (err) { - setText(i18n('BTN_ERROR')()) console.error(err) + if (fallback) { + // use fallback + await fallback() + setText(name) + } else { + setText(i18n('BTN_ERROR')()) + } } btn.onclick = _onclick diff --git a/src/main.ts b/src/main.ts index 9cbce7c..060e062 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,6 +13,12 @@ const main = (): void => { const btnList = new BtnList() const filename = scoreinfo.fileName + let indvPartBtn: HTMLButtonElement | null = null + const fallback = () => { + // btns fallback to load from MSCZ file (`Individual Parts`) + return indvPartBtn?.click() + } + btnList.add({ name: i18n('DOWNLOAD')('MSCZ'), action: BtnAction.process(downloadMscz), @@ -20,7 +26,7 @@ const main = (): void => { btnList.add({ name: i18n('DOWNLOAD')('PDF'), - action: BtnAction.process(downloadPDF), + action: BtnAction.process(downloadPDF, fallback, 3 * 60 * 1000 /* 3min */), }) btnList.add({ @@ -35,15 +41,15 @@ const main = (): void => { btnList.add({ name: i18n('DOWNLOAD')('MIDI'), - action: BtnAction.download(() => getFileUrl('midi')), + action: BtnAction.download(() => getFileUrl('midi'), fallback, 30 * 1000 /* 30s */), }) btnList.add({ name: i18n('DOWNLOAD')('MP3'), - action: BtnAction.download(() => getFileUrl('mp3')), + action: BtnAction.download(() => getFileUrl('mp3'), fallback, 30 * 1000 /* 30s */), }) - btnList.add({ + indvPartBtn = btnList.add({ name: i18n('IND_PARTS')(), tooltip: i18n('IND_PARTS_TOOLTIP')(), action: BtnAction.mscoreWindow(async (w, score, txt) => { diff --git a/src/utils.ts b/src/utils.ts index ee5b88e..2665295 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -19,6 +19,19 @@ export const fetchData = async (url: string, init?: RequestInit): Promise (promise: T | Promise, ms: number): Promise => { + if (!(promise instanceof Promise)) { + return promise + } + + return new Promise((resolve, reject) => { + const i = setTimeout(() => { + reject(new Error('timeout')) + }, ms) + promise.then(resolve, reject).finally(() => clearTimeout(i)) + }) +} + export const waitForDocumentLoaded = (): Promise => { if (document.readyState !== 'complete') { return new Promise(resolve => {