feat: add support for external apps
And added GBA Emulator external app
This commit is contained in:
parent
cf8062c83c
commit
a6922ac448
7 changed files with 104 additions and 12 deletions
BIN
app/static/app-icons/gba-emulator.png
Normal file
BIN
app/static/app-icons/gba-emulator.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 498 KiB |
26
client/src/lib/Apps/GBAEmulator/GBAEmulator.svelte
Normal file
26
client/src/lib/Apps/GBAEmulator/GBAEmulator.svelte
Normal 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>
|
|
@ -11,7 +11,7 @@
|
|||
import { musicPlayerBootupSequence } from '../../Sequences/sequences'
|
||||
|
||||
onMount(() => {
|
||||
setTimeout(musicPlayerBootupSequence, 5000)
|
||||
musicPlayerBootupSequence()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -2,20 +2,45 @@ import Camera from './Camera/Camera.svelte'
|
|||
import MusicBrowser from './MusicBrowser/MusicBrowser.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': {
|
||||
name: 'Camera',
|
||||
component: Camera,
|
||||
icon: '/static/app-icons/camera.png',
|
||||
icon: resolveIconPath('camera', 'png'),
|
||||
external: false,
|
||||
},
|
||||
'media-player': {
|
||||
name: 'Media Player',
|
||||
component: MusicBrowser,
|
||||
icon: '/static/app-icons/media-player.png',
|
||||
icon: resolveIconPath('media-player', 'png'),
|
||||
external: false,
|
||||
},
|
||||
'settings': {
|
||||
name: '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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ export class Notifications {
|
|||
const sendToast = (duration: number) => {
|
||||
toast.success(message, {
|
||||
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,
|
||||
...options,
|
||||
})
|
||||
|
@ -45,7 +45,7 @@ export class Notifications {
|
|||
const sendToast = (duration: number) => {
|
||||
toast.error(message, {
|
||||
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,
|
||||
...options,
|
||||
})
|
||||
|
@ -66,7 +66,7 @@ export class Notifications {
|
|||
public static info(message: string, options?: NotificationOptions) {
|
||||
const sendToast = (duration: number) => {
|
||||
toast(message, {
|
||||
style: 'padding: 25px; font-size: 1.5rem;',
|
||||
style: 'padding: 25px; font-size: 1.5rem; gap: 0.5rem;',
|
||||
icon: InfoIcon,
|
||||
duration,
|
||||
...options,
|
||||
|
@ -88,7 +88,7 @@ export class Notifications {
|
|||
const sendToast = (duration: number) => {
|
||||
toast(message, {
|
||||
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,
|
||||
duration,
|
||||
...options,
|
||||
|
|
|
@ -196,6 +196,25 @@ export const infotainmentBootupSequence = async () => {
|
|||
}, 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 () => {
|
||||
if (
|
||||
get(sequenceStore).musicStartedFirstTime ||
|
||||
|
@ -207,8 +226,28 @@ export const musicPlayerBootupSequence = async () => {
|
|||
|
||||
sequenceStore.update('musicStartedFirstTime', true)
|
||||
|
||||
Notifications.info('Downloading copyrighted music...', {
|
||||
withAudio: true,
|
||||
src: getVoicePath('downloading-copyrighted-music', 'en'),
|
||||
waitForInfotainmentBootup(() => {
|
||||
Notifications.info('Downloading copyrighted music...', {
|
||||
withAudio: true,
|
||||
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'),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,11 +5,13 @@ import { writable } from 'svelte/store'
|
|||
interface SequenceStoreData {
|
||||
infotainmentStartedFirstTime: boolean
|
||||
musicStartedFirstTime: boolean
|
||||
gbaEmulatorStartedFirstTime: boolean
|
||||
}
|
||||
|
||||
let defaults: SequenceStoreData = {
|
||||
infotainmentStartedFirstTime: false, // for infotainment bootup sequence
|
||||
musicStartedFirstTime: false,
|
||||
gbaEmulatorStartedFirstTime: false,
|
||||
}
|
||||
|
||||
const createSequenceStore = () => {
|
||||
|
|
Loading…
Reference in a new issue