Merge pull request #1 from couscousdude/camera-feed-overhaul

Camera feed overhaul
This commit is contained in:
Youwen Wu 2024-03-17 20:37:20 -07:00 committed by GitHub
commit 13e5790567
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 161 additions and 63 deletions

View file

@ -17,6 +17,7 @@
"@tauri-apps/cli": "^1.5.10", "@tauri-apps/cli": "^1.5.10",
"@tsconfig/svelte": "^5.0.2", "@tsconfig/svelte": "^5.0.2",
"@types/howler": "^2.2.11", "@types/howler": "^2.2.11",
"@types/konami-code-js": "^0.8.3",
"@types/three": "^0.161.2", "@types/three": "^0.161.2",
"autoprefixer": "^10.4.17", "autoprefixer": "^10.4.17",
"postcss": "^8.4.35", "postcss": "^8.4.35",
@ -35,6 +36,7 @@
"@threlte/extras": "^8.8.0", "@threlte/extras": "^8.8.0",
"camera-controls": "^2.8.3", "camera-controls": "^2.8.3",
"howler": "^2.2.4", "howler": "^2.2.4",
"konami-code-js": "^0.8.1",
"material-icons": "^1.13.12", "material-icons": "^1.13.12",
"material-symbols": "^0.15.0", "material-symbols": "^0.15.0",
"overlayscrollbars-svelte": "^0.5.3", "overlayscrollbars-svelte": "^0.5.3",

View file

@ -23,6 +23,9 @@ dependencies:
howler: howler:
specifier: ^2.2.4 specifier: ^2.2.4
version: 2.2.4 version: 2.2.4
konami-code-js:
specifier: ^0.8.1
version: 0.8.1
material-icons: material-icons:
specifier: ^1.13.12 specifier: ^1.13.12
version: 1.13.12 version: 1.13.12
@ -61,6 +64,9 @@ devDependencies:
'@types/howler': '@types/howler':
specifier: ^2.2.11 specifier: ^2.2.11
version: 2.2.11 version: 2.2.11
'@types/konami-code-js':
specifier: ^0.8.3
version: 0.8.3
'@types/three': '@types/three':
specifier: ^0.161.2 specifier: ^0.161.2
version: 0.161.2 version: 0.161.2
@ -724,6 +730,10 @@ packages:
resolution: {integrity: sha512-7aBoUL6RbSIrqKnpEgfa1wSNUBK06mn08siP2QI0zYk7MXfEJAaORc4tohamQYqCqVESoDyRWSdQn2BOKWj2Qw==} resolution: {integrity: sha512-7aBoUL6RbSIrqKnpEgfa1wSNUBK06mn08siP2QI0zYk7MXfEJAaORc4tohamQYqCqVESoDyRWSdQn2BOKWj2Qw==}
dev: true dev: true
/@types/konami-code-js@0.8.3:
resolution: {integrity: sha512-TYwIDZ16MuzqHdgv/zhbbE3p/iXY/FotJkMYx1oOzZKJoINezl91stc/U/i5AknH5lrEpXoims+tsCnAwIJGew==}
dev: true
/@types/pug@2.0.10: /@types/pug@2.0.10:
resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
dev: true dev: true
@ -1312,6 +1322,10 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/konami-code-js@0.8.1:
resolution: {integrity: sha512-bJ0tuWYLYiUueIVTpA0MV4h4Gz1X16uuJggh5TpIWXOQoLv0238SU7Im23z2wYKCCBsOfk5j4HKWB/pqdCgu5Q==}
dev: false
/lilconfig@2.1.0: /lilconfig@2.1.0:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'} engines: {node: '>=10'}

View file

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 217 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 KiB

After

Width:  |  Height:  |  Size: 217 KiB

View file

@ -1,3 +1,11 @@
declare module 'konami-code-js' {
export default class KonamiCode {
constructor(options: any = {})
setCallback: (callback: () => void) => void
disable: () => void
}
}
type Gear = 'park' | 'reverse' | 'neutral' | 'low' | 'auto' | 'drive' type Gear = 'park' | 'reverse' | 'neutral' | 'low' | 'auto' | 'drive'
type Mode = 'chill' | 'ludicrous' | 'cruise' type Mode = 'chill' | 'ludicrous' | 'cruise'

View file

