refactor: overhaul sequences

This commit is contained in:
Youwen Wu 2024-02-24 20:21:36 -08:00
parent cf1f014a21
commit a879519b8a
Signed by: youwen5
GPG key ID: 865658ED1FE61EC3
5 changed files with 250 additions and 191 deletions

View file

@ -1,18 +1,18 @@
<script lang="ts"> <script lang="ts">
import AppContainer from '../AppContainer.svelte' import AppContainer from "../AppContainer.svelte";
import { Notifications } from '../../Notifications/notifications' import { Notifications } from "../../Notifications/notifications";
import { onMount } from 'svelte' import { onMount } from "svelte";
import { gbaEmulatorBootupSequence } from '../../Sequences/sequences' import { gbaEmulatorBootupSequence } from "../../Sequences/sequences";
const handleError = () => { const handleError = () => {
Notifications.warn( Notifications.error(
'Failed to load the GBA Emulator app. Did you add it to the app/static/external-apps directory?' "Failed to load the GBA Emulator app. Did you add it to the app/static/external-apps directory?"
) );
} };
onMount(() => { onMount(() => {
gbaEmulatorBootupSequence() gbaEmulatorBootupSequence();
}) });
</script> </script>
<AppContainer useContainer={false} class="h-screen w-full"> <AppContainer useContainer={false} class="h-screen w-full">

View file

@ -1,46 +1,53 @@
import Camera from './Camera/Camera.svelte' import Camera from "./Camera/Camera.svelte";
import MusicBrowser from './MusicBrowser/MusicBrowser.svelte' import MusicBrowser from "./MusicBrowser/MusicBrowser.svelte";
import Settings from './Settings/Settings.svelte' import Settings from "./Settings/Settings.svelte";
type Format = 'png' | 'jpg' | 'webp' type Format = "png" | "jpg" | "webp";
const resolveIconPath = (slug: keyof typeof appList, format: Format) => { const resolveIconPath = (slug: keyof typeof appList, format: Format) => {
return `/static/app-icons/${slug}.${format}` return `/static/app-icons/${slug}.${format}`;
} };
import GBAEmulator from './GBAEmulator/GBAEmulator.svelte' import GBAEmulator from "./GBAEmulator/GBAEmulator.svelte";
import JsDoom from "./JSDoom/JSDoom.svelte";
interface AppList { interface AppList {
[key: string]: { [key: string]: {
name: string name: string;
component: any component: any;
icon: string icon: string;
external: boolean external: boolean;
} };
} }
export const appList: AppList = { export const appList: AppList = {
'camera': { camera: {
name: 'Camera', name: "Camera",
component: Camera, component: Camera,
icon: resolveIconPath('camera', 'png'), icon: resolveIconPath("camera", "png"),
external: false, external: false,
}, },
'media-player': { "media-player": {
name: 'Media Player', name: "Media Player",
component: MusicBrowser, component: MusicBrowser,
icon: resolveIconPath('media-player', 'png'), icon: resolveIconPath("media-player", "png"),
external: false, external: false,
}, },
'settings': { "gba-emulator": {
name: 'Settings', name: "GBA Emulator",
component: Settings,
icon: resolveIconPath('settings', 'webp'),
external: false,
},
'gba-emulator': {
name: 'GBA Emulator',
component: GBAEmulator, component: GBAEmulator,
icon: resolveIconPath('gba-emulator', 'png'), icon: resolveIconPath("gba-emulator", "png"),
external: true, external: true,
}, },
} jsdoom: {
name: "Doom",
component: JsDoom,
icon: resolveIconPath("jsdoom", "png"),
external: true,
},
settings: {
name: "Settings",
component: Settings,
icon: resolveIconPath("settings", "webp"),
external: false,
},
};

View file

