feat: add more sequences, loading splash, and settings
This commit is contained in:
parent
3508746e07
commit
cfdb9094ce
21 changed files with 530 additions and 20 deletions
10
client/package-lock.json
generated
10
client/package-lock.json
generated
|
@ -16,6 +16,7 @@
|
||||||
"svelte-french-toast": "^1.2.0"
|
"svelte-french-toast": "^1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@svelte-plugins/tooltips": "^3.0.0",
|
||||||
"@sveltejs/adapter-static": "^3.0.1",
|
"@sveltejs/adapter-static": "^3.0.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||||
"@tsconfig/svelte": "^5.0.2",
|
"@tsconfig/svelte": "^5.0.2",
|
||||||
|
@ -713,6 +714,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
|
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@svelte-plugins/tooltips": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@svelte-plugins/tooltips/-/tooltips-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-lE7LKU01OY8XYwsWmRxEcBZJqVv+f/TP5JQP5eqzb9ppS9UeN8DF/mFP9i2BfELRfYdwl4qwUENTPYBndMI3LA==",
|
||||||
|
"dev": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sveltejs/adapter-static": {
|
"node_modules/@sveltejs/adapter-static": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.1.tgz",
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@svelte-plugins/tooltips": "^3.0.0",
|
||||||
"@sveltejs/adapter-static": "^3.0.1",
|
"@sveltejs/adapter-static": "^3.0.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||||
"@tsconfig/svelte": "^5.0.2",
|
"@tsconfig/svelte": "^5.0.2",
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { Toaster } from 'svelte-french-toast'
|
import { Toaster } from 'svelte-french-toast'
|
||||||
import { initializationSequence } from './lib/Sequences/sequences'
|
import { initializationSequence } from './lib/Sequences/sequences'
|
||||||
|
import Loading from './lib/Loading/Loading.svelte'
|
||||||
|
import { settingsStore } from './lib/stores/settingsStore'
|
||||||
|
import getSettings from './lib/utils/getSettings'
|
||||||
|
|
||||||
let activeApp: App = 'camera'
|
let activeApp: App = 'camera'
|
||||||
let topics: TelemetryTopics = {
|
let topics: TelemetryTopics = {
|
||||||
|
@ -27,13 +30,25 @@
|
||||||
booleans: ['ebrake', 'reorient', 'gpws'],
|
booleans: ['ebrake', 'reorient', 'gpws'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let loading = $settingsStore.fastStartup ? false : true
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
let savedSettings = getSettings()
|
||||||
|
if (savedSettings !== false) {
|
||||||
|
settingsStore.set(savedSettings)
|
||||||
|
}
|
||||||
initializeTelemetry(topics, 200)
|
initializeTelemetry(topics, 200)
|
||||||
initializationSequence()
|
setTimeout(() => {
|
||||||
|
loading = false
|
||||||
|
initializationSequence()
|
||||||
|
}, 3000)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="select-none">
|
<main
|
||||||
|
class="select-none transition-opacity duration-300"
|
||||||
|
class:opacity-0={loading}
|
||||||
|
>
|
||||||
<!-- driver dashboard -->
|
<!-- driver dashboard -->
|
||||||
<div class="h-screen w-[35vw] fixed shadow-lg shadow-slate-800 z-10">
|
<div class="h-screen w-[35vw] fixed shadow-lg shadow-slate-800 z-10">
|
||||||
<Dashboard />
|
<Dashboard />
|
||||||
|
@ -50,9 +65,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- toast service -->
|
<!-- toast service -->
|
||||||
<Toaster />
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<Loading />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Toaster />
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
main {
|
main {
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
|
|
BIN
client/src/assets/eecs-wordmark.png
Normal file
BIN
client/src/assets/eecs-wordmark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
BIN
client/src/assets/figurehead-primary.png
Normal file
BIN
client/src/assets/figurehead-primary.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>
|
|
Before Width: | Height: | Size: 1.9 KiB |
|
@ -7,9 +7,15 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { onDestroy } from 'svelte'
|
||||||
import { fade } from 'svelte/transition'
|
import { fade } from 'svelte/transition'
|
||||||
|
import { infotainmentBootupSequence } from '../Sequences/sequences'
|
||||||
|
|
||||||
export let useContainer: boolean = true
|
export let useContainer: boolean = true
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
infotainmentBootupSequence()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -7,6 +7,12 @@
|
||||||
import Song from './Song.svelte'
|
import Song from './Song.svelte'
|
||||||
import { songList } from '../../Dashboard/MediaPlayer/songList'
|
import { songList } from '../../Dashboard/MediaPlayer/songList'
|
||||||
import AppContainer from '../AppContainer.svelte'
|
import AppContainer from '../AppContainer.svelte'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
import { musicPlayerBootupSequence } from '../../Sequences/sequences'
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
setTimeout(musicPlayerBootupSequence, 5000)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AppContainer
|
<AppContainer
|
||||||
|
|
|
@ -1,22 +1,44 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Notifications } from '../../Notifications/notifications'
|
import { Notifications } from '../../Notifications/notifications'
|
||||||
|
import { settingsStore } from '../../stores/settingsStore'
|
||||||
import AppContainer from '../AppContainer.svelte'
|
import AppContainer from '../AppContainer.svelte'
|
||||||
|
import SettingsToggle from './SettingsToggle.svelte'
|
||||||
|
|
||||||
const handleClick = () => {
|
settingsStore.subscribe(async value => {
|
||||||
Notifications.error('Jankboard initialized', {
|
window.localStorage.setItem('settings', JSON.stringify(value))
|
||||||
src: '/static/voices/en/jankboard-initialized.wav',
|
})
|
||||||
})
|
|
||||||
Notifications.playAudio('static/voices/en/jankboard-initialized.wav')
|
const resetSettings = () => {
|
||||||
|
window.localStorage.setItem('settings', '')
|
||||||
|
settingsStore.reset()
|
||||||
|
Notifications.success('Settings reset! Refresh for all changes to apply.')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AppContainer
|
<AppContainer
|
||||||
class="flex gap-4 bg-blue-200 bg-opacity-25 backdrop-blur-xl media-background rounded-3xl flex-wrap px-10 py-20"
|
class="flex gap-6 bg-blue-200 bg-opacity-25 backdrop-blur-xl media-background rounded-3xl flex-wrap px-10 py-20"
|
||||||
>
|
>
|
||||||
<button
|
<h1 class="text-5xl font-medium text-slate-100 basis-full">Settings</h1>
|
||||||
class="px-4 py-2 bg-blue-500 rounded-md hover:brightness-75"
|
<h2 class="text-2xl font-medium text-slate-200 mt-4 basis-full">General</h2>
|
||||||
on:click={handleClick}
|
<div class="flex flex-col gap-2">
|
||||||
>
|
<SettingsToggle
|
||||||
Test Toast
|
setting="disableAnnoyances"
|
||||||
</button>
|
tooltip="Disable non-critical popups and audio cues."
|
||||||
|
>Disable Annoyances</SettingsToggle
|
||||||
|
>
|
||||||
|
<SettingsToggle
|
||||||
|
setting="goWoke"
|
||||||
|
tooltip="Disables content that could be perceived as offensive for PR and DEI purposes."
|
||||||
|
>Go Woke</SettingsToggle
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="mt-10 px-4 py-2 bg-blue-500 hover:brightness-75 text-medium rounded-lg w-min"
|
||||||
|
on:click={resetSettings}>Reset</button
|
||||||
|
>
|
||||||
|
|
||||||
|
<footer class="bottom-0 -mb-10 mt-10 text-slate-300">
|
||||||
|
Settings are synced to the browser's local storage. If things seem broken,
|
||||||
|
try clearing the local Jankboard data and try again.
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
</AppContainer>
|
</AppContainer>
|
||||||
|
|
27
client/src/lib/Apps/Settings/SettingsToggle.svelte
Normal file
27
client/src/lib/Apps/Settings/SettingsToggle.svelte
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
|
||||||
|
@param setting - The setting to be toggled
|
||||||
|
@param inverted? - If false, the toggle syncs to the setting (toggle = on, setting = true). If true, the toggle syncs to the setting's inverse (toggle = off, setting = true).
|
||||||
|
@param tooltip - Helpful tooltip for the setting
|
||||||
|
|
||||||
|
@children The setting's label
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { SettingsStoreData } from '../../stores/settingsStore'
|
||||||
|
import { settingsStore } from '../../stores/settingsStore'
|
||||||
|
import Switch from './Switch.svelte'
|
||||||
|
|
||||||
|
export let setting: keyof SettingsStoreData
|
||||||
|
export let inverted: boolean = false
|
||||||
|
export let tooltip: string = ''
|
||||||
|
|
||||||
|
$: value = inverted ? !$settingsStore[setting] : $settingsStore[setting]
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
settingsStore.update(setting, !value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Switch bind:checked={value} on:click={handleClick} {tooltip}><slot /></Switch>
|
77
client/src/lib/Apps/Settings/Switch.svelte
Normal file
77
client/src/lib/Apps/Settings/Switch.svelte
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Tooltip } from '@svelte-plugins/tooltips'
|
||||||
|
|
||||||
|
export let checked: boolean
|
||||||
|
export let tooltip: string
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" bind:checked on:click />
|
||||||
|
<span class="slider" />
|
||||||
|
</label>
|
||||||
|
{#if tooltip !== ''}
|
||||||
|
<Tooltip content={tooltip} arrow={false}>
|
||||||
|
<span class="flex-grow text-xl text-slate-100 font-medium"
|
||||||
|
><slot />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
{:else}
|
||||||
|
<span class="flex-grow text-xl text-slate-100 font-medium"><slot /> </span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 60px;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
-webkit-transition: 0.4s;
|
||||||
|
transition: 0.4s;
|
||||||
|
border-radius: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: 26px;
|
||||||
|
width: 26px;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
background-color: white;
|
||||||
|
-webkit-transition: 0.4s;
|
||||||
|
transition: 0.4s;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider {
|
||||||
|
background-color: #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider {
|
||||||
|
box-shadow: 0 0 1px #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider:before {
|
||||||
|
-webkit-transform: translateX(26px);
|
||||||
|
-ms-transform: translateX(26px);
|
||||||
|
transform: translateX(26px);
|
||||||
|
}
|
||||||
|
</style>
|
30
client/src/lib/Loading/Loading.svelte
Normal file
30
client/src/lib/Loading/Loading.svelte
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { blur } from 'svelte/transition'
|
||||||
|
|
||||||
|
import SvelteLogo from './SvelteLogo.svelte'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="absolute w-screen h-screen flex justify-center items-center flex-col overflow-hidden bg"
|
||||||
|
transition:blur={{ duration: 300, amount: 0.5 }}
|
||||||
|
>
|
||||||
|
<div class="max-w-64">
|
||||||
|
<SvelteLogo />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
.bg {
|
||||||
|
background: #2c3e50; /* fallback for old browsers */
|
||||||
|
background: -webkit-linear-gradient(
|
||||||
|
to right,
|
||||||
|
#2c3e50,
|
||||||
|
#fd746c
|
||||||
|
); /* Chrome 10-25, Safari 5.1-6 */
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
#2c3e50,
|
||||||
|
#fd746c
|
||||||
|
); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
|
||||||
|
}
|
||||||
|
</style>
|
63
client/src/lib/Loading/SvelteLogo.svelte
Normal file
63
client/src/lib/Loading/SvelteLogo.svelte
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { quintOut } from 'svelte/easing'
|
||||||
|
import { fade, draw, fly } from 'svelte/transition'
|
||||||
|
import { expand } from './customTransitions'
|
||||||
|
import { inner, outer } from './shape'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
|
let visible = false
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
visible = true
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if visible}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 103 124">
|
||||||
|
<g out:fade={{ duration: 200 }} opacity="0.3">
|
||||||
|
<path
|
||||||
|
in:expand={{ duration: 400, delay: 1000, easing: quintOut }}
|
||||||
|
style="stroke: #ff3e00; fill: #ff3e00; stroke-width: 50;"
|
||||||
|
d={outer}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
in:draw={{ duration: 1000 }}
|
||||||
|
style="stroke:#ff3e00; stroke-width: 1.5"
|
||||||
|
d={inner}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div class="centered" out:fly={{ y: -20, duration: 800 }}>
|
||||||
|
{#each 'JANKBOARD' as char, i}
|
||||||
|
<span in:fade|global={{ delay: 1000 + i * 150, duration: 800 }}
|
||||||
|
>{char}</span
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: white;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
@apply text-8xl absolute text-slate-300;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered span {
|
||||||
|
will-change: filter;
|
||||||
|
}
|
||||||
|
</style>
|
18
client/src/lib/Loading/customTransitions.ts
Normal file
18
client/src/lib/Loading/customTransitions.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { cubicOut } from 'svelte/easing'
|
||||||
|
import type { EasingFunction } from 'svelte/transition'
|
||||||
|
|
||||||
|
export function expand(
|
||||||
|
node: Element,
|
||||||
|
params: { delay: number; duration: number; easing: EasingFunction }
|
||||||
|
) {
|
||||||
|
const { delay = 0, duration = 400, easing = cubicOut } = params
|
||||||
|
|
||||||
|
const w = parseFloat(getComputedStyle(node).strokeWidth)
|
||||||
|
|
||||||
|
return {
|
||||||
|
delay,
|
||||||
|
duration,
|
||||||
|
easing,
|
||||||
|
css: (t: number) => `opacity: ${t}; stroke-width: ${t * w}`,
|
||||||
|
}
|
||||||
|
}
|
2
client/src/lib/Loading/shape.ts
Normal file
2
client/src/lib/Loading/shape.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const inner = `M45.41,108.86A21.81,21.81,0,0,1,22,100.18,20.2,20.2,0,0,1,18.53,84.9a19,19,0,0,1,.65-2.57l.52-1.58,1.41,1a35.32,35.32,0,0,0,10.75,5.37l1,.31-.1,1a6.2,6.2,0,0,0,1.11,4.08A6.57,6.57,0,0,0,41,95.19a6,6,0,0,0,1.68-.74L70.11,76.94a5.76,5.76,0,0,0,2.59-3.83,6.09,6.09,0,0,0-1-4.6,6.58,6.58,0,0,0-7.06-2.62,6.21,6.21,0,0,0-1.69.74L52.43,73.31a19.88,19.88,0,0,1-5.58,2.45,21.82,21.82,0,0,1-23.43-8.68A20.2,20.2,0,0,1,20,51.8a19,19,0,0,1,8.56-12.7L56,21.59a19.88,19.88,0,0,1,5.58-2.45A21.81,21.81,0,0,1,85,27.82,20.2,20.2,0,0,1,88.47,43.1a19,19,0,0,1-.65,2.57l-.52,1.58-1.41-1a35.32,35.32,0,0,0-10.75-5.37l-1-.31.1-1a6.2,6.2,0,0,0-1.11-4.08,6.57,6.57,0,0,0-7.06-2.62,6,6,0,0,0-1.68.74L36.89,51.06a5.71,5.71,0,0,0-2.58,3.83,6,6,0,0,0,1,4.6,6.58,6.58,0,0,0,7.06,2.62,6.21,6.21,0,0,0,1.69-.74l10.48-6.68a19.88,19.88,0,0,1,5.58-2.45,21.82,21.82,0,0,1,23.43,8.68A20.2,20.2,0,0,1,87,76.2a19,19,0,0,1-8.56,12.7L51,106.41a19.88,19.88,0,0,1-5.58,2.45`
|
||||||
|
export const outer = `M65,34 L37,52 A1 1 0 0 0 44 60 L70.5,44.5 A1 1 0 0 0 65,34Z M64,67 L36,85 A1 1 0 0 0 42 94 L68,77.5 A1 1 0 0 0 64,67Z`
|
10
client/src/lib/Notifications/WarnIcon.svelte
Normal file
10
client/src/lib/Notifications/WarnIcon.svelte
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export let size = '1.5rem'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="material-symbols-outlined text-[#fafafa]"
|
||||||
|
style="font-size: {size}"
|
||||||
|
>
|
||||||
|
warning
|
||||||
|
</span>
|
|
@ -2,6 +2,7 @@ import { toast } from 'svelte-french-toast'
|
||||||
import type { ToastOptions } from 'svelte-french-toast'
|
import type { ToastOptions } from 'svelte-french-toast'
|
||||||
import InfoIcon from './InfoIcon.svelte'
|
import InfoIcon from './InfoIcon.svelte'
|
||||||
import { Howl } from 'howler'
|
import { Howl } from 'howler'
|
||||||
|
import WarnIcon from './WarnIcon.svelte'
|
||||||
|
|
||||||
interface NotificationOptions extends ToastOptions {
|
interface NotificationOptions extends ToastOptions {
|
||||||
withAudio?: boolean
|
withAudio?: boolean
|
||||||
|
@ -62,7 +63,7 @@ export class Notifications {
|
||||||
sendToast(this.defaultDuration)
|
sendToast(this.defaultDuration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static info(message: string, options?: any) {
|
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;',
|
||||||
|
@ -83,6 +84,28 @@ export class Notifications {
|
||||||
sendToast(this.defaultDuration)
|
sendToast(this.defaultDuration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static warn(message: string, options?: NotificationOptions) {
|
||||||
|
const sendToast = (duration: number) => {
|
||||||
|
toast(message, {
|
||||||
|
style:
|
||||||
|
'padding: 25px; font-size: 1.5rem; background-color: #f59e0b; color: #fafafa;',
|
||||||
|
icon: WarnIcon,
|
||||||
|
duration,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (options?.withAudio && options?.src) {
|
||||||
|
let sound: Howl
|
||||||
|
sound = new Howl({
|
||||||
|
src: [options.src],
|
||||||
|
preload: true,
|
||||||
|
autoplay: true,
|
||||||
|
onload: () => sendToast(sound.duration() * 1000),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
sendToast(this.defaultDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
public static playAudio(src: string) {
|
public static playAudio(src: string) {
|
||||||
new Howl({
|
new Howl({
|
||||||
src: [src],
|
src: [src],
|
||||||
|
|
|
@ -3,26 +3,148 @@ define various sequences to play out in this file
|
||||||
for example, we can define an initialization sequence that
|
for example, we can define an initialization sequence that
|
||||||
plays out some series of notifications, and call it whenever we need it,
|
plays out some series of notifications, and call it whenever we need it,
|
||||||
or a sequence to change the screen color and play some audio queues for a crash
|
or a sequence to change the screen color and play some audio queues for a crash
|
||||||
|
these sequences should be self contained and not rely on any external state outside of
|
||||||
|
that in sequenceStore so that they can be easily invoked from anywhere
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Notifications } from '../Notifications/notifications'
|
import { Notifications } from '../Notifications/notifications'
|
||||||
|
import { sequenceStore } from '../stores/sequenceStore'
|
||||||
|
import { settingsStore } from '../stores/settingsStore'
|
||||||
|
import { get } from 'svelte/store'
|
||||||
import getVoicePath from '../utils/getVoicePath'
|
import getVoicePath from '../utils/getVoicePath'
|
||||||
|
import { tick } from 'svelte'
|
||||||
|
|
||||||
export const initializationSequence = () => {
|
// await a "tick" (a svelte update frame) at the start of every sequence so that
|
||||||
|
// state is synced and no weird side effects occur
|
||||||
|
|
||||||
|
export const initializationSequence = async () => {
|
||||||
|
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(() => {
|
||||||
Notifications.success('LittenOS is online.', {
|
if (get(settingsStore).goWoke) return
|
||||||
|
Notifications.success('LittenOS is online', {
|
||||||
withAudio: true,
|
withAudio: true,
|
||||||
src: getVoicePath('littenos-is-online', 'en'),
|
src: getVoicePath('littenos-is-online', 'en'),
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Notifications.error('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(() => {
|
||||||
|
Notifications.playAudio(getVoicePath('hello-virtual-assistant', 'en'))
|
||||||
|
}, 3000)
|
||||||
}, 3000)
|
}, 3000)
|
||||||
}, 3000)
|
}, 3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const criticalFailureIminentSequence = async () => {
|
||||||
|
Notifications.error('Critical robot failure imminent', {
|
||||||
|
withAudio: true,
|
||||||
|
src: getVoicePath('critical-robot-failure', 'en'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const collisionDetectedSequence = async () => {
|
||||||
|
Notifications.error('Collision detected', {
|
||||||
|
withAudio: true,
|
||||||
|
src: getVoicePath('collision-detected', 'en'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const collisionImminentSequence = async () => {
|
||||||
|
Notifications.error('Collision imminent', {
|
||||||
|
withAudio: true,
|
||||||
|
src: getVoicePath('collision-imminent', 'en'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cruiseControlEngagedSequence = async () => {
|
||||||
|
if (get(settingsStore).disableAnnoyances) return
|
||||||
|
Notifications.success('Cruise control engaged', {
|
||||||
|
withAudio: true,
|
||||||
|
src: getVoicePath('cruise-control-engaged', 'en'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const retardSequence = async () => {
|
||||||
|
if (get(settingsStore).goWoke) return
|
||||||
|
Notifications.warn('Retard', {
|
||||||
|
withAudio: true,
|
||||||
|
src: getVoicePath('retard', 'en'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const breaching254Sequence = async () => {
|
||||||
|
if (get(settingsStore).disableAnnoyances) return
|
||||||
|
Notifications.info('Breaching 254 mainframe', {
|
||||||
|
withAudio: true,
|
||||||
|
src: getVoicePath('breaching-254-mainframe', 'en'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const breaching1323Sequence = async () => {
|
||||||
|
if (get(settingsStore).disableAnnoyances) return
|
||||||
|
Notifications.info('Breaching 1323 mainframe', {
|
||||||
|
withAudio: true,
|
||||||
|
src: getVoicePath('breaching-1323-mainframe', 'en'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const bullyingRohanSequence = async () => {
|
||||||
|
if (get(settingsStore).disableAnnoyances) return
|
||||||
|
Notifications.info('Bullying Rohan', {
|
||||||
|
withAudio: true,
|
||||||
|
src: getVoicePath('bullying-rohan', 'en'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const userErrorDetectedSequence = async () => {
|
||||||
|
Notifications.error('User error detected', {
|
||||||
|
withAudio: true,
|
||||||
|
src: getVoicePath('user-error-detected', 'en'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const infotainmentBootupSequence = async () => {
|
||||||
|
if (
|
||||||
|
get(sequenceStore).infotainmentStartedFirstTime ||
|
||||||
|
get(settingsStore).disableAnnoyances
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await tick()
|
||||||
|
|
||||||
|
sequenceStore.update('infotainmentStartedFirstTime', true)
|
||||||
|
|
||||||
|
Notifications.info('Infotainment system buffering', {
|
||||||
|
withAudio: true,
|
||||||
|
src: getVoicePath('infotainment-system-buffering', 'en'),
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
Notifications.success('Infotainment system online', {
|
||||||
|
withAudio: true,
|
||||||
|
src: getVoicePath('infotainment-system-online', 'en'),
|
||||||
|
})
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const musicPlayerBootupSequence = async () => {
|
||||||
|
if (
|
||||||
|
get(sequenceStore).musicStartedFirstTime ||
|
||||||
|
get(settingsStore).disableAnnoyances
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await tick()
|
||||||
|
|
||||||
|
sequenceStore.update('musicStartedFirstTime', true)
|
||||||
|
|
||||||
|
Notifications.info('Downloading copyrighted music...', {
|
||||||
|
withAudio: true,
|
||||||
|
src: getVoicePath('downloading-copyrighted-music', 'en'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
32
client/src/lib/stores/sequenceStore.ts
Normal file
32
client/src/lib/stores/sequenceStore.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/* in this store, put stateful variables that sequences need to access */
|
||||||
|
|
||||||
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
|
interface SequenceStoreData {
|
||||||
|
infotainmentStartedFirstTime: boolean
|
||||||
|
musicStartedFirstTime: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
let defaults: SequenceStoreData = {
|
||||||
|
infotainmentStartedFirstTime: false, // for infotainment bootup sequence
|
||||||
|
musicStartedFirstTime: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSequenceStore = () => {
|
||||||
|
const { subscribe, set, update } = writable<SequenceStoreData>(defaults)
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
update: (
|
||||||
|
data: keyof SequenceStoreData,
|
||||||
|
newValue: SequenceStoreData[typeof data]
|
||||||
|
) => {
|
||||||
|
update(store => {
|
||||||
|
store[data] = newValue
|
||||||
|
return store
|
||||||
|
})
|
||||||
|
},
|
||||||
|
reset: () => set(defaults),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sequenceStore = createSequenceStore()
|
35
client/src/lib/stores/settingsStore.ts
Normal file
35
client/src/lib/stores/settingsStore.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/* stores global app wide settings */
|
||||||
|
|
||||||
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
|
export interface SettingsStoreData {
|
||||||
|
disableAnnoyances: boolean
|
||||||
|
goWoke: boolean
|
||||||
|
fastStartup: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaults: SettingsStoreData = {
|
||||||
|
disableAnnoyances: false, // disable non-critical notifications
|
||||||
|
goWoke: false, // go woke (for showing parents or other officials where DEI has taken over), disables "offensive" sequences
|
||||||
|
fastStartup: false, // skip the loading splash screen (for development purposes. Setting this from within the app has no effect.)
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSequenceStore = () => {
|
||||||
|
const { subscribe, set, update } = writable<SettingsStoreData>(defaults)
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
update: (
|
||||||
|
data: keyof SettingsStoreData,
|
||||||
|
newValue: SettingsStoreData[typeof data]
|
||||||
|
) => {
|
||||||
|
update(store => {
|
||||||
|
store[data] = newValue
|
||||||
|
return store
|
||||||
|
})
|
||||||
|
},
|
||||||
|
reset: () => set(defaults),
|
||||||
|
set: (data: SettingsStoreData) => set(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const settingsStore = createSequenceStore()
|
7
client/src/lib/utils/getSettings.ts
Normal file
7
client/src/lib/utils/getSettings.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default function getSettings() {
|
||||||
|
if (localStorage.getItem('settings') !== null) {
|
||||||
|
return JSON.parse(localStorage.getItem('settings') as string)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue