import { CardValue, Tooltip } from "@aomao/engine"; import { $, Card, CardToolbarItemOptions, CardType, escape, getFileSize, isEngine, isMobile, NodeInterface, sanitizeUrl, ToolbarItemOptions, } from "@aomao/engine"; import "./index.css"; export interface AudioValue extends CardValue { /** * 音频唯一标识 */ audio_id?: string; /** * 音频名称 */ name: string; /** * 音频地址 */ url: string; /** * 下载地址 */ download?: string; /** * 状态 * uploading 上传中 * done 上传成功 */ status?: "uploading" | "transcoding" | "done" | "error"; /** * 上传进度 */ percent?: number; /** * 音频大小 */ size?: number; /** * 错误状态下的错误信息 */ message?: string; } class AudioComponent extends Card { static get cardName() { return "audio"; } static get cardType() { return CardType.BLOCK; } static get autoSelected() { return false; } private container?: NodeInterface; getLocales() { return this.editor.language.get<{ [key: string]: string }>("audio"); } renderTemplate(value: AudioValue) { const { name, status, size, message, percent } = value; const locales = this.getLocales(); const icons = { audio: `
`, spin: ``, warn: `
`, error: 'X', }; if (status === "error") { return `
${escape(name)}
${icons.error} ${message || locales["loadError"]}
`; } const fileSize: string = size ? getFileSize(size) : ""; if (status === "uploading") { return `
${icons.audio}
${escape(name)} (${escape(fileSize)})
${icons.spin} ${percent || 0}%
`; } const isLoading = typeof status === "undefined"; if (status === "transcoding" || isLoading) { return `
${icons.audio}
${escape(name)} (${escape(fileSize)})
${icons.spin} ${ isLoading ? locales["loading"] : locales["transcoding"] }%
`; } return `
`; } onBeforeRender = (action: "query" | "download" | "cover", url: string) => { const audioPlugin = this.editor.plugin.components["audio"] as any; if (audioPlugin) { const { onBeforeRender } = audioPlugin["options"] || {}; if (onBeforeRender) return onBeforeRender(action, url); } return url; }; initPlayer() { const value = this.getValue(); if (!value) return; const url = sanitizeUrl(this.onBeforeRender("query", value.url)); const audio = document.createElement("audio"); audio.preload = "none"; audio.setAttribute("src", url); audio.setAttribute("webkit-playsinline", "webkit-playsinline"); audio.setAttribute("playsinline", "playsinline"); this.container?.find(".data-audio-content").append(audio); audio.oncontextmenu = function () { return false; }; // 一次渲染时序开启 controls 会触发一次内容为空的 window.onerror,疑似 chrome bug setTimeout(() => { audio.controls = true; }, 0); } downloadFile = () => { const value = this.getValue(); if (!value?.download) return; window.open(sanitizeUrl(this.onBeforeRender("download", value.url))); }; toolbar() { const items: Array = []; const value = this.getValue(); if (!value) return items; const { status, download } = value; const locale = this.getLocales(); if (status === "done") { if (download) { items.push({ type: "button", content: '', title: locale.download, onClick: this.downloadFile, }); } if (isEngine(this.editor) && !this.editor.readonly) { items.push({ type: "copy", }); items.push({ type: "separator", }); } } if (isEngine(this.editor) && !this.editor.readonly) { items.push({ type: "delete", }); } return items; } setProgressPercent(percent: number) { this.container?.find(".percent").html(`${percent}%`); } onActivate(activated: boolean) { if (activated) this.container?.addClass("data-audio-active"); else this.container?.removeClass("data-audio-active"); } checker( audio_id: string, success: (data?: { url: string; name?: string; cover?: string; download?: string; status?: string; }) => void, failed: (message: string) => void ) { const { command } = this.editor; const handle = () => { command.executeMethod( "audio-uploader", "query", audio_id, (data?: { url: string; name?: string; cover?: string; download?: string; status?: string; }) => { if (data && data.status !== "done") setTimeout(handle, 3000); else success(data); }, (message: string) => { failed(message); } ); }; handle(); } render(): string | void | NodeInterface { const value = this.getValue(); if (!value) return; const center = this.getCenter(); //先清空卡片内容容器 center.empty(); const { command, plugin } = this.editor; const { audio_id, status } = value; const locales = this.getLocales(); //阅读模式 if (!isEngine(this.editor)) { if (status === "done") { //设置为加载状态 this.container = $( this.renderTemplate({ ...value, status: undefined }) ); const updateValue = (data?: { url: string; name?: string; cover?: string; download?: string; }) => { const newValue: AudioValue = { ...value, url: data?.url ? data.url : value.url, name: data?.name ? data.name : value.name, download: data?.download ? data.download : value.download, }; this.container = $(this.renderTemplate(newValue)); center.empty(); center.append(this.container); this.initPlayer(); }; if (plugin.components["audio-uploader"]) { command.executeMethod( "audio-uploader", "query", audio_id, (data?: { url: string; name?: string; cover?: string; download?: string; }) => { updateValue(data); }, (error: string) => { this.container = $( this.renderTemplate({ ...value, status: "error", message: error || locales["loadError"], }) ); center.empty(); center.append(this.container); } ); } else { updateValue(); } return this.container; } else if (status === "error") { return $( this.renderTemplate({ ...value, message: value.message || locales["loadError"], }) ); } } //转换中 else if (status === "transcoding") { this.container = $(this.renderTemplate(value)); if (!audio_id) throw "audio id is undefined"; this.checker( audio_id, (data?: { url: string; name?: string; cover?: string; download?: string; status?: string; }) => { const newValue: V = { ...value, url: data?.url ? data.url : value.url, name: data?.name ? data.name : value.name, download: data?.download ? data.download : value.download, status: "done", }; this.setValue(newValue); this.container = $(this.renderTemplate(newValue)); center.empty(); center.append(this.container); this.initPlayer(); }, (error: string) => { const newValue: V = { ...value, status: "error", message: error || locales["loadError"], }; this.setValue(newValue); this.container = $(this.renderTemplate(newValue)); center.empty(); center.append(this.container); } ); return this.container; } //已完成 else if (status === "done") { //设置为加载状态 this.container = $(this.renderTemplate({ ...value, status: undefined })); command.executeMethod( "audio-uploader", "query", audio_id, (data?: { url: string; name?: string; cover?: string; download?: string; }) => { const newValue: AudioValue = { ...value, url: data?.url ? data.url : value.url, name: data?.name ? data.name : value.name, download: data?.download ? data.download : value.download, }; this.container = $(this.renderTemplate(newValue)); center.empty(); center.append(this.container); this.initPlayer(); }, (error: string) => { this.container = $( this.renderTemplate({ ...value, status: "error", message: error || locales["loadError"], }) ); center.empty(); center.append(this.container); } ); return this.container; } else { return $(this.renderTemplate(value)); } } didRender() { super.didRender(); this.container?.on(isMobile ? "touchstart" : "click", () => { if (isEngine(this.editor) && !this.activated) { this.editor.card.activate(this.root); } }); } } export default AudioComponent;