@ -14,42 +14,47 @@ Sequences should be either event-driven or periodic. In the case of periodic
sequences, invoke them in the periodicSequence function sequences, invoke them in the periodicSequence function
*/ */
import { Notifications } from '../Notifications/notifications' import { Notifications } from "../Notifications/notifications";
import { sequenceStore } from '../stores/sequenceStore' import { sequenceStore } from "../stores/sequenceStore";
import { settingsStore } from '../stores/settingsStore' import { settingsStore } from "../stores/settingsStore";
import { get } from 'svelte/store' import { get } from "svelte/store";
import getVoicePath from '../utils/getVoicePath' import getVoicePath from "../utils/getVoicePath";
import { tick } from 'svelte' import { tick } from "svelte";
// await a "tick" (a svelte update frame) at the start of every sequence so that // await a "tick" (a svelte update frame) at the start of every sequence so that
// state is synced and no weird side effects occur // state is synced and no weird side effects occur
export const initializationSequence = async () => { export const initializationSequence = async () => {
await tick() await tick();
Notifications.info('Jankboard initialized!', { Notifications.info("Jankboard initialized!", {
withAudio: true, withAudio: true,
src: getVoicePath('jankboard-initialized', 'en'), src: getVoicePath("jankboard-initialized", "en"),
}) });
setTimeout(() => { setTimeout(() => {
if (get(settingsStore).goWoke) return if (get(settingsStore).goWoke) return;
Notifications.success('LittenOS is online', { Notifications.success("LittenOS is online", {
withAudio: true, withAudio: true,
src: getVoicePath('littenos-is-online', 'en'), src: getVoicePath("littenos-is-online", "en"),
}) });
setTimeout(() => { setTimeout(() => {
Notifications.warn('Breaching Monte Vista codebase', { Notifications.warn("Breaching Monte Vista codebase", {
withAudio: true, withAudio: true,
src: getVoicePath('breaching-monte-vista', 'en'), src: getVoicePath("breaching-monte-vista", "en"),
}) });
setTimeout(() => { setTimeout(() => {
Notifications.playAudio(getVoicePath('hello-virtual-assistant', 'en')) Notifications.playAudio(
periodicSequence() getVoicePath("hello-virtual-assistant", "en"),
}, 3000) () => {
}, 3000) sequenceStore.update("initializationComplete", true);
}, 3000) periodicSequence();
} }
);
}, 3000);
}, 3000);
}, 3000);
};
let counter = 1 let counter = 1;
/** /**
* Special sequence that plays invokes itself periodically, started automatically * Special sequence that plays invokes itself periodically, started automatically
* at the end of the initializationSequence * at the end of the initializationSequence
@ -59,7 +64,7 @@ let counter = 1
* @return void * @return void
*/ */
const periodicSequence = async () => { const periodicSequence = async () => {
await tick() await tick();
/** /**
* Returns either true or false based on the provided probability * Returns either true or false based on the provided probability
@ -69,11 +74,11 @@ const periodicSequence = async () => {
*/ */
const chance = (probability: number) => { const chance = (probability: number) => {
if (probability < 0 || probability > 1) { if (probability < 0 || probability > 1) {
throw new Error('Probability must be between 0 and 1') throw new Error("Probability must be between 0 and 1");
} }
return Math.random() < probability return Math.random() < probability;
} };
/** /**
* Calls a callback function at regular intervals. * Calls a callback function at regular intervals.
@ -82,119 +87,134 @@ const periodicSequence = async () => {
* @param callback - the function to call * @param callback - the function to call
*/ */
const every = (seconds: number, callback: () => void) => { const every = (seconds: number, callback: () => void) => {
if (counter % seconds === 0) callback() if (counter % seconds === 0) callback();
} };
// add your periodic sequences here // add your periodic sequences here
every(10, () => { every(15, () => {
if (chance(0.2)) breaching1323Sequence() if (chance(0.2)) breaching1323Sequence();
else if (chance(0.2)) breaching254Sequence() else if (chance(0.2)) breaching254Sequence();
else if (chance(0.05)) bullyingRohanSequence() else if (chance(0.05)) bullyingRohanSequence();
}) else if (chance(0.1)) bypassCoprocessorRestrictionsSequence();
});
// Dont touch // Dont touch
counter++ counter++;
setTimeout(periodicSequence, 1000) setTimeout(periodicSequence, 1000);
} };
export const criticalFailureIminentSequence = async () => { export const criticalFailureIminentSequence = async () => {
await tick() await tick();
Notifications.error('Critical robot failure imminent', { Notifications.error("Critical robot failure imminent", {
withAudio: true, withAudio: true,
src: getVoicePath('critical-robot-failure', 'en'), src: getVoicePath("critical-robot-failure", "en"),
}) });
} };
export const collisionDetectedSequence = async () => { export const collisionDetectedSequence = async () => {
await tick() await tick();
Notifications.error('Collision detected', { Notifications.error("Collision detected", {
withAudio: true, withAudio: true,
src: getVoicePath('collision-detected', 'en'), src: getVoicePath("collision-detected", "en"),
}) });
} };
export const collisionImminentSequence = async () => { export const collisionImminentSequence = async () => {
await tick() await tick();
Notifications.error('Collision imminent', { Notifications.error("Collision imminent", {
withAudio: true, withAudio: true,
src: getVoicePath('collision-imminent', 'en'), src: getVoicePath("collision-imminent", "en"),
}) });
} };
export const cruiseControlEngagedSequence = async () => { export const cruiseControlEngagedSequence = async () => {
if (get(settingsStore).disableAnnoyances) return if (get(settingsStore).disableAnnoyances) return;
await tick() await tick();
Notifications.success('Cruise control engaged', { Notifications.success("Cruise control engaged", {
withAudio: true, withAudio: true,
src: getVoicePath('cruise-control-engaged', 'en'), src: getVoicePath("cruise-control-engaged", "en"),
}) });
} };
export const retardSequence = async () => { export const retardSequence = async () => {
if (get(settingsStore).goWoke) return if (get(settingsStore).goWoke) return;
await tick() await tick();
Notifications.warn('Retard', { Notifications.warn("Retard", {
withAudio: true, withAudio: true,
src: getVoicePath('retard', 'en'), src: getVoicePath("retard", "en"),
}) });
} };
const breaching254Sequence = async () => { const breaching254Sequence = async () => {
if (get(settingsStore).disableAnnoyances) return if (get(settingsStore).disableAnnoyances) return;
await tick() await tick();
Notifications.warn('Breaching 254 mainframe', { Notifications.warn("Breaching 254 mainframe", {
withAudio: true, withAudio: true,
src: getVoicePath('breaching-254-mainframe', 'en'), src: getVoicePath("breaching-254-mainframe", "en"),
}) });
} };
const breaching1323Sequence = async () => { const breaching1323Sequence = async () => {
if (get(settingsStore).disableAnnoyances) return if (get(settingsStore).disableAnnoyances) return;
await tick() await tick();
Notifications.warn('Breaching 1323 mainframe', { Notifications.warn("Breaching 1323 mainframe", {
withAudio: true, withAudio: true,
src: getVoicePath('breaching-1323-mainframe', 'en'), src: getVoicePath("breaching-1323-mainframe", "en"),
}) });
} };
const bullyingRohanSequence = async () => { const bullyingRohanSequence = async () => {
if (get(settingsStore).disableAnnoyances) return if (get(settingsStore).disableAnnoyances) return;
await tick() await tick();
Notifications.info('Bullying Rohan', { Notifications.info("Bullying Rohan", {
withAudio: true, withAudio: true,
src: getVoicePath('bullying-rohan', 'en'), src: getVoicePath("bullying-rohan", "en"),
}) });
} };
export const userErrorDetectedSequence = async () => { export const userErrorDetectedSequence = async () => {
await tick() await tick();
Notifications.error('User error detected', { Notifications.error("User error detected", {
withAudio: true, withAudio: true,
src: getVoicePath('user-error-detected', 'en'), src: getVoicePath("user-error-detected", "en"),
}) });
} };
export const infotainmentBootupSequence = async () => { export const infotainmentBootupSequence = async () => {
if ( if (
get(sequenceStore).infotainmentStartedFirstTime || get(sequenceStore).infotainmentStartedFirstTime ||
get(settingsStore).disableAnnoyances get(settingsStore).disableAnnoyances
) )
return return;
await tick() await tick();
sequenceStore.update('infotainmentStartedFirstTime', true) const sequence = () => {
Notifications.info("Infotainment system buffering", {
Notifications.info('Infotainment system buffering', {
withAudio: true,
src: getVoicePath('infotainment-system-buffering', 'en'),
})
setTimeout(() => {
Notifications.success('Infotainment system online', {
withAudio: true, withAudio: true,
src: getVoicePath('infotainment-system-online', 'en'), src: getVoicePath("infotainment-system-buffering", "en"),
}) });
}, 3000) setTimeout(() => {
} Notifications.success("Infotainment system online", {
withAudio: true,
src: getVoicePath("infotainment-system-online", "en"),
onComplete: () => {
sequenceStore.update("infotainmentStartedFirstTime", true);
},
});
}, 3000);
};
if (!get(sequenceStore).initializationComplete) {
const unsubscribe = sequenceStore.subscribe((data) => {
if (data.initializationComplete) {
sequence();
unsubscribe();
}
});
} else {
sequence();
}
};
/** /**
* Waits for the infotainment system to boot up before executing the given sequence. * Waits for the infotainment system to boot up before executing the given sequence.
@ -202,52 +222,80 @@ export const infotainmentBootupSequence = async () => {
* If it's already booted, the sequence will be executed immediately. * If it's already booted, the sequence will be executed immediately.
* *
* @param sequence - The sequence to execute after infotainment bootup, or immediately it already booted. * @param sequence - The sequence to execute after infotainment bootup, or immediately it already booted.
* @param delay? - The delay in milliseconds to wait if infotainment system is currently booting. Defaults to 5000ms
*/ */
const waitForInfotainmentBootup = ( const waitForInfotainmentBootup = (sequence: () => void) => {
sequence: () => void,
delay: number = 5000
) => {
if (!get(sequenceStore).infotainmentStartedFirstTime) { if (!get(sequenceStore).infotainmentStartedFirstTime) {
setTimeout(sequence, delay) const unsubscribe = sequenceStore.subscribe((data) => {
if (data.infotainmentStartedFirstTime) {
sequence();
unsubscribe();
}
});
} else { } else {
sequence() sequence();
} }
} };
export const musicPlayerBootupSequence = async () => { export const musicPlayerBootupSequence = async () => {
if ( if (
get(sequenceStore).musicStartedFirstTime || get(sequenceStore).musicStartedFirstTime ||
get(settingsStore).disableAnnoyances get(settingsStore).disableAnnoyances
) )
return return;
await tick() await tick();
sequenceStore.update('musicStartedFirstTime', true) sequenceStore.update("musicStartedFirstTime", true);
waitForInfotainmentBootup(() => { waitForInfotainmentBootup(() => {
Notifications.info('Downloading copyrighted music...', { Notifications.info("Downloading copyrighted music...", {
withAudio: true, withAudio: true,
src: getVoicePath('downloading-copyrighted-music', 'en'), src: getVoicePath("downloading-copyrighted-music", "en"),
}) });
}) });
} };
export const gbaEmulatorBootupSequence = async () => { export const gbaEmulatorBootupSequence = async () => {
if ( if (
get(sequenceStore).gbaEmulatorStartedFirstTime || get(sequenceStore).gbaEmulatorStartedFirstTime ||
get(settingsStore).disableAnnoyances get(settingsStore).disableAnnoyances
) )
return return;
await tick() await tick();
sequenceStore.update('gbaEmulatorStartedFirstTime', true) sequenceStore.update("gbaEmulatorStartedFirstTime", true);
waitForInfotainmentBootup(() => { waitForInfotainmentBootup(() => {
Notifications.info('Loading pirated Nintendo ROMs', { Notifications.info("Loading pirated Nintendo ROMs", {
withAudio: true, withAudio: true,
src: getVoicePath('loading-pirated-nintendo', 'en'), src: getVoicePath("loading-pirated-nintendo", "en"),
}) });
}) });
} };
export const doomBootupSequence = async () => {
if (
get(sequenceStore).doomStartedFirstTime ||
get(settingsStore).disableAnnoyances
)
return;
await tick();
sequenceStore.update("doomStartedFirstTime", true);
waitForInfotainmentBootup(() => {
Notifications.success("Doom Engaged", {
withAudio: true,
src: getVoicePath("doom-engaged", "en"),
});
});
};
const bypassCoprocessorRestrictionsSequence = async () => {
if (get(settingsStore).disableAnnoyances) return;
await tick();
Notifications.warn("Bypassing coprocessor restrictions", {
withAudio: true,
src: getVoicePath("bypassing-coprocessor-restrictions", "en"),
});
};