@ -1,15 +1,16 @@
<script lang="ts"> <script lang="ts">
import AppContainer from '../AppContainer.svelte' import AppContainer from '../AppContainer.svelte'
import CameraContainer from './CameraContainer.svelte' import CameraContainer from './CameraContainer.svelte'
import { settingsStore } from '../../stores/settingsStore'
</script> </script>
<AppContainer <AppContainer
class="px-10 py-20 flex gap-4 w-full backdrop-blur-lg justify-center h-full rounded-3xl shadow-md bg-slate-300 bg-opacity-30" class="px-10 py-20 flex gap-4 w-full backdrop-blur-lg justify-center h-full rounded-3xl shadow-md bg-slate-300 bg-opacity-30"
> >
<div class="my-auto"> <div class="my-auto">
<CameraContainer cameraUrl="camera_url_here" /> <CameraContainer cameraUrl={$settingsStore.frontCameraAddr} />
</div> </div>
<div class="my-auto"> <div class="my-auto">
<CameraContainer cameraUrl="camera_url_here" /> <CameraContainer cameraUrl={$settingsStore.rearCameraAddr} />
</div> </div>
</AppContainer> </AppContainer>

View file

@ -7,10 +7,19 @@
--> -->
<script lang="ts"> <script lang="ts">
export let cameraUrl: string export let cameraUrl: string
let feed: HTMLImageElement
let placeholder: HTMLDivElement
$: {
if (feed && placeholder) {
placeholder.style.width = `${feed.clientWidth}px`
placeholder.style.height = `${feed.clientHeight}px`
}
}
</script> </script>
<div> <div>
<!-- svelte-ignore a11y-missing-attribute -->
<div class="grid"> <div class="grid">
<img <img
src={cameraUrl} src={cameraUrl}
@ -18,9 +27,11 @@
height="400px" height="400px"
class="rounded-xl shadow-md layer1 z-10" class="rounded-xl shadow-md layer1 z-10"
alt="" alt=""
bind:this={feed}
/> />
<div <div
class="w-[400px] h-[400px] rounded-xl shadow-md bg-neutral-300 animate-pulse layer2" class="rounded-xl shadow-md bg-neutral-300 animate-pulse layer2"
bind:this={placeholder}
/> />
</div> </div>
</div> </div>

View file

@ -5,6 +5,9 @@
import SettingsSelector from './SettingsSelector.svelte' import SettingsSelector from './SettingsSelector.svelte'
import SettingsInput from './SettingsInput.svelte' import SettingsInput from './SettingsInput.svelte'
import SettingsToggle from './SettingsToggle.svelte' import SettingsToggle from './SettingsToggle.svelte'
import KonamiCode from 'konami-code-js'
import { onMount } from 'svelte'
import { fly } from 'svelte/transition'
settingsStore.subscribe(async value => { settingsStore.subscribe(async value => {
window.localStorage.setItem('settings', JSON.stringify(value)) window.localStorage.setItem('settings', JSON.stringify(value))
@ -15,43 +18,97 @@
settingsStore.reset() settingsStore.reset()
Notifications.success('Settings reset! Refresh for all changes to apply.') Notifications.success('Settings reset! Refresh for all changes to apply.')
} }
let unlockSecret = false
onMount(() => {
const kc = new KonamiCode()
kc.setCallback(() => {
kc.disable()
unlockSecret = true
})
})
</script> </script>
<AppContainer <AppContainer
class="flex gap-6 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 transition-all"
> >
<h1 class="text-5xl font-medium text-slate-100 basis-full">Settings</h1> <h1 class="text-5xl font-medium text-slate-100 basis-full">Settings</h1>
<p class="text-slate-300">Hover over setting names to see helpful tooltips</p> <p class="text-slate-300">Hover over setting names to see helpful tooltips</p>
<h2 class="text-2xl font-medium text-slate-200 mt-4 basis-full">General</h2>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<SettingsToggle <h2 class="text-2xl font-medium text-slate-200 mt-4 basis-full">General</h2>
setting="disableAnnoyances" <ul>
tooltip="Disable non-critical popups and audio cues." <li>
>Disable Annoyances</SettingsToggle <SettingsToggle
> setting="disableAnnoyances"
<SettingsToggle tooltip="Disable non-critical popups and audio cues."
setting="goWoke" >Disable Annoyances</SettingsToggle
tooltip="Disables content that could be perceived as offensive for PR and DEI purposes." >
>Go Woke</SettingsToggle </li>
> <li>
<SettingsInput <SettingsSelector
setting="randomWeight" setting="voiceLang"
tooltip="Changes the likelihood of random events occurring (default: 1). Set to a decimal to lower probability and a number >= 1 to increase it." options={['en-US', 'en-RU', 'en-UK']}
width="3rem" tooltip="Selects the language/locale used for Jankboard voice prompts. Does not affect application language (ie. Jankboard itself will always be in English)."
> >Voice Prompt Language</SettingsSelector
RNG Weight >
</SettingsInput> </li>
<SettingsSelector </ul>
setting="voiceLang"
options={['en-US', 'en-RU', 'en-UK']} <h2 class="text-2xl font-medium text-slate-200 mt-4 basis-full">
tooltip="Selects the language/locale used for Jankboard voice prompts. Does not affect application language (ie. Jankboard itself will always be in English)." Camera Configuration
>Voice Prompt Language</SettingsSelector </h2>
> <ul>
<SettingsToggle <li>
setting="sentry" <SettingsInput
tooltip="Sentry mode protects the robot and operator from foreign threats." setting="frontCameraAddr"
>Sentry Mode</SettingsToggle tooltip="Set the IP address of the front-facing camera. Input in the format xxx.xxx.xxx.xxx:PORT (eg. 244.178.44.111:8080)"
> width="16rem">Front Camera IP</SettingsInput
>
</li>
<li>
<SettingsInput
setting="rearCameraAddr"
tooltip="Set the IP address of the rear-facing camera. Input in the format xxx.xxx.xxx.xxx:PORT (eg. 244.178.44.111:8080)"
width="16rem">Rear Camera IP</SettingsInput
>
</li>
</ul>
<h2 class="text-2xl font-medium text-slate-200 mt-4 basis-full">Fun</h2>
<ul>
<li>
<SettingsToggle
setting="sentry"
tooltip="Sentry mode protects the robot and operator from foreign threats (doesn't actually do anything besides add extra voice prompts)"
>Sentry Mode</SettingsToggle
>
</li>
<li>
<SettingsInput
setting="randomWeight"
tooltip="Changes the likelihood of random events occurring (default: 1). Set to a decimal to lower probability and a number >= 1 to increase it."
width="3rem"
>
RNG Weight
</SettingsInput>
</li>
</ul>
{#if unlockSecret}
<div transition:fly={{ y: -100, duration: 200 }}>
<h2 class="text-2xl font-medium text-slate-200 mt-4 basis-full">
Secret
</h2>
<ul>
<li>
<SettingsToggle
setting="goWoke"
tooltip="Disables content that could be perceived as offensive for PR and DEI purposes."
>Go Woke</SettingsToggle
>
</li>
</ul>
</div>
{/if}
<button <button
class="mt-10 px-4 py-2 bg-amber-600 hover:brightness-75 text-medium rounded-lg w-min" class="mt-10 px-4 py-2 bg-amber-600 hover:brightness-75 text-medium rounded-lg w-min"
on:click={resetSettings}>Reset</button on:click={resetSettings}>Reset</button
@ -63,3 +120,10 @@
</footer> </footer>
</div> </div>
</AppContainer> </AppContainer>
<div class="w-full h-24" />
<style lang="postcss">
li {
@apply my-3;
}
</style>

View file

@ -13,7 +13,6 @@
const handleSubmit = async () => { const handleSubmit = async () => {
await tick() await tick()
// @ts-expect-error
settingsStore.update(setting, value) settingsStore.update(setting, value)
} }

View file

@ -217,7 +217,7 @@ export const infotainmentBootupSequence = async () => {
} }
if (!get(sequenceStore).initializationComplete) { if (!get(sequenceStore).initializationComplete) {
const unsubscribe = sequenceStore.subscribe((data) => { const unsubscribe = sequenceStore.subscribe(data => {
if (data.initializationComplete) { if (data.initializationComplete) {
sequence() sequence()
unsubscribe() unsubscribe()
@ -237,7 +237,7 @@ export const infotainmentBootupSequence = async () => {
*/ */
const waitForInfotainmentBootup = (sequence: () => void) => { const waitForInfotainmentBootup = (sequence: () => void) => {
if (!get(sequenceStore).infotainmentStartedFirstTime) { if (!get(sequenceStore).infotainmentStartedFirstTime) {
const unsubscribe = sequenceStore.subscribe((data) => { const unsubscribe = sequenceStore.subscribe(data => {
if (data.infotainmentStartedFirstTime) { if (data.infotainmentStartedFirstTime) {
sequence() sequence()
unsubscribe() unsubscribe()

View file

@ -11,6 +11,8 @@ export interface SettingsStoreData {
randomWeight: number randomWeight: number
voiceLang: SupportedLanguage voiceLang: SupportedLanguage
sentry: boolean sentry: boolean
frontCameraAddr: string
rearCameraAddr: string
} }
export const defaults: SettingsStoreData = { export const defaults: SettingsStoreData = {
@ -18,8 +20,10 @@ export const defaults: SettingsStoreData = {
goWoke: true, // go woke (for showing parents or other officials where DEI has taken over), disables "offensive" sequences goWoke: true, // 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.) fastStartup: false, // skip the loading splash screen (for development purposes. Setting this from within the app has no effect.)
randomWeight: 1, // the weight of random events (multiplied by the original probability) randomWeight: 1, // the weight of random events (multiplied by the original probability)
voiceLang: 'en-US', // locale-specific voice for alerts voiceLang: 'en-UK', // locale-specific voice for alerts
sentry: true, // protect the robot and operator from foreign threats sentry: false, // protect the robot and operator from foreign threats
frontCameraAddr: '',
rearCameraAddr: '',
} }
const createSequenceStore = () => { const createSequenceStore = () => {
@ -30,7 +34,7 @@ const createSequenceStore = () => {
data: keyof SettingsStoreData, data: keyof SettingsStoreData,
newValue: SettingsStoreData[typeof data] newValue: SettingsStoreData[typeof data]
) => { ) => {
update((store) => { update(store => {
// @ts-expect-error // @ts-expect-error
store[data] = newValue store[data] = newValue
return store return store

View file

@ -44,3 +44,4 @@ screen!**
inlined. inlined.
4. Rename this outputted `index.html` to `splashscreen.html`, and then move it 4. Rename this outputted `index.html` to `splashscreen.html`, and then move it
into `/client/public`, replacing the existing `splashscreen.html`. into `/client/public`, replacing the existing `splashscreen.html`.
Note: the background image will not load in development since it's designed to load `/splash-screen.jpg` from the main app. Update the `splash-screen.jpg` image in `client/public` to change it.

View file

@ -3,13 +3,7 @@
</script> </script>
<svelte:head> <svelte:head>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <title>Jankboard 2</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
</svelte:head> </svelte:head>
<Loading /> <Loading />

View file

@ -1,8 +1,9 @@
<script lang="ts"> <script lang="ts">
import { blur, fade } from 'svelte/transition' import { fly } from 'svelte/transition'
import SvelteLogo from './SvelteLogo.svelte' import SvelteLogo from './SvelteLogo.svelte'
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { cubicIn } from 'svelte/easing'
let loadingStuck = false let loadingStuck = false
@ -15,24 +16,23 @@
<div <div
class="absolute w-screen h-screen flex justify-center items-center flex-col overflow-hidden select-none bg" class="absolute w-screen h-screen flex justify-center items-center flex-col overflow-hidden select-none bg"
transition:blur={{ duration: 300, amount: 0.5 }}
> >
<div class="max-w-64"> <div class="max-w-64">
<SvelteLogo /> <SvelteLogo />
</div> </div>
{#if loadingStuck} {#if loadingStuck}
<p <p
class="text-5xl text-slate-300 absolute bottom-20 animate-pulse" class="text-4xl text-slate-300 absolute bottom-20 animate-pulse font-medium"
transition:fade={{ duration: 300 }} in:fly={{ duration: 150, y: 25, easing: cubicIn }}
> >
Loading 3D assets...please wait Loading 3D assets...
</p> </p>
{/if} {/if}
</div> </div>
<style lang="postcss"> <style lang="postcss">
.bg { .bg {
background-image: url('../../assets/wallpaper.jpg'); background-image: url('/splash-screen.jpg');
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: cover; background-size: cover;
} }