improve typing

This commit is contained in:
SArpnt 2024-04-23 17:16:19 -04:00
parent 094ae74b05
commit 13ca78419b
Signed by: SArpnt
SSH key fingerprint: SHA256:iDMeic8KkqqEsN4wODlgsk1d/oW1ojZ/cu/MEWyfLBw
10 changed files with 79 additions and 65 deletions

View file

@ -1,4 +1,4 @@
import { f32ToRound10, isPlainObject, Song, SongMode } from "./common.ts";
import { f32ToRound10, isPlainObject, Song, SongMode, StrongPartial } from "./common.ts";
import elements from "./elements.ts";
import { fromUrlData, setUrlData } from "./url/mod.ts";
import { CodeEditor } from "./code-editor/mod.ts";
@ -18,7 +18,7 @@ if (globalThis.loadLibrary !== false) {
}
handleWindowResize(true);
document.defaultView.addEventListener("resize", handleWindowResize);
document.defaultView.addEventListener("resize", () => handleWindowResize(false));
let volume: number;
let timeUnit: TimeUnit;
@ -127,7 +127,6 @@ function handleMessage(e: MessageEvent<any>) {
}
}
}
let saveData = null;
function refreshCode() {
if (audioWorklet) {
@ -138,7 +137,7 @@ function handleWindowResize(force?: boolean) {
autoSizeCanvas(force);
}
function autoSizeCanvas(force: boolean = false) {
if (!elements.canvas.dataset.forcedWidth) {
if (!elements.canvas.dataset["forcedWidth"]) {
const innerWidth = window.innerWidth;
// 768 is halfway between 512 and 1024, 3 added for outline
if (innerWidth >= 768 + 3) {
@ -179,7 +178,7 @@ function getSong(): Song {
return { code: codeEditor.getCode(), ...songData };
}
function setSong(songData?: Partial<Song>, play = true) {
function setSong(songData?: StrongPartial<Song>, play = true) {
let code, sampleRate, mode;
if (songData) {
({ code, sampleRate, mode } = songData);
@ -193,7 +192,7 @@ function setSong(songData?: Partial<Song>, play = true) {
togglePlay(true);
}
}
function applyPlaybackMode(playbackMode) {
function applyPlaybackMode(playbackMode: SongMode) {
setPlaybackMode(playbackMode);
elements.playbackMode.value = playbackMode;
}
@ -230,7 +229,7 @@ function resetTime() {
elements.canvasTogglePlay.classList.add("canvas-toggleplay-show");
}
}
function setByteSample(value, send = true, clear = false) {
function setByteSample(value: number, send = true, clear = false) {
if (audioWorklet && isFinite(value)) {
byteSample = value;
updateCounterValue();
@ -240,7 +239,7 @@ function setByteSample(value, send = true, clear = false) {
osc.moveTimeCursor(byteSample, playSpeed > 0);
}
}
function setPlaybackMode(playbackMode) {
function setPlaybackMode(playbackMode: SongMode) {
if (audioWorklet) {
songData.mode = playbackMode;
if (globalThis.useUrlData !== false) {
@ -252,7 +251,8 @@ function setPlaybackMode(playbackMode) {
function setSampleRate(sampleRate: number) {
if (audioWorklet) {
const rate = Math.min(Math.max(Math.fround(sampleRate), 2), 2 ** 24 - 1);
elements.sampleRate.value = f32ToRound10(rate);
// implicit cast
elements.sampleRate.value = f32ToRound10(rate) as unknown as string;
songData.sampleRate = rate;
// TODO if funcbeat change time so that sec stays the same
audioWorklet.port.postMessage({ songData, updateSampleRatio: true });
@ -290,9 +290,10 @@ function timeCursorShouldBeVisible(): boolean {
}
function updateCounterValue() {
elements.timeValue.placeholder = convertToUnit(byteSample);
// implicit cast
elements.timeValue.placeholder = convertToUnit(byteSample) as unknown as string;
}
function convertFromUnit(value, unit = timeUnit) {
function convertFromUnit(value: number, unit = timeUnit) {
switch (unit) {
case "t":
return value;
@ -309,7 +310,7 @@ function convertToUnit(value: number, unit = timeUnit) {
return (value / songData.sampleRate).toFixed(3);
}
}
function setTimeUnit(value: number, userInput = true) {
function setTimeUnit(value?: number | TimeUnit, userInput = true) {
if (value !== undefined) {
if (typeof value === "number") {
timeUnit = timeUnits[value];
@ -352,7 +353,8 @@ function changeScale(amount: number) {
function setVolume(save = true, value?: number) {
if (value !== undefined) {
volume = value;
elements.volume.value = volume;
// implicit cast
elements.volume.value = volume as unknown as string;
}
volume = elements.volume.valueAsNumber;
@ -373,22 +375,23 @@ function loadDefaultSettings() {
setTimeUnit(undefined, false);
}
function loadSettings(): Settings {
if (localStorage.settings && globalThis.useLocalStorage !== false) {
if (globalThis.useLocalStorage !== false && localStorage["settings"]) {
let settings: {
drawSettings: typeof osc.settings;
volume: typeof volume;
timeUnit: typeof timeUnit;
drawSettings?: typeof osc.settings;
volume?: typeof volume;
timeUnit?: typeof timeUnit;
};
try {
settings = JSON.parse(localStorage.settings);
// FIXME: this should be validated
settings = JSON.parse(localStorage["settings"]);
} catch (err) {
console.error("Couldn't load settings!", localStorage.settings);
console.error("Couldn't load settings!", localStorage["settings"]);
localStorage.clear();
loadDefaultSettings();
return;
}
if (Object.hasOwnProperty.call(settings, "drawSettings")) {
if (settings.drawSettings) {
osc.settings = settings.drawSettings;
} else {
osc.settings.scale = 5;
@ -396,7 +399,7 @@ function loadSettings(): Settings {
setVolume(false, settings.volume);
if (Object.hasOwnProperty.call(settings, "timeUnit")) {
if (settings.timeUnit) {
setTimeUnit(settings.timeUnit, false);
} else {
setTimeUnit(undefined, false);
@ -407,7 +410,7 @@ function loadSettings(): Settings {
}
function saveSettings() {
if (globalThis.useLocalStorage !== false) {
localStorage.settings = JSON.stringify({
localStorage["settings"] = JSON.stringify({
drawSettings: osc.settings,
volume,
timeUnit,

View file

@ -18,6 +18,9 @@ class CodeEditor {
const codemirror = c.replace(editCallback, textarea);
this.getCode = () => codemirror.state.doc.toString();
this.setCode = v =>
// codemirror type is wrong
// TODO pull request fix
// @ts-ignore
codemirror.dispatch({
changes: { from: 0, to: codemirror.state.doc.length, insert: v },
});

View file

@ -19,8 +19,8 @@ function initTextarea(editCallback: () => void, textarea: HTMLTextAreaElement) {
let line = 0;
for (let c = 0; ; line++) {
c += lines[line].length;
if (c > char) 1;
break;
if (c > char)
break;
}
return line;
};

View file

@ -1,3 +1,5 @@
type StrongPartial<T> = { [P in keyof T]?: T[P] | undefined };
function f32ToRound10(value: number): number {
// this is stupid but it works for numbers less
// than 10 digits on one side of the decimal point
@ -33,4 +35,4 @@ type Song = {
mode: SongMode;
};
export { f32ToRound10, isPlainObject, SmallSongMode, SongMode, Song };
export { StrongPartial, f32ToRound10, isPlainObject, SmallSongMode, SongMode, Song };

View file

@ -1,19 +1,19 @@
export default {
main: document.getElementsByTagName("main")[0],
error: document.getElementById("error")!,
canvas: document.getElementById("canvas-main")!, // this is the only shared element
canvas: document.getElementById("canvas-main") as HTMLCanvasElement, // this is the only shared element
timeCursor: document.getElementById("canvas-timecursor")!,
timeUnit: document.getElementById("control-time-unit")!,
timeUnitLabel: document.getElementById("control-time-unit-label")!,
timeValue: document.getElementById("control-time-value")!,
timeValue: document.getElementById("control-time-value") as HTMLInputElement,
scaleUp: document.getElementById("control-scaleup")!,
scaleDown: document.getElementById("control-scaledown")!,
scaleUp: document.getElementById("control-scaleup") as HTMLButtonElement,
scaleDown: document.getElementById("control-scaledown") as HTMLButtonElement,
playbackMode: document.getElementById("control-song-mode")!,
sampleRate: document.getElementById("control-sample-rate")!,
volume: document.getElementById("control-volume")!,
playbackMode: document.getElementById("control-song-mode") as HTMLSelectElement,
sampleRate: document.getElementById("control-sample-rate") as HTMLInputElement,
volume: document.getElementById("control-volume") as HTMLInputElement,
canvasTogglePlay: document.getElementById("canvas-toggleplay")!,

View file

@ -7,27 +7,27 @@ const nameids: [string, (show: boolean) => void, ...string[]][] = [
[
"timeControls",
show => {
elements.controls.dataset.timeControlsDisabled = !show as unknown as string;
elements.canvasContainer.dataset.disabled = !show as unknown as string;
elements.controls.dataset["timeControlsDisabled"] = !show as unknown as string;
elements.canvasContainer.dataset["disabled"] = !show as unknown as string;
},
"canvas-toggleplay",
],
[
"playbackControls",
show => {
elements.controls.dataset.playbackControlsDisabled = !show as unknown as string;
elements.controls.dataset["playbackControlsDisabled"] = !show as unknown as string;
},
],
[
"viewControls",
show => {
elements.controls.dataset.viewControlsDisabled = !show as unknown as string;
elements.controls.dataset["viewControlsDisabled"] = !show as unknown as string;
},
],
[
"songControls",
show => {
elements.controls.dataset.songControlsDisabled = !show as unknown as string;
elements.controls.dataset["songControlsDisabled"] = !show as unknown as string;
},
],
["error", () => {}, "error"],
@ -41,9 +41,9 @@ window.addEventListener(
if (isPlainObject(e.data)) {
const data: { [index: string]: any } = e.data;
// show/hide elements
if (isPlainObject(data.show)) {
if (isPlainObject(data["show"])) {
for (const [name, fn, ...ids] of nameids) {
const show: { [index: string]: any } = data.show;
const show: { [index: string]: any } = data["show"];
if (show[name] !== undefined) {
if (show[name]) {
fn(true);
@ -60,21 +60,21 @@ window.addEventListener(
}
}
if (data.forceScopeWidth !== undefined && elements.canvas) {
if (typeof data.forceScopeWidth === "number") {
elements.canvas.dataset.forcedWidth = true as unknown as string;
setCanvasWidth(data.forceScopeWidth);
if (data["forceScopeWidth"] !== undefined && elements.canvas) {
if (typeof data["forceScopeWidth"] === "number") {
elements.canvas.dataset["forcedWidth"] = true as unknown as string;
setCanvasWidth(data["forceScopeWidth"]);
} else {
delete elements.canvas.dataset.forcedWidth;
delete elements.canvas.dataset["forcedWidth"];
autoSizeCanvas();
}
}
if (data.getSong) {
if (data["getSong"]) {
window.parent.postMessage({ song: getSong() }, "*");
}
if (isPlainObject(data.setSong)) {
setSong(data.setSong, false);
if (isPlainObject(data["setSong"])) {
setSong(data["setSong"], false);
}
}
},

View file

@ -53,11 +53,11 @@ function createEntryElem(entry: Entry) {
) as HTMLElement[];
if (songElems.length) {
for (const elem of songElems) {
const songData = elem.dataset.songData ? JSON.parse(elem.dataset.songData) : {};
const songData = elem.dataset["songData"] ? JSON.parse(elem.dataset["songData"]) : {};
const onclick = elem.dataset.hasOwnProperty("codeFile")
? () =>
fetch(`/library/${elem.dataset.codeFile}`)
fetch(`/library/${elem.dataset["codeFile"]}`)
.then(response => response.text())
.then(code => setSong(Object.assign(songData, { code })))
: () => setSong(Object.assign({ code: elem.innerText }, songData));
@ -233,9 +233,8 @@ function createEntryElem(entry: Entry) {
if (entry.children) {
let childrenElem = document.createElement("ul");
for (let i = 0, len = entry.children.length; i < len; ++i) {
let childEntry = entry.children[i];
childrenElem.append(createEntryElem(childEntry));
for (const child of entry.children) {
childrenElem.append(createEntryElem(child));
}
entryElem.append(childrenElem);
}

View file

@ -28,10 +28,14 @@ function into89(data: Uint8Array, bitIndex = 0): string {
while (true) {
//const byteIndex = bitIndex >> 3;
//const bitOffset = bitIndex & 7;
let bigint = 0n;
// collects 123 bits of data
let chunkInt = 0n;
for (let i = 0; i < 123; i++) {
if (bitIndex >> 3 < data.length) {
bigint += BigInt(!!(data[bitIndex >> 3] & (1 << (bitIndex & 7)))) << BigInt(i);
// if bitIndex is negative, this will implicitly cast
const currentByte = data[bitIndex >> 3] as number;
const bitOfByte = 1 << (bitIndex & 7);
chunkInt += BigInt(!!(currentByte & bitOfByte)) << BigInt(i);
bitIndex++;
} else {
// length kept being wrong, i gave up on it
@ -45,17 +49,17 @@ function into89(data: Uint8Array, bitIndex = 0): string {
// bigint /= 89n;
//}
//// unreachable
while (bigint) {
coded += tochar[Number(bigint % 89n)];
bigint /= 89n;
while (chunkInt) {
coded += tochar[Number(chunkInt % 89n)];
chunkInt /= 89n;
}
return coded;
}
}
//bigint = BigInt.asUintN(123, bigint << BigInt(bitOffset));
for (let j = 0; j < 19; j++) {
coded += tochar[Number(bigint % 89n)];
bigint /= 89n;
coded += tochar[Number(chunkInt % 89n)];
chunkInt /= 89n;
}
}
}
@ -82,13 +86,13 @@ function from89(coded: string, bitIndex = 0 /*, byteUp = true*/): Uint8Array {
if (coded.length) {
const chunks = coded.match(/.{1,19}/g) as RegExpMatchArray;
for (const chunk of chunks) {
let bigint = 0n;
let chunkInt = 0n;
for (const letter of chunk.split("").reverse()) {
bigint *= 89n;
bigint += fromchar[letter];
chunkInt *= 89n;
chunkInt += fromchar[letter];
}
for (let i = 123; i; i--) {
let bit = Number(bigint & 1n);
const bit = Number(chunkInt & 1n);
currentByte |= bit << (bitIndex & 7);
if ((bitIndex & 7) === 7) {
data[bitIndex >> 3] = currentByte;
@ -101,7 +105,7 @@ function from89(coded: string, bitIndex = 0 /*, byteUp = true*/): Uint8Array {
}
return data;
}
bigint >>= 1n;
chunkInt >>= 1n;
}
}
}

View file

@ -30,6 +30,7 @@ async function fromUrlData(hash: string): Promise<Song | undefined> {
let compressedCode;
if (v === "6") {
data = from89(hash.substring(2), 12);
// if data[1] is undefined the implicit cast to 0 is fine
data[1] = (data[1] >> 4) | 0x40;
compressedCode = data.subarray(6);
} else {

View file

@ -9,7 +9,9 @@
"strict": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
// as far as i can tell there is no way to define a type that indicates a
// valid index for some value. this makes this pretty much impossible to use
//"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"types": ["jest"]