View file

@ -1,34 +1,38 @@
/* in this store, put stateful variables that sequences need to access */ /* in this store, put stateful variables that sequences need to access */
import { writable } from 'svelte/store' import { writable } from "svelte/store";
interface SequenceStoreData { interface SequenceStoreData {
infotainmentStartedFirstTime: boolean initializationComplete: boolean;
musicStartedFirstTime: boolean infotainmentStartedFirstTime: boolean;
gbaEmulatorStartedFirstTime: boolean musicStartedFirstTime: boolean;
gbaEmulatorStartedFirstTime: boolean;
doomStartedFirstTime: boolean;
} }
let defaults: SequenceStoreData = { let defaults: SequenceStoreData = {
initializationComplete: false,
infotainmentStartedFirstTime: false, // for infotainment bootup sequence infotainmentStartedFirstTime: false, // for infotainment bootup sequence
musicStartedFirstTime: false, musicStartedFirstTime: false,
gbaEmulatorStartedFirstTime: false, gbaEmulatorStartedFirstTime: false,
} doomStartedFirstTime: false,
};
const createSequenceStore = () => { const createSequenceStore = () => {
const { subscribe, set, update } = writable<SequenceStoreData>(defaults) const { subscribe, set, update } = writable<SequenceStoreData>(defaults);
return { return {
subscribe, subscribe,
update: ( update: (
data: keyof SequenceStoreData, data: keyof SequenceStoreData,
newValue: SequenceStoreData[typeof data] newValue: SequenceStoreData[typeof data]
) => { ) => {
update(store => { update((store) => {
store[data] = newValue store[data] = newValue;
return store return store;
}) });
}, },
reset: () => set(defaults), reset: () => set(defaults),
} };
} };
export const sequenceStore = createSequenceStore() export const sequenceStore = createSequenceStore();

