initial support for pwa mode
This commit is contained in:
@ -12,7 +12,8 @@
|
|||||||
"@tauri-apps/cli": "^1.4.0",
|
"@tauri-apps/cli": "^1.4.0",
|
||||||
"@types/audioworklet": "^0.0.49",
|
"@types/audioworklet": "^0.0.49",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^4.4.5"
|
"vite": "^4.4.5",
|
||||||
|
"vite-plugin-pwa": "^0.16.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-javascript": "^6.1.9",
|
"@codemirror/lang-javascript": "^6.1.9",
|
||||||
|
|||||||
@ -171,6 +171,7 @@ export class UserAPI {
|
|||||||
this.app.interface.error_line.innerHTML = errorMessage;
|
this.app.interface.error_line.innerHTML = errorMessage;
|
||||||
this.app.interface.error_line.style.color = "color-red-800";
|
this.app.interface.error_line.style.color = "color-red-800";
|
||||||
this.app.interface.error_line.classList.remove("hidden");
|
this.app.interface.error_line.classList.remove("hidden");
|
||||||
|
// @ts-ignore
|
||||||
this.errorTimeoutID = setTimeout(
|
this.errorTimeoutID = setTimeout(
|
||||||
() => this.app.interface.error_line.classList.add("hidden"),
|
() => this.app.interface.error_line.classList.add("hidden"),
|
||||||
2000
|
2000
|
||||||
@ -184,6 +185,7 @@ export class UserAPI {
|
|||||||
this.app.interface.error_line.innerHTML = message as string;
|
this.app.interface.error_line.innerHTML = message as string;
|
||||||
this.app.interface.error_line.style.color = "white";
|
this.app.interface.error_line.style.color = "white";
|
||||||
this.app.interface.error_line.classList.remove("hidden");
|
this.app.interface.error_line.classList.remove("hidden");
|
||||||
|
// @ts-ignore
|
||||||
this.printTimeoutID = setTimeout(
|
this.printTimeoutID = setTimeout(
|
||||||
() => this.app.interface.error_line.classList.add("hidden"),
|
() => this.app.interface.error_line.classList.add("hidden"),
|
||||||
4000
|
4000
|
||||||
|
|||||||
@ -81,6 +81,7 @@ export const blinkScript = (
|
|||||||
_drawBlinker(shiftAmount);
|
_drawBlinker(shiftAmount);
|
||||||
|
|
||||||
// Save timeout ID for later clearing
|
// Save timeout ID for later clearing
|
||||||
|
// @ts-ignore
|
||||||
app.blinkTimeouts[shiftAmount] = setTimeout(() => {
|
app.blinkTimeouts[shiftAmount] = setTimeout(() => {
|
||||||
_clearBlinker(shiftAmount);
|
_clearBlinker(shiftAmount);
|
||||||
// Clear the canvas before drawing new blinkers
|
// Clear the canvas before drawing new blinkers
|
||||||
|
|||||||
@ -578,8 +578,7 @@ export class MidiConnection {
|
|||||||
if (typeof output === "number") {
|
if (typeof output === "number") {
|
||||||
if (output < 0 || output >= this.midiOutputs.length) {
|
if (output < 0 || output >= this.midiOutputs.length) {
|
||||||
console.error(
|
console.error(
|
||||||
`Invalid MIDI output index. Index must be in the range 0-${
|
`Invalid MIDI output index. Index must be in the range 0-${this.midiOutputs.length - 1
|
||||||
this.midiOutputs.length - 1
|
|
||||||
}.`
|
}.`
|
||||||
);
|
);
|
||||||
return this.currentOutputIndex;
|
return this.currentOutputIndex;
|
||||||
@ -608,8 +607,7 @@ export class MidiConnection {
|
|||||||
if (typeof input === "number") {
|
if (typeof input === "number") {
|
||||||
if (input < 0 || input >= this.midiInputs.length) {
|
if (input < 0 || input >= this.midiInputs.length) {
|
||||||
console.error(
|
console.error(
|
||||||
`Invalid MIDI input index. Index must be in the range 0-${
|
`Invalid MIDI input index. Index must be in the range 0-${this.midiInputs.length - 1
|
||||||
this.midiInputs.length - 1
|
|
||||||
}.`
|
}.`
|
||||||
);
|
);
|
||||||
return -1;
|
return -1;
|
||||||
@ -676,6 +674,7 @@ export class MidiConnection {
|
|||||||
delete this.scheduledNotes[noteNumber];
|
delete this.scheduledNotes[noteNumber];
|
||||||
}, (duration - 0.02) * 1000);
|
}, (duration - 0.02) * 1000);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
this.scheduledNotes[noteNumber] = timeoutId;
|
this.scheduledNotes[noteNumber] = timeoutId;
|
||||||
} else {
|
} else {
|
||||||
console.error("MIDI output not available.");
|
console.error("MIDI output not available.");
|
||||||
|
|||||||
@ -1,229 +1,230 @@
|
|||||||
import { noteNameToMidi } from "zifferjs";
|
import { noteNameToMidi } from "zifferjs";
|
||||||
import { type UserAPI } from "./API";
|
import { type UserAPI } from "./API";
|
||||||
import { Player } from "./classes/ZPlayer";
|
import { Player } from "./classes/ZPlayer";
|
||||||
export {};
|
export { };
|
||||||
|
|
||||||
// Extend String prototype
|
// Extend String prototype
|
||||||
declare global {
|
declare global {
|
||||||
interface String {
|
interface String {
|
||||||
speak(): void;
|
speak(): void;
|
||||||
rate(speed: number): string;
|
rate(speed: number): string;
|
||||||
pitch(pitch: number): string;
|
pitch(pitch: number): string;
|
||||||
volume(volume: number): string;
|
volume(volume: number): string;
|
||||||
voice(voice: number): string;
|
voice(voice: number): string;
|
||||||
lang(language: string): string;
|
lang(language: string): string;
|
||||||
options(): SpeechOptions;
|
options(): SpeechOptions;
|
||||||
z(): Player;
|
z(): Player;
|
||||||
z0(): Player;
|
z0(): Player;
|
||||||
z1(): Player;
|
z1(): Player;
|
||||||
z2(): Player;
|
z2(): Player;
|
||||||
z3(): Player;
|
z3(): Player;
|
||||||
z4(): Player;
|
z4(): Player;
|
||||||
z5(): Player;
|
z5(): Player;
|
||||||
z6(): Player;
|
z6(): Player;
|
||||||
z7(): Player;
|
z7(): Player;
|
||||||
z8(): Player;
|
z8(): Player;
|
||||||
z9(): Player;
|
z9(): Player;
|
||||||
z10(): Player;
|
z10(): Player;
|
||||||
z11(): Player;
|
z11(): Player;
|
||||||
z12(): Player;
|
z12(): Player;
|
||||||
z13(): Player;
|
z13(): Player;
|
||||||
z14(): Player;
|
z14(): Player;
|
||||||
z15(): Player;
|
z15(): Player;
|
||||||
z16(): Player;
|
z16(): Player;
|
||||||
note(): number;
|
note(): number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isJsonString = (str: string):boolean => {
|
const isJsonString = (str: string): boolean => {
|
||||||
return str[0] === '{' && str[str.length - 1] === '}'
|
return str[0] === '{' && str[str.length - 1] === '}'
|
||||||
}
|
}
|
||||||
|
|
||||||
const stringObject = (str: string, params: object) => {
|
const stringObject = (str: string, params: object) => {
|
||||||
if(isJsonString(str)) {
|
if (isJsonString(str)) {
|
||||||
const obj = JSON.parse(str);
|
const obj = JSON.parse(str);
|
||||||
return JSON.stringify({...obj, ...params});
|
return JSON.stringify({ ...obj, ...params });
|
||||||
} else {
|
} else {
|
||||||
return JSON.stringify({...params, text: str});
|
return JSON.stringify({ ...params, text: str });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeStringExtensions = (api: UserAPI) => {
|
export const makeStringExtensions = (api: UserAPI) => {
|
||||||
String.prototype.speak = function () {
|
String.prototype.speak = function() {
|
||||||
const options = JSON.parse(this.valueOf());
|
const options = JSON.parse(this.valueOf());
|
||||||
new Speaker({ ...options, text: options.text }).speak().then(() => {
|
new Speaker({ ...options, text: options.text }).speak().then(() => {
|
||||||
// Done
|
// Done
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
console.log("Error speaking:", e);
|
console.log("Error speaking:", e);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.rate = function (speed: number) {
|
String.prototype.rate = function(speed: number) {
|
||||||
return stringObject(this.valueOf(), {rate: speed});
|
return stringObject(this.valueOf(), { rate: speed });
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.pitch = function (pitch: number) {
|
String.prototype.pitch = function(pitch: number) {
|
||||||
return stringObject(this.valueOf(), {pitch: pitch});
|
return stringObject(this.valueOf(), { pitch: pitch });
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.lang = function (language: string) {
|
String.prototype.lang = function(language: string) {
|
||||||
return stringObject(this.valueOf(),{lang: language});
|
return stringObject(this.valueOf(), { lang: language });
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.volume = function (volume: number) {
|
String.prototype.volume = function(volume: number) {
|
||||||
return stringObject(this.valueOf(), {volume: volume});
|
return stringObject(this.valueOf(), { volume: volume });
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.voice = function (voice: number) {
|
String.prototype.voice = function(voice: number) {
|
||||||
return stringObject(this.valueOf(), {voice: voice});
|
return stringObject(this.valueOf(), { voice: voice });
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z = function (options: {[key: string]: any} = {}) {
|
String.prototype.z = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z(this.valueOf(), options);
|
return api.z(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z0 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z0 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z0(this.valueOf(), options);
|
return api.z0(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z1 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z1 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z1(this.valueOf(), options);
|
return api.z1(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z2 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z2 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z2(this.valueOf(), options);
|
return api.z2(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z3 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z3 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z3(this.valueOf(), options);
|
return api.z3(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z4 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z4 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z4(this.valueOf(), options);
|
return api.z4(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z5 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z5 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z5(this.valueOf(), options);
|
return api.z5(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z6 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z6 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z6(this.valueOf(), options);
|
return api.z6(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z7 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z7 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z7(this.valueOf(), options);
|
return api.z7(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z8 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z8 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z8(this.valueOf(), options);
|
return api.z8(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z9 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z9 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z9(this.valueOf(), options);
|
return api.z9(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z10 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z10 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z10(this.valueOf(), options);
|
return api.z10(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z11 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z11 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z11(this.valueOf(), options);
|
return api.z11(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z12 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z12 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z12(this.valueOf(), options);
|
return api.z12(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z13 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z13 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z13(this.valueOf(), options);
|
return api.z13(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z14 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z14 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z14(this.valueOf(), options);
|
return api.z14(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z15 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z15 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z15(this.valueOf(), options);
|
return api.z15(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.z16 = function (options: {[key: string]: any} = {}) {
|
String.prototype.z16 = function(options: { [key: string]: any } = {}) {
|
||||||
return api.z16(this.valueOf(), options);
|
return api.z16(this.valueOf(), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
String.prototype.note = function () {
|
String.prototype.note = function() {
|
||||||
try {
|
try {
|
||||||
return parseInt(this.valueOf());
|
return parseInt(this.valueOf());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return noteNameToMidi(this.valueOf());
|
return noteNameToMidi(this.valueOf());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type SpeechOptions = {
|
type SpeechOptions = {
|
||||||
text?: string;
|
text?: string;
|
||||||
rate?: number;
|
rate?: number;
|
||||||
pitch?: number;
|
pitch?: number;
|
||||||
volume?: number;
|
volume?: number;
|
||||||
voice?: number;
|
voice?: number;
|
||||||
lang?: string;
|
lang?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let speakerTimeout: number;
|
let speakerTimeout: number;
|
||||||
|
|
||||||
export class Speaker {
|
export class Speaker {
|
||||||
constructor(
|
constructor(
|
||||||
public options: SpeechOptions
|
public options: SpeechOptions
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
speak = () => {
|
speak = () => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
if (this.options.text) {
|
if (this.options.text) {
|
||||||
const synth = window.speechSynthesis;
|
const synth = window.speechSynthesis;
|
||||||
if(synth.speaking) synth.cancel();
|
if (synth.speaking) synth.cancel();
|
||||||
|
|
||||||
const utterance = new SpeechSynthesisUtterance(this.options.text);
|
const utterance = new SpeechSynthesisUtterance(this.options.text);
|
||||||
utterance.rate = this.options.rate || 1;
|
utterance.rate = this.options.rate || 1;
|
||||||
utterance.pitch = this.options.pitch || 1;
|
utterance.pitch = this.options.pitch || 1;
|
||||||
utterance.volume = this.options.volume || 1;
|
utterance.volume = this.options.volume || 1;
|
||||||
if (this.options.voice) {
|
if (this.options.voice) {
|
||||||
utterance.voice = synth.getVoices()[this.options.voice];
|
utterance.voice = synth.getVoices()[this.options.voice];
|
||||||
}
|
}
|
||||||
if(this.options.lang) {
|
if (this.options.lang) {
|
||||||
// Check if language has country code
|
// Check if language has country code
|
||||||
if (this.options.lang.length === 2) {
|
if (this.options.lang.length === 2) {
|
||||||
utterance.lang = `${this.options.lang}-${this.options.lang.toUpperCase()}`
|
utterance.lang = `${this.options.lang}-${this.options.lang.toUpperCase()}`
|
||||||
} else if (this.options.lang.length === 5) {
|
} else if (this.options.lang.length === 5) {
|
||||||
utterance.lang = this.options.lang;
|
utterance.lang = this.options.lang;
|
||||||
} else {
|
} else {
|
||||||
// Fallback to en us
|
// Fallback to en us
|
||||||
utterance.lang = 'en-US';
|
utterance.lang = 'en-US';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
utterance.onend = () => {
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
utterance.onerror = (error) => {
|
|
||||||
reject(error);
|
|
||||||
};
|
|
||||||
|
|
||||||
if(synth.speaking) {
|
|
||||||
// Cancel again?
|
|
||||||
synth.cancel();
|
|
||||||
// Set timeout
|
|
||||||
if(speakerTimeout) clearTimeout(speakerTimeout);
|
|
||||||
speakerTimeout = setTimeout(() => {
|
|
||||||
synth.speak(utterance);
|
|
||||||
}, 200);
|
|
||||||
} else {
|
|
||||||
synth.speak(utterance);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
reject("No text provided");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
utterance.onend = () => {
|
||||||
}
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
utterance.onerror = (error) => {
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (synth.speaking) {
|
||||||
|
// Cancel again?
|
||||||
|
synth.cancel();
|
||||||
|
// Set timeout
|
||||||
|
if (speakerTimeout) clearTimeout(speakerTimeout);
|
||||||
|
// @ts-ignore
|
||||||
|
speakerTimeout = setTimeout(() => {
|
||||||
|
synth.speak(utterance);
|
||||||
|
}, 200);
|
||||||
|
} else {
|
||||||
|
synth.speak(utterance);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
reject("No text provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
|
||||||
// import * as mdPlugin from 'vite-plugin-markdown';
|
// import * as mdPlugin from 'vite-plugin-markdown';
|
||||||
|
|
||||||
export default defineConfig(({ command, mode, ssrBuild }) => {
|
export default defineConfig(({ command, mode, ssrBuild }) => {
|
||||||
if (command === "serve") {
|
if (command === "serve") {
|
||||||
return {
|
return {
|
||||||
|
plugins: [
|
||||||
|
VitePWA({ registerType: 'autoUpdate' })
|
||||||
|
],
|
||||||
assetsInclude: ["**/*.md"],
|
assetsInclude: ["**/*.md"],
|
||||||
server: {
|
server: {
|
||||||
port: 8000,
|
port: 8000,
|
||||||
@ -12,6 +17,9 @@ export default defineConfig(({ command, mode, ssrBuild }) => {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
|
plugins: [
|
||||||
|
VitePWA({ registerType: 'autoUpdate' })
|
||||||
|
],
|
||||||
chunkSizeWarningLimit: 1600 * 2,
|
chunkSizeWarningLimit: 1600 * 2,
|
||||||
build: {
|
build: {
|
||||||
outDir: "dist",
|
outDir: "dist",
|
||||||
|
|||||||
Reference in New Issue
Block a user