add many features

This commit is contained in:
Youwen Wu 2024-02-21 20:10:42 -08:00
parent 699f655b03
commit fa3bc3c18a
30 changed files with 542 additions and 38 deletions

View file

@ -12,6 +12,7 @@ import waitress
from flask import Flask, render_template, send_from_directory
from flask_socketio import SocketIO, emit
from ntcore import util
import os
def signal_handler(_signal: int, _frame) -> None:
@ -23,7 +24,7 @@ signal.signal(signal.SIGINT, signal_handler)
# initialize flask app and socketio
app = Flask(__name__, static_folder="dist")
app = Flask(__name__, static_folder="dist", static_url_path="/")
socketio = SocketIO(app)
inst = ntcore.NetworkTableInstance.getDefault()
@ -79,15 +80,18 @@ def choose(key: str, selection: str) -> str:
# Route for serving dynamic content (all files within dist/)
@app.route("/static/<path:filename>")
def custom_static(filename):
return send_from_directory("static", filename)
@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def serve_file(path):
return send_from_directory("dist", path)
# Special route for the root to serve index.html
@app.route("/")
def serve_index():
return send_from_directory("dist", "index.html")
def serve(path):
if path != "" and os.path.exists(app.static_folder + "/" + path):
return send_from_directory(app.static_folder, path)
else:
return send_from_directory(app.static_folder, "index.html")
def start(**server_kwargs: dict) -> None:
@ -110,6 +114,12 @@ def connect() -> None:
"""Handle client connection."""
@socketio.on("ping")
def ping() -> None:
emit("pong")
print("pinged!")
@socketio.on("request_data")
def request(obj: dict) -> None:
"""Handle client telemetry request.

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

104
client/package-lock.json generated
View file

@ -7,6 +7,12 @@
"": {
"name": "-client",
"version": "0.0.0",
"dependencies": {
"@fontsource/roboto": "^5.0.8",
"material-icons": "^1.13.12",
"material-symbols": "^0.15.0",
"socket.io-client": "^4.7.4"
},
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
@ -414,6 +420,11 @@
"node": ">=12"
}
},
"node_modules/@fontsource/roboto": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz",
"integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA=="
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@ -700,6 +711,11 @@
"win32"
]
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"node_modules/@sveltejs/adapter-static": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.1.tgz",
@ -1161,7 +1177,6 @@
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
@ -1238,6 +1253,26 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
"node_modules/engine.io-client": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
"integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.11.0",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
"integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/es6-promise": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
@ -1665,6 +1700,16 @@
"node": ">=12"
}
},
"node_modules/material-icons": {
"version": "1.13.12",
"resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.12.tgz",
"integrity": "sha512-/2YoaB79IjUK2B2JB+vIXXYGtBfHb/XG66LvoKVM5ykHW7yfrV5SP6d7KLX6iijY6/G9GqwgtPQ/sbhFnOURVA=="
},
"node_modules/material-symbols": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/material-symbols/-/material-symbols-0.15.0.tgz",
"integrity": "sha512-216LRlmN8fZb0CoIOaQBmRZ55BptWcd7z//0v7dXQA6aogsvI9Qp1nMQ5jZ44dbgBXntUQzWdB5Q2D+6bJXioA=="
},
"node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
@ -1766,8 +1811,7 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mz": {
"version": "2.7.0",
@ -2320,6 +2364,32 @@
"node": ">= 10"
}
},
"node_modules/socket.io-client": {
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz",
"integrity": "sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/sorcery": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
@ -2999,6 +3069,34 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/yaml": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",

View file

@ -21,5 +21,11 @@
"tslib": "^2.6.2",
"typescript": "^5.2.2",
"vite": "^5.1.4"
},
"dependencies": {
"@fontsource/roboto": "^5.0.8",
"material-icons": "^1.13.12",
"material-symbols": "^0.15.0",
"socket.io-client": "^4.7.4"
}
}

View file

@ -1,13 +1,47 @@
<script lang="ts">
import '@fontsource/roboto/latin.css'
import svelteLogo from './assets/svelte.svg'
import viteLogo from '/vite.svg'
import 'material-icons/iconfont/material-icons.css'
import Dashboard from './lib/Dashboard/Dashboard.svelte'
import 'material-symbols'
import AppBar from './lib/Apps/AppBar.svelte'
import { appList } from './lib/Apps/appList'
let activeApp: App = 'media-player'
</script>
<main></main>
<main class="select-none">
<!-- driver dashboard -->
<div class="h-screen w-[35vw] fixed shadow-lg shadow-slate-800 z-10">
<Dashboard />
</div>
<!-- the infotainment system -->
<div class="h-screen w-[65vw] right-0 fixed infotainment-container">
<!-- dynamic app system (edit appList.ts to add new apps) -->
<div class="mx-10 mt-10">
<svelte:component this={appList[activeApp].component} />
</div>
<div class="fixed w-[65vw] flex justify-center right-0 bottom-0 mb-4">
<AppBar bind:activeApp {appList} />
</div>
</div>
</main>
<style lang="postcss">
main {
font-family: 'Roboto', sans-serif;
}
.infotainment-container {
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>

View file

@ -14,6 +14,6 @@
}
body {
@apply bg-black;
@apply bg-black text-white;
}
}

21
client/src/globals.d.ts vendored Normal file
View file

@ -0,0 +1,21 @@
type Gear = 'p' | 'r' | 'n' | 'l' | 'a' | 'd'
type Mode = 'chill' | 'ludicrous' | 'cruise'
type App = 'camera' | 'media-player'
interface SongData {
title: string
artist: string
src: string
coverImg: string
slug: string
}
interface AppData {
[key: App]: {
name: string
component: SvelteComponent
icon: string
}
}

View file

@ -0,0 +1,36 @@
<script lang="ts">
export let activeApp: App
export let appList: AppData
</script>
<!-- Genius codeium engineering automatically loads apps -->
<div
class="flex gap-4 justify-between h-20 bg-slate-300 backdrop-blur-sm rounded-xl px-6 shadow-md"
>
{#each Object.entries(appList) as [appName, appData]}
<button
on:click={() => {
// jank svelte won't let you use typescript in code blocks,
// so we have to ignore this fake type error
// @ts-ignore
activeApp = appName
}}
>
<img
src={appData.icon}
alt={appName + ' icon'}
class="app-icon"
class:selected={activeApp === appName}
/>
</button>
{/each}
</div>
<style lang="postcss">
.app-icon {
@apply my-auto hover:brightness-50 h-16 rounded-2xl shadow-md transition-all duration-150;
}
.selected {
@apply brightness-50;
}
</style>

View file

@ -0,0 +1,34 @@
<script lang="ts">
import CameraContainer from './CameraContainer.svelte'
</script>
<div
class="flex gap-4 w-full py-40 px-10 backdrop-blur-lg justify-center h-full camera-background rounded-3xl"
>
<div class="my-auto">
<CameraContainer
cameraUrl="https://www.investopedia.com/thmb/1epKngue22nqkTg1v8H_Yz5O6Ng=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc()/Soros-final-f2fd5a6b1dce4ef88d60443754b79bcb.png"
/>
</div>
<div class="my-auto">
<CameraContainer
cameraUrl="https://www.investopedia.com/thmb/1epKngue22nqkTg1v8H_Yz5O6Ng=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc()/Soros-final-f2fd5a6b1dce4ef88d60443754b79bcb.png"
/>
</div>
</div>
<style lang="postcss">
.camera-background {
background: #4b79a1; /* fallback for old browsers */
background: -webkit-linear-gradient(
to right,
#4b79a1,
#283e51
); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(
to right,
#4b79a1,
#283e51
); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
</style>

View file

@ -0,0 +1,13 @@
<script lang="ts">
export let cameraUrl: string
</script>
<div>
<img
src={cameraUrl}
width="400px"
height="400px"
alt="camera feed"
class="rounded-xl shadow-md"
/>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View file

@ -0,0 +1,22 @@
<script lang="ts">
import Song from './Song.svelte'
import { songList } from './songList'
</script>
<div
class="flex gap-4 w-full py-10 px-10 backdrop-blur-xl h-full media-background rounded-3xl flex-wrap"
>
{#each songList as song}
<Song {song} />
{/each}
</div>
<style lang="postcss">
.media-background {
background: radial-gradient(
circle,
rgba(238, 174, 202, 1) 0%,
rgba(148, 187, 233, 1) 100%
);
}
</style>

View file

@ -0,0 +1,33 @@
<script lang="ts">
export let song: SongData
let { title, artist, coverImg } = song
</script>
<div
class="flex gap-1 flex-col rounded-lg p-4 bg-slate-800 backdrop-blur-xl shadow-md w-60"
>
<img src={coverImg} alt="album cover" class="shadow-md rounded-lg w-full" />
<p class="mt-2 text-3xl font-medium">{title}</p>
<p class="text-xl text-slate-400">{artist}</p>
<div class="flex justify-center">
<div class="my-auto flex gap-4">
<button class="mt-2">
<span class="material-symbols-outlined icon">skip_previous</span>
</button>
<button class="mt-2">
<span class="material-symbols-outlined icon">play_arrow</span>
</button>
<button class="mt-2">
<span class="material-symbols-outlined icon">skip_next</span>
</button>
</div>
</div>
</div>
<style lang="postcss">
.icon {
font-size: 28px;
font-variation-settings: 'FILL' 1;
}
</style>

View file

@ -0,0 +1,30 @@
export const songList = [
{
title: 'Danger Zone',
artist: 'Kenny Loggins',
src: '/static/songs/danger-zone/audio.mp3',
coverImg: '/static/songs/danger-zone/cover.png',
slug: 'danger-zone',
},
{
title: 'Danger Zone',
artist: 'Kenny Loggins',
src: '/static/songs/danger-zone/audio.mp3',
coverImg: '/static/songs/danger-zone/cover.png',
slug: 'danger-zone',
},
{
title: 'Danger Zone',
artist: 'Kenny Loggins',
src: '/static/songs/danger-zone/audio.mp3',
coverImg: '/static/songs/danger-zone/cover.png',
slug: 'danger-zone',
},
{
title: 'Danger Zone',
artist: 'Kenny Loggins',
src: '/static/songs/danger-zone/audio.mp3',
coverImg: '/static/songs/danger-zone/cover.png',
slug: 'danger-zone',
},
]

View file

@ -0,0 +1,15 @@
import Camera from './Camera/Camera.svelte'
import MusicBrowser from './MusicPlayer/MusicBrowser.svelte'
export const appList = {
'camera': {
name: 'Camera',
component: Camera,
icon: '/static/app-icons/camera.png',
},
'media-player': {
name: 'Media Player',
component: MusicBrowser,
icon: '/static/app-icons/media-player.png',
},
}

View file

@ -0,0 +1,21 @@
<script lang="ts">
import TopBar from './TopBar/TopBar.svelte'
import Speedometer from './Speedometer.svelte'
import SpeedLimit from './SpeedLimit.svelte'
import MediaDisplay from './MediaDisplay/MediaDisplay.svelte'
</script>
<div class="mt-2">
<div class="px-5">
<TopBar />
<div class="h-0.5 mt-1 w-full bg-slate-300 border-0"></div>
<div class="mt-8 flex justify-between">
<Speedometer />
<SpeedLimit />
</div>
</div>
<div class="fixed bottom-0 w-[35vw]">
<MediaDisplay />
</div>
</div>

View file

@ -0,0 +1,28 @@
<script lang="ts">
export let playing = false
</script>
<div class="my-auto flex gap-4">
<button class="mt-2">
<span class="material-symbols-outlined icon">skip_previous</span>
</button>
{#if playing}
<button class="mt-2">
<span class="material-symbols-outlined icon"> pause </span>
</button>
{:else}
<button class="mt-2">
<span class="material-symbols-outlined icon">play_arrow</span>
</button>
{/if}
<button class="mt-2">
<span class="material-symbols-outlined icon">skip_next</span>
</button>
</div>
<style lang="postcss">
.icon {
font-size: 34px;
font-variation-settings: 'FILL' 1;
}
</style>

View file

@ -0,0 +1,20 @@
<script lang="ts">
import Controls from './Controls.svelte'
</script>
<div class="rounded-t-lg bg-neutral-800 px-4 py-2 h-24 flex justify-between">
<div class="flex gap-6">
<div class="aspect-square">
<img
src="https://upload.wikimedia.org/wikipedia/en/thumb/2/2c/Loggins_-_Danger_Zone_single_cover.png/220px-Loggins_-_Danger_Zone_single_cover.png"
alt="album cover"
class="w-full h-full object-cover rounded-lg shadow-sm shadow-neutral-500"
/>
</div>
<div class="my-auto">
<p class="text-xl font-medium">Danger Zone</p>
<p class="text-lg text-slate-400">Kenny Loggins</p>
</div>
</div>
<Controls playing={false} />
</div>

View file

@ -0,0 +1,18 @@
<script lang="ts">
export let speedLimit: number = 5.0
$: formatted = speedLimit.toFixed(1)
</script>
<div
class="bg-white p-[0.15rem] text-black rounded-xl shadow-md shadow-neutral-500"
>
<div
class="px-3 py-1 border-black rounded-xl border-2 flex flex-col text-center gap-1"
>
<div class="text-lg font-medium">
SPEED<br />LIMIT
</div>
<div class="text-2xl font-bold">{formatted}</div>
</div>
</div>

View file

@ -0,0 +1,11 @@
<script lang="ts">
// in mph
export let speed: number = 0.0
$: formatted = speed.toFixed(1)
</script>
<div class="flex flex-col gap-4">
<div class="text-6xl">{formatted}</div>
<div class="text-2xl font-medium">MPH</div>
</div>

View file

@ -0,0 +1,18 @@
<script lang="ts">
export let voltage: number
$: formatted = voltage.toFixed(1)
</script>
<span class="flex gap-1">
<div class="text-lg font-medium">
{formatted} V
</div>
<span class="material-symbols-outlined battery-icon">battery_horiz_075</span>
</span>
<style lang="postcss">
.battery-icon {
font-size: 30.8px;
}
</style>

View file

@ -0,0 +1,20 @@
<script lang="ts">
export let selectedGear: Gear
</script>
<div class="flex justify-center w-full">
<div class="flex flex-row gap-2 text-neutral-400 text-xl font-bold">
<div class:highlighted={selectedGear === 'p'}>P</div>
<div class:highlighted={selectedGear === 'r'}>R</div>
<div class:highlighted={selectedGear === 'n'}>N</div>
<div class:highlighted={selectedGear === 'l'}>L</div>
<div class:highlighted={selectedGear === 'a'}>A</div>
<div class:highlighted={selectedGear === 'd'}>D</div>
</div>
</div>
<style lang="postcss">
.highlighted {
@apply text-white;
}
</style>

View file

@ -0,0 +1,21 @@
<script lang="ts">
export let selectedMode: Mode
let modeText = ''
switch (selectedMode) {
case 'chill':
modeText = 'CHILL'
break
case 'cruise':
modeText = 'CRUISE'
break
case 'ludicrous':
modeText = 'LUDICROUS'
break
}
</script>
<div class="font-medium text-xl">
{modeText}
</div>

View file

@ -0,0 +1,17 @@
<script>
import BatteryDisplay from './BatteryDisplay.svelte'
import GearSelector from './GearSelector.svelte'
import ModeSelector from './ModeSelector.svelte'
</script>
<div class="flex flex-row w-full justify-between">
<div>
<GearSelector selectedGear="p" />
</div>
<div>
<ModeSelector selectedMode="chill" />
</div>
<div>
<BatteryDisplay voltage={12.5} />
</div>
</div>

17
package-lock.json generated
View file

@ -1,17 +0,0 @@
{
"name": "jankboard-2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@fontsource/roboto": "^5.0.8"
}
},
"node_modules/@fontsource/roboto": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz",
"integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA=="
}
}
}

View file

@ -1,5 +0,0 @@
{
"dependencies": {
"@fontsource/roboto": "^5.0.8"
}
}