diff --git a/index.css b/index.css index dddd5ad0..8612336a 100644 --- a/index.css +++ b/index.css @@ -745,6 +745,7 @@ input[type="number"] { height: 100%; user-select: text; caret-color: auto; + font-size: 13.33px; } input:disabled { diff --git a/src/App.ts b/src/App.ts index 2cbe7b6e..fb653f54 100644 --- a/src/App.ts +++ b/src/App.ts @@ -25,7 +25,6 @@ import { Keybinds } from "./util/Keybinds" import { Options } from "./util/Options" import { extname } from "./util/Path" import { fpsUpdate } from "./util/Performance" -import { getBrowser } from "./util/Util" import { FileHandler } from "./util/file-handler/FileHandler" declare global { interface Window { @@ -317,36 +316,6 @@ function init() {
Please visit your browser settings and enable WebGL.
` - } else if (getBrowser().includes("Safari")) { - document.querySelector( - "body" - )!.innerHTML = `
-
-

Safari is currently not supported

-
Please use Chrome/Firefox instead.
-
Check the console for more info.
-
-
` - console.log( - `SMEditor is not supported for Safari due to various issues involving rendering and sound. - PIXI.js, the library used in SMEditor, takes an extremely long time to load and does not perform well on Safari. - Additionally, many audio files cannot be played in Safari. - If you still want to try loading SMEditor, run the command runSafari()` - ) - window.runSafari = () => { - document.querySelector("body")!.innerHTML = `
- -
- -
-
-
- -
- ` - window.app = new App() - window.runSafari = undefined - } } else { window.app = new App() } diff --git a/src/chart/audio/ChartAudio.ts b/src/chart/audio/ChartAudio.ts index 540d17cb..6bffb053 100644 --- a/src/chart/audio/ChartAudio.ts +++ b/src/chart/audio/ChartAudio.ts @@ -425,7 +425,7 @@ export class ChartAudio { } catch (e) { if (this.type == ".ogg") { // attempt to decode with ogg - const oggdec = await (await import("../../util/OggDec")).default + const oggdec = (await import("../../util/OggDec")).default try { resolve(await oggdec.decodeOggData(data)) } catch (err) { diff --git a/src/gui/widget/DebugWidget.ts b/src/gui/widget/DebugWidget.ts index 95aedefa..78b1bce6 100644 --- a/src/gui/widget/DebugWidget.ts +++ b/src/gui/widget/DebugWidget.ts @@ -6,11 +6,11 @@ import { Texture, } from "pixi.js" import { BetterRoundedRect } from "../../util/BetterRoundedRect" +import { clamp, roundDigit } from "../../util/Math" import { Options } from "../../util/Options" +import { getFPS, getTPS } from "../../util/Performance" import { Widget } from "./Widget" import { WidgetManager } from "./WidgetManager" -import { clamp, roundDigit } from "../../util/Math" -import { getFPS, getTPS } from "../../util/Performance" const GRAPH_HEIGHT = 50 @@ -115,8 +115,8 @@ export class DebugWidget extends Widget { this.fpsBg.width = this.fpsText.width + 10 this.fpsBg.height = this.fpsText.height + 10 if (Options.debug.showTimers) { - this.fpsBg.y = (GRAPH_HEIGHT + 5) * (this.children.length + 2) - 5 - this.fpsText.y = (GRAPH_HEIGHT + 5) * (this.children.length + 2) + this.fpsBg.y = (GRAPH_HEIGHT + 5) * this.graphs.children.length - 5 + this.fpsText.y = (GRAPH_HEIGHT + 5) * this.graphs.children.length } else { this.fpsBg.y = -5 this.fpsText.y = 0 diff --git a/src/util/OggDec.d.ts b/src/util/OggDec.d.ts index d8e44c21..8aba9aa0 100644 --- a/src/util/OggDec.d.ts +++ b/src/util/OggDec.d.ts @@ -1,5 +1,6 @@ +const ogg: OggDec +export default ogg + interface OggDec { - decodeOggData(data: ArrayBuffer): ArrayBuffer + decodeOggData(data: ArrayBuffer): Promise } - -export default oggdec = OggDec diff --git a/src/util/SafariFileWorker.ts b/src/util/SafariFileWorker.ts deleted file mode 100644 index b0ea3dbe..00000000 --- a/src/util/SafariFileWorker.ts +++ /dev/null @@ -1,102 +0,0 @@ -// WebKit is currently bugged, making this not work at all -// Thanks Apple -export class SafariFileWorker { - private static _worker: Worker - private static workID = 0 - private static map: Map) => void> = - new Map() - - private static _init() { - const workerCode = ` - function resolvePath(path) { - let pathParts = path.split("/") - pathParts = pathParts.filter(item => item != "." && item != "") - while (pathParts.indexOf("..") > -1) { - const ind = pathParts.indexOf("..") - if (ind == 0) { - throw Error("Path" + pathParts.join("/") + "is invalid!") - } - pathParts.splice(ind - 1, 2) - } - return pathParts.join("/") - } - - async function getFileHandle(path, options) { - try { - const pathParts = resolvePath(path).split("/") - const filename = pathParts.pop() - const dirHandle = await getDirectoryHandle( - pathParts.join("/"), - options - ) - if (!dirHandle) return undefined - return await dirHandle.getFileHandle(filename, options) - } catch (err) { - console.log(err) - return undefined - } - } - - async function getDirectoryHandle(path, options, dir){ - dir ||= await navigator.storage.getDirectory() - if (path == "" || path == ".") return dir - try { - const pathParts = resolvePath(path).split("/") - const dirname = pathParts.shift() - const dirHandle = await dir.getDirectoryHandle(dirname, options) - if (!dirHandle) return undefined - if (pathParts.length == 0) return dirHandle - return getDirectoryHandle(pathParts.join("/"), options, dirHandle) - } catch (err) { - console.log(err) - return undefined - } - } - - async function writeFile(path, arrayBuffer) { - const handle = await getFileHandle(path) - const accessHandle = await handle.createSyncAccessHandle(); - const writeSize = accessHandle.write(arrayBuffer, { "at": 0 }); - await accessHandle.flush(); - const fileSize = await accessHandle.getSize(); - // Read file content to a buffer. - const readBuffer = new ArrayBuffer(fileSize); - const readSize = accessHandle.read(readBuffer, { "at": 0 }); - console.log(readSize) - await accessHandle.close(); - } - - self.onmessage = function(e){ - writeFile(e.data[1],e.data[2]).then(()=>{ - postMessage(e.data[0]) - }) - } - ` - - const blob = new Blob([workerCode], { type: "application/javascript" }) - this._worker = new Worker(URL.createObjectURL(blob)) - this._worker.onmessage = e => { - const id = e.data as number - console.log("Finished job " + id) - if (this.map.has(id)) { - this.map.get(id)!() - } - } - } - - private static get worker(): Worker { - if (!this._worker) this._init() - return this._worker - } - - static async writeHandle(path: string, data: Blob | string) { - const id = this.workID++ - console.log("Starting work write " + path + ", id: " + id) - const promise = new Promise(resolve => this.map.set(id, resolve)) - const encode = new TextEncoder() - const buffer = - typeof data == "string" ? encode.encode(data) : await data.arrayBuffer() - this.worker.postMessage([id, path, buffer]) - return promise - } -} diff --git a/src/util/file-handler/SafariFileWorker.ts b/src/util/file-handler/SafariFileWorker.ts new file mode 100644 index 00000000..5f13d8e3 --- /dev/null +++ b/src/util/file-handler/SafariFileWorker.ts @@ -0,0 +1,110 @@ +// hacky type stuff +interface SecureFileSystemFileHandle extends FileSystemHandle { + readonly kind: "file" + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/createSyncAccessHandle) */ + createSyncAccessHandle(): Promise + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/createWritable) */ + createWritable( + options?: FileSystemCreateWritableOptions + ): Promise + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/getFile) */ + getFile(): Promise +} + +interface FileSystemSyncAccessHandle { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/close) */ + close(): void + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/flush) */ + flush(): void + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/getSize) */ + getSize(): number + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/read) */ + read( + buffer: AllowSharedBufferSource, + options?: FileSystemReadWriteOptions + ): number + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/truncate) */ + truncate(newSize: number): void + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/write) */ + write( + buffer: AllowSharedBufferSource, + options?: FileSystemReadWriteOptions + ): number +} + +interface FileSystemReadWriteOptions { + at?: number +} + +onmessage = async event => { + const [id, path, buffer] = event.data + const fileHandle = await getFileHandle(path) + if (!fileHandle) { + postMessage({ + id, + success: false, + error: "Couldn't locate file", + }) + return + } + const accessHandle = await fileHandle.createSyncAccessHandle() + accessHandle.write(buffer) + accessHandle.flush() + accessHandle.close() + postMessage({ + id, + success: true, + }) +} + +async function getDirectoryHandle( + path: string, + options?: FileSystemGetFileOptions, + dir?: FileSystemDirectoryHandle +): Promise { + dir ||= await navigator.storage.getDirectory() + if (path == "" || path == ".") return dir + const pathParts = resolvePath(path).split("/") + const dirname = pathParts.shift()! + try { + const dirHandle = await dir.getDirectoryHandle(dirname, options) + if (!dirHandle) return undefined + if (pathParts.length == 0) return dirHandle + return getDirectoryHandle(pathParts.join("/"), options, dirHandle) + } catch (err) { + console.error(`Failed to get directory ${path} (${dirname}): ` + err) + return undefined + } +} + +async function getFileHandle( + path: string, + options?: FileSystemGetFileOptions +): Promise { + try { + const pathParts = resolvePath(path).split("/") + const filename = pathParts.pop()! + const dirHandle = await getDirectoryHandle(pathParts.join("/"), options) + if (!dirHandle) return undefined + return (await dirHandle.getFileHandle( + filename, + options + )) as SecureFileSystemFileHandle + } catch (err) { + console.error("Failed to get file " + path + ": " + err) + return undefined + } +} + +function resolvePath(path: string): string { + let pathParts = path.split("/") + pathParts = pathParts.filter(item => item != "." && item != "") + while (pathParts.indexOf("..") > -1) { + const ind = pathParts.indexOf("..") + if (ind == 0) { + throw Error("Path" + pathParts.join("/") + "is invalid!") + } + pathParts.splice(ind - 1, 2) + } + return pathParts.join("/") +} diff --git a/src/util/file-handler/SafariFileWriter.ts b/src/util/file-handler/SafariFileWriter.ts new file mode 100644 index 00000000..6cc8eeee --- /dev/null +++ b/src/util/file-handler/SafariFileWriter.ts @@ -0,0 +1,39 @@ +export class SafariFileWriter { + private static worker = new Worker( + new URL("./SafariFileWorker.ts", import.meta.url), + { + type: "module", + } + ) + private static workID = 0 + private static map: Map< + number, + [(value: void | PromiseLike) => void, (reason?: any) => void] + > = new Map() + static { + this.worker.onmessage = event => { + const data = event.data + console.log("finished job " + data.id) + console.log(data) + if (data.success) { + this.map.get(data.id)![0]() + } else { + this.map.get(data.id)![1](data.reason) + } + this.map.delete(data.id) + } + } + + static async writeHandle(path: string, data: Blob | string) { + const id = this.workID++ + console.log("Starting work write " + path + ", id: " + id) + const promise = new Promise((resolve, reject) => + this.map.set(id, [resolve, reject]) + ) + const encode = new TextEncoder() + const buffer = + typeof data == "string" ? encode.encode(data) : await data.arrayBuffer() + this.worker.postMessage([id, path, buffer], [buffer]) + return promise + } +} diff --git a/src/util/file-handler/WebFileHandler.ts b/src/util/file-handler/WebFileHandler.ts index 73d34746..6b818551 100644 --- a/src/util/file-handler/WebFileHandler.ts +++ b/src/util/file-handler/WebFileHandler.ts @@ -6,15 +6,14 @@ import { import JSZip from "jszip" import { WaterfallManager } from "../../gui/element/WaterfallManager" import { basename, dirname, extname } from "../Path" -import { SafariFileWorker } from "../SafariFileWorker" -import { getBrowser } from "../Util" import { BaseFileHandler } from "./FileHandler" +import { SafariFileWriter } from "./SafariFileWriter" export class WebFileHandler implements BaseFileHandler { private root!: FileSystemDirectoryHandle constructor() { - if (support.adapter.native && !getBrowser().includes("Safari")) { + if (support.adapter.native) { getOriginPrivateDirectory().then(root => (this.root = root)) } else { getOriginPrivateDirectory( @@ -173,8 +172,7 @@ export class WebFileHandler implements BaseFileHandler { async hasFile(path: string): Promise { try { - await this.getFileHandle(path) - return true + return (await this.getFileHandle(path)) !== undefined } catch (err) { return false } @@ -440,7 +438,7 @@ export class WebFileHandler implements BaseFileHandler { } else { const path = await this.root.resolve(handle) if (!path) return - await SafariFileWorker.writeHandle(path.join("/"), data) + await SafariFileWriter.writeHandle(path.join("/"), data) } } }