View file

@ -1,35 +1,35 @@
/* stores global app wide settings */ /* stores global app wide settings */
import { writable } from 'svelte/store' import { writable } from "svelte/store";
export interface SettingsStoreData { export interface SettingsStoreData {
disableAnnoyances: boolean disableAnnoyances: boolean;
goWoke: boolean goWoke: boolean;
fastStartup: boolean fastStartup: boolean;
} }
export const defaults: SettingsStoreData = { export const defaults: SettingsStoreData = {
disableAnnoyances: false, // disable non-critical notifications disableAnnoyances: false, // disable non-critical notifications
goWoke: false, // go woke (for showing parents or other officials where DEI has taken over), disables "offensive" sequences goWoke: false, // go woke (for showing parents or other officials where DEI has taken over), disables "offensive" sequences
fastStartup: true, // skip the loading splash screen (for development purposes. Setting this from within the app has no effect.) fastStartup: false, // skip the loading splash screen (for development purposes. Setting this from within the app has no effect.)
} };
const createSequenceStore = () => { const createSequenceStore = () => {
const { subscribe, set, update } = writable<SettingsStoreData>(defaults) const { subscribe, set, update } = writable<SettingsStoreData>(defaults);
return { return {
subscribe, subscribe,
update: ( update: (
data: keyof SettingsStoreData, data: keyof SettingsStoreData,
newValue: SettingsStoreData[typeof data] newValue: SettingsStoreData[typeof data]
) => { ) => {
update(store => { update((store) => {
store[data] = newValue store[data] = newValue;
return store return store;
}) });
}, },
reset: () => set(defaults), reset: () => set(defaults),
set: (data: SettingsStoreData) => set(data), set: (data: SettingsStoreData) => set(data),
} };
} };
export const settingsStore = createSequenceStore() export const settingsStore = createSequenceStore();