add many features
This commit is contained in:
parent
699f655b03
commit
fa3bc3c18a
30 changed files with 542 additions and 38 deletions
Binary file not shown.
|
@ -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.
|
||||
|
|
BIN
app/static/app-icons/camera.png
Normal file
BIN
app/static/app-icons/camera.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
BIN
app/static/app-icons/media-player.png
Normal file
BIN
app/static/app-icons/media-player.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
app/static/songs/danger-zone/audio.mp3
Normal file
BIN
app/static/songs/danger-zone/audio.mp3
Normal file
Binary file not shown.
BIN
app/static/songs/danger-zone/cover.png
Normal file
BIN
app/static/songs/danger-zone/cover.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
104
client/package-lock.json
generated
104
client/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -14,6 +14,6 @@
|
|||
}
|
||||
|
||||
body {
|
||||
@apply bg-black;
|
||||
@apply bg-black text-white;
|
||||
}
|
||||
}
|
||||
|
|
21
client/src/globals.d.ts
vendored
Normal file
21
client/src/globals.d.ts
vendored
Normal 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
|
||||
}
|
||||
}
|
36
client/src/lib/Apps/AppBar.svelte
Normal file
36
client/src/lib/Apps/AppBar.svelte
Normal 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>
|
34
client/src/lib/Apps/Camera/Camera.svelte
Normal file
34
client/src/lib/Apps/Camera/Camera.svelte
Normal 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>
|
13
client/src/lib/Apps/Camera/CameraContainer.svelte
Normal file
13
client/src/lib/Apps/Camera/CameraContainer.svelte
Normal 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>
|
BIN
client/src/lib/Apps/Camera/icon.png
Normal file
BIN
client/src/lib/Apps/Camera/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
22
client/src/lib/Apps/MusicPlayer/MusicBrowser.svelte
Normal file
22
client/src/lib/Apps/MusicPlayer/MusicBrowser.svelte
Normal 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>
|
33
client/src/lib/Apps/MusicPlayer/Song.svelte
Normal file
33
client/src/lib/Apps/MusicPlayer/Song.svelte
Normal 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>
|
30
client/src/lib/Apps/MusicPlayer/songList.ts
Normal file
30
client/src/lib/Apps/MusicPlayer/songList.ts
Normal 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',
|
||||
},
|
||||
]
|
15
client/src/lib/Apps/appList.ts
Normal file
15
client/src/lib/Apps/appList.ts
Normal 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',
|
||||
},
|
||||
}
|
21
client/src/lib/Dashboard/Dashboard.svelte
Normal file
21
client/src/lib/Dashboard/Dashboard.svelte
Normal 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>
|
28
client/src/lib/Dashboard/MediaDisplay/Controls.svelte
Normal file
28
client/src/lib/Dashboard/MediaDisplay/Controls.svelte
Normal 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>
|
20
client/src/lib/Dashboard/MediaDisplay/MediaDisplay.svelte
Normal file
20
client/src/lib/Dashboard/MediaDisplay/MediaDisplay.svelte
Normal 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>
|
18
client/src/lib/Dashboard/SpeedLimit.svelte
Normal file
18
client/src/lib/Dashboard/SpeedLimit.svelte
Normal 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>
|
11
client/src/lib/Dashboard/Speedometer.svelte
Normal file
11
client/src/lib/Dashboard/Speedometer.svelte
Normal 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>
|
18
client/src/lib/Dashboard/TopBar/BatteryDisplay.svelte
Normal file
18
client/src/lib/Dashboard/TopBar/BatteryDisplay.svelte
Normal 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>
|
20
client/src/lib/Dashboard/TopBar/GearSelector.svelte
Normal file
20
client/src/lib/Dashboard/TopBar/GearSelector.svelte
Normal 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>
|
21
client/src/lib/Dashboard/TopBar/ModeSelector.svelte
Normal file
21
client/src/lib/Dashboard/TopBar/ModeSelector.svelte
Normal 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>
|
17
client/src/lib/Dashboard/TopBar/TopBar.svelte
Normal file
17
client/src/lib/Dashboard/TopBar/TopBar.svelte
Normal 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
17
package-lock.json
generated
|
@ -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=="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.0.8"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue