feat: add support for external apps

And added GBA Emulator external app
This commit is contained in:
Youwen Wu 2024-02-24 19:36:19 -08:00
parent cf8062c83c
commit a6922ac448
7 changed files with 104 additions and 12 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

View file

@ -0,0 +1,26 @@
<script lang="ts">
import AppContainer from '../AppContainer.svelte'
import { Notifications } from '../../Notifications/notifications'
import { onMount } from 'svelte'
import { gbaEmulatorBootupSequence } from '../../Sequences/sequences'
const handleError = () => {
Notifications.warn(
'Failed to load the GBA Emulator app. Did you add it to the app/static/external-apps directory?'
)
}
onMount(() => {
gbaEmulatorBootupSequence()
})
</script>
<AppContainer useContainer={false} class="h-screen w-full">
<iframe
title="GBA Emulator"
src="/static/external-apps/gba-emulator/index.html"
class="w-full h-screen rounded-xl"
frameborder="0"
on:error={handleError}
/>
</AppContainer>

View file

@ -11,7 +11,7 @@
import { musicPlayerBootupSequence } from '../../Sequences/sequences' import { musicPlayerBootupSequence } from '../../Sequences/sequences'
onMount(() => { onMount(() => {
setTimeout(musicPlayerBootupSequence, 5000) musicPlayerBootupSequence()
}) })
</script> </script>

View file

@ -2,20 +2,45 @@ 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'
export const appList = { type Format = 'png' | 'jpg' | 'webp'
const resolveIconPath = (slug: keyof typeof appList, format: Format) => {
return `/static/app-icons/${slug}.${format}`
}
import GBAEmulator from './GBAEmulator/GBAEmulator.svelte'
interface AppList {
[key: string]: {
name: string
component: any
icon: string
external: boolean
}
}
export const appList: AppList = {
'camera': { 'camera': {
name: 'Camera', name: 'Camera',
component: Camera, component: Camera,
icon: '/static/app-icons/camera.png', icon: resolveIconPath('camera', 'png'),
external: false,
}, },
'media-player': { 'media-player': {
name: 'Media Player', name: 'Media Player',
component: MusicBrowser, component: MusicBrowser,
icon: '/static/app-icons/media-player.png', icon: resolveIconPath('media-player', 'png'),
external: false,
}, },
'settings': { 'settings': {
name: 'Settings', name: 'Settings',
component: Settings, component: Settings,
icon: '/static/app-icons/settings.webp', icon: resolveIconPath('settings', 'webp'),
external: false,
},
'gba-emulator': {
name: 'GBA Emulator',
component: GBAEmulator,
icon: resolveIconPath('gba-emulator', 'png'),
external: true,
}, },
} }

View file

@ -20,7 +20,7 @@ export class Notifications {
const sendToast = (duration: number) => { const sendToast = (duration: number) => {
toast.success(message, { toast.success(message, {
style: style:
'padding: 25px; font-size: 1.5rem; background-color: #15803d; color: #fafafa;', 'padding: 25px; font-size: 1.5rem; background-color: #15803d; color: #fafafa; gap: 0.5rem;',
duration, duration,
...options, ...options,
}) })
@ -45,7 +45,7 @@ export class Notifications {
const sendToast = (duration: number) => { const sendToast = (duration: number) => {
toast.error(message, { toast.error(message, {
style: style:
'padding: 25px; font-size: 1.5rem; background-color: #dc2626; color: #fafafa;', 'padding: 25px; font-size: 1.5rem; background-color: #dc2626; color: #fafafa; gap: 0.5rem;',
duration, duration,
...options, ...options,
}) })
@ -66,7 +66,7 @@ export class Notifications {
public static info(message: string, options?: NotificationOptions) { public static info(message: string, options?: NotificationOptions) {
const sendToast = (duration: number) => { const sendToast = (duration: number) => {
toast(message, { toast(message, {
style: 'padding: 25px; font-size: 1.5rem;', style: 'padding: 25px; font-size: 1.5rem; gap: 0.5rem;',
icon: InfoIcon, icon: InfoIcon,
duration, duration,
...options, ...options,
@ -88,7 +88,7 @@ export class Notifications {
const sendToast = (duration: number) => { const sendToast = (duration: number) => {
toast(message, { toast(message, {
style: style:
'padding: 25px; font-size: 1.5rem; background-color: #f59e0b; color: #fafafa;', 'padding: 25px; font-size: 1.5rem; background-color: #f59e0b; color: #fafafa; gap: 0.5rem;',
icon: WarnIcon, icon: WarnIcon,
duration, duration,
...options, ...options,

View file

@ -196,6 +196,25 @@ export const infotainmentBootupSequence = async () => {
}, 3000) }, 3000)
} }
/**
* Waits for the infotainment system to boot up before executing the given sequence.
* Designed to be used by apps who want to play a bootup sequence but not overlap with the default one.
* 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 delay? - The delay in milliseconds to wait if infotainment system is currently booting. Defaults to 5000ms
*/
const waitForInfotainmentBootup = (
sequence: () => void,
delay: number = 5000
) => {
if (!get(sequenceStore).infotainmentStartedFirstTime) {
setTimeout(sequence, delay)
} else {
sequence()
}
}
export const musicPlayerBootupSequence = async () => { export const musicPlayerBootupSequence = async () => {
if ( if (
get(sequenceStore).musicStartedFirstTime || get(sequenceStore).musicStartedFirstTime ||
@ -207,8 +226,28 @@ export const musicPlayerBootupSequence = async () => {
sequenceStore.update('musicStartedFirstTime', true) sequenceStore.update('musicStartedFirstTime', true)
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 () => {
if (
get(sequenceStore).gbaEmulatorStartedFirstTime ||
get(settingsStore).disableAnnoyances
)
return
await tick()
sequenceStore.update('gbaEmulatorStartedFirstTime', true)
waitForInfotainmentBootup(() => {
Notifications.info('Loading pirated Nintendo ROMs', {
withAudio: true,
src: getVoicePath('loading-pirated-nintendo', 'en'),
})
})
} }

View file

@ -5,11 +5,13 @@ import { writable } from 'svelte/store'
interface SequenceStoreData { interface SequenceStoreData {
infotainmentStartedFirstTime: boolean infotainmentStartedFirstTime: boolean
musicStartedFirstTime: boolean musicStartedFirstTime: boolean
gbaEmulatorStartedFirstTime: boolean
} }
let defaults: SequenceStoreData = { let defaults: SequenceStoreData = {
infotainmentStartedFirstTime: false, // for infotainment bootup sequence infotainmentStartedFirstTime: false, // for infotainment bootup sequence
musicStartedFirstTime: false, musicStartedFirstTime: false,
gbaEmulatorStartedFirstTime: false,
} }
const createSequenceStore = () => { const createSequenceStore = () => {