Merge branch 'main' of https://repos.hackathon.bz.it/2025-summer/team-1
This commit is contained in:
commit
656d1e40c7
15 changed files with 137 additions and 60 deletions
|
@ -5,13 +5,13 @@ from flask import Flask, Response, jsonify, request
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from flask_socketio import SocketIO, join_room, leave_room
|
from flask_socketio import SocketIO, join_room, leave_room
|
||||||
|
|
||||||
from .state import State
|
from state import State
|
||||||
from .connect import get_connection
|
from connect import get_connection
|
||||||
from .room import Room
|
from room import Room
|
||||||
from .song import Song, init_db, get_song_by_title_artist, add_song_in_db, get_song_by_uuid
|
from song import Song, init_db, get_song_by_title_artist, add_song_in_db, get_song_by_uuid
|
||||||
from .song_fetch import query_search, yt_get_audio_url, yt_search_song
|
from song_fetch import query_search, yt_get_audio_url, yt_search_song
|
||||||
from .qrcode_gen import generate_qr
|
from qrcode_gen import generate_qr
|
||||||
from .gps import is_within_range, distance_between_coords, Coordinates
|
from gps import is_within_range, distance_between_coords, Coordinates
|
||||||
|
|
||||||
|
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
|
@ -29,7 +29,7 @@ init_db(state.db)
|
||||||
|
|
||||||
state.rooms[1000] = Room(
|
state.rooms[1000] = Room(
|
||||||
id=1000,
|
id=1000,
|
||||||
coord=(1.0, 5.5),
|
coord=Coordinates(46.6769043, 11.1851585),
|
||||||
name="Test Room",
|
name="Test Room",
|
||||||
pin=None,
|
pin=None,
|
||||||
tags=set(),
|
tags=set(),
|
||||||
|
@ -85,7 +85,15 @@ def join():
|
||||||
if room.pin is not None and room.pin != code:
|
if room.pin is not None and room.pin != code:
|
||||||
return error("Invalid code")
|
return error("Invalid code")
|
||||||
|
|
||||||
if room.distance > room.range_size:
|
distance = distance_between_coords(
|
||||||
|
lhs=room.coord,
|
||||||
|
rhs=Coordinates(
|
||||||
|
latitude=float(request.args["lat"]),
|
||||||
|
longitude=float(request.args["lon"]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if distance > room.range_size:
|
||||||
return error("You are not within the room range")
|
return error("You are not within the room range")
|
||||||
|
|
||||||
return {"success": True, "ws": f"/ws/{room_id}"}
|
return {"success": True, "ws": f"/ws/{room_id}"}
|
||||||
|
@ -144,8 +152,8 @@ def room_new():
|
||||||
lat, lon = room_cords.split(",")
|
lat, lon = room_cords.split(",")
|
||||||
|
|
||||||
room = Room(
|
room = Room(
|
||||||
id=max(state.rooms or [0]) + 1, #
|
id=max(state.rooms or [0]) + 1,
|
||||||
coord=(float(lat), float(lon)),
|
coord=Coordinates(float(lat), float(lon)),
|
||||||
range_size=int(room_range),
|
range_size=int(room_range),
|
||||||
name=room_name,
|
name=room_name,
|
||||||
pin=room_pin,
|
pin=room_pin,
|
||||||
|
@ -165,12 +173,12 @@ def room_new():
|
||||||
def room():
|
def room():
|
||||||
lat = request.args.get("lat")
|
lat = request.args.get("lat")
|
||||||
lon = request.args.get("lon")
|
lon = request.args.get("lon")
|
||||||
|
|
||||||
if lat and lon:
|
if lat and lon:
|
||||||
user_coords = Coordinates(latitude=float(lat), longitude=float(lon))
|
user_coords = Coordinates(latitude=float(lat), longitude=float(lon))
|
||||||
else:
|
else:
|
||||||
return error("Missing user coordinates")
|
return error("Missing user coordinates")
|
||||||
|
|
||||||
distance = distance_between_coords(user_coords, room.coord)
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"id": room.id,
|
"id": room.id,
|
||||||
|
@ -178,10 +186,10 @@ def room():
|
||||||
"private": room.pin is not None,
|
"private": room.pin is not None,
|
||||||
"coords": room.coord,
|
"coords": room.coord,
|
||||||
"range": room.range_size,
|
"range": room.range_size,
|
||||||
"distance": distance,
|
"distance": d,
|
||||||
}
|
}
|
||||||
for room in state.rooms.values()
|
for room in state.rooms.values()
|
||||||
if distance <= room.range_size
|
if (d := distance_between_coords(user_coords, room.coord)) <= room.range_size
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import math
|
import math
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Coordinates:
|
class Coordinates:
|
||||||
latitude: int
|
latitude: float
|
||||||
longitude: int
|
longitude: float
|
||||||
|
|
||||||
|
|
||||||
def distance_between_coords(lhs: Coordinates, rhs: Coordinates) -> float:
|
def distance_between_coords(lhs: Coordinates, rhs: Coordinates) -> float:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import random
|
import random
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from gps import Coordinates
|
||||||
from song import Song
|
from song import Song
|
||||||
|
|
||||||
USER_SCORE_WEIGHT = 0.7
|
USER_SCORE_WEIGHT = 0.7
|
||||||
|
@ -30,12 +31,11 @@ class Rank:
|
||||||
@dataclass
|
@dataclass
|
||||||
class Room:
|
class Room:
|
||||||
id: int
|
id: int
|
||||||
coord: tuple[float, float]
|
coord: Coordinates
|
||||||
name: str
|
name: str
|
||||||
pin: int | None
|
pin: int | None
|
||||||
tags: set[str]
|
tags: set[str]
|
||||||
range_size: int # in meters ??
|
range_size: int # in meters ??
|
||||||
distance: float
|
|
||||||
|
|
||||||
songs: dict[str, UserScoredSong] # all songs + user score (the playlist)
|
songs: dict[str, UserScoredSong] # all songs + user score (the playlist)
|
||||||
history: list[Song] # all songs previously played
|
history: list[Song] # all songs previously played
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Lilita+One&display=swap');
|
||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
|
|
||||||
@keyframes spin-slower {
|
@keyframes spin-slower {
|
||||||
|
@ -14,6 +15,20 @@
|
||||||
animation: spin-slower 15s linear infinite;
|
animation: spin-slower 15s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
@apply bg-light-pine-base dark:bg-dark-pine-base;
|
||||||
|
/* or use your color class here */
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lilita-one-regular {
|
||||||
|
font-family: "Lilita One", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-dark-pine-base: hsl(249deg, 22%, 12%);
|
--color-dark-pine-base: hsl(249deg, 22%, 12%);
|
||||||
--color-dark-pine-surface: hsl(247deg, 23%, 15%);
|
--color-dark-pine-surface: hsl(247deg, 23%, 15%);
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
queueSongs[playingIndex],
|
queueSongs[playingIndex],
|
||||||
playingIndex == queueSongs.length - 1 ? createEmptySong() : queueSongs[playingIndex + 1],
|
playingIndex == queueSongs.length - 1 ? createEmptySong() : queueSongs[playingIndex + 1],
|
||||||
])
|
])
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
console.log(displaySongs)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative flex w-full justify-center overflow-hidden">
|
<div class="relative flex w-full justify-center overflow-hidden">
|
||||||
|
@ -28,11 +32,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex h-[60vw] max-h-[250px] w-[60vw] max-w-[250px] items-center justify-center">
|
<div class="flex h-[60vw] max-h-[250px] w-[60vw] max-w-[250px] items-center justify-center"></div>
|
||||||
{#if i === 1}
|
|
||||||
<p>No song in queue</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { type Room } from "$lib/types"
|
|
||||||
let { room }: { room: Room } = $props()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="room flex flex-col items-start gap-2 rounded-lg bg-white p-6 shadow-md">
|
|
||||||
<h2 class="flex items-center gap-2 text-xl font-semibold">
|
|
||||||
{room.name}
|
|
||||||
{#if room.private}
|
|
||||||
<span class="text-gray-500">🔒</span>
|
|
||||||
{/if}
|
|
||||||
</h2>
|
|
||||||
<!-- <span class="text-sm text-gray-600"> -->
|
|
||||||
<!-- {participants} participant{participants === 1 ? "" : "s"} -->
|
|
||||||
<!-- </span> -->
|
|
||||||
</div>
|
|
18
frontend/src/lib/components/RoomComponent.svelte
Normal file
18
frontend/src/lib/components/RoomComponent.svelte
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { type Room } from "$lib/types"
|
||||||
|
let { room }: { room: Room } = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="flex w-82 cursor-pointer flex-row items-center rounded-md border border-dark-pine-muted bg-light-pine-overlay p-3 hover:bg-dark-pine-base/20 dark:bg-dark-pine-overlay hover:dark:bg-light-pine-base/20"
|
||||||
|
>
|
||||||
|
<div class="flex flex-row">
|
||||||
|
{room.name}
|
||||||
|
{room.private ? "🔒" : ""}
|
||||||
|
</div>
|
||||||
|
<div class="grow"></div>
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<div class="font-mono">64m</div>
|
||||||
|
<div class="rounded bg-light-pine-blue px-2 py-0.5 text-dark-pine-text dark:bg-dark-pine-blue">Join</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
|
@ -4,15 +4,25 @@
|
||||||
let input = $state("")
|
let input = $state("")
|
||||||
|
|
||||||
async function sendSong() {
|
async function sendSong() {
|
||||||
let resp = await fetch(`/api/addsong?room=${roomId}&query=${input}`, { method: "POST" })
|
await fetch(`/api/addsong?room=${roomId}&query=${input}`, { method: "POST" })
|
||||||
input = ""
|
input = ""
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="bg-lime-500 flex h-full w-full flex-row items-center gap-2 rounded border-2 border-lime-600">
|
<div class="flex h-full w-full flex-row items-center gap-2 rounded border-2 border-lime-600 bg-lime-500">
|
||||||
<input type="text" placeholder="Song & Artist" class="font-bold outline-none text-white h-[50px] px-4 w-3/4 rounded" bind:value={input} />
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Song & Artist"
|
||||||
|
class="h-[50px] w-3/4 rounded px-4 font-bold text-white outline-none"
|
||||||
|
bind:value={input}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (e.key == "Enter") {
|
||||||
|
sendSong()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
class="shadow-xl hover:scale-105 w-1/4 h-[40px] cursor-pointer bg-lime-600 border-lime-700 font-semibold text-white border-2 i-lucide-check rounded active:scale-90 duration-100"
|
class="i-lucide-check h-[40px] w-1/4 cursor-pointer rounded border-2 border-lime-700 bg-lime-600 font-semibold text-white shadow-xl duration-100 hover:scale-105 active:scale-90"
|
||||||
onclick={sendSong}>Add</button
|
onclick={sendSong}>Add</button
|
||||||
>
|
>
|
||||||
<span class="i-lucide-chevrons-left"></span>
|
<span class="i-lucide-chevrons-left"></span>
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const createEmptySong = function(): Song {
|
||||||
artist: "",
|
artist: "",
|
||||||
tags: [""],
|
tags: [""],
|
||||||
image_id: "",
|
image_id: "",
|
||||||
youtube_id: 0,
|
youtube_id: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ const RoomSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
private: z.boolean(),
|
private: z.boolean(),
|
||||||
coords: z.tuple([z.number(), z.number()]),
|
coords: z.tuple([z.number(), z.number()]),
|
||||||
range: z.number().int()
|
range: z.number().int(),
|
||||||
})
|
})
|
||||||
export type Room = z.infer<typeof RoomSchema>
|
export type Room = z.infer<typeof RoomSchema>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { parseSong, parseSuggestion, type FetchError, type Song, type Suggestion } from "./types"
|
import { parseSong, parseSuggestion, type FetchError, type Song, type Suggestion } from "./types"
|
||||||
|
|
||||||
export const joinRoom = async function (roomId: string): Promise<[FetchError | null, string]> {
|
export const joinRoom = async function (roomId: string, lat: number, lon: number): Promise<[FetchError | null, string]> {
|
||||||
let resp = await fetch("/api/join?room=" + roomId)
|
let resp = await fetch(`/api/join?room=${roomId}&lat=${lat}&lon=${lon}`)
|
||||||
|
|
||||||
if (resp.status != 200) {
|
if (resp.status != 200) {
|
||||||
return [{ code: 400, message: "Cannot join the room" }, ""]
|
return [{ code: 400, message: "Cannot join the room" }, ""]
|
||||||
|
|
|
@ -1,10 +1,40 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import RoomComponent from "$lib/components/Room.svelte"
|
import RoomComponent from "$lib/components/RoomComponent.svelte"
|
||||||
import { parseRoom, type Room } from "$lib/types"
|
import { parseRoom, type Room } from "$lib/types"
|
||||||
|
import { Plus } from "@lucide/svelte"
|
||||||
|
|
||||||
let room: Room = { id: "asd", coords: [0.123, 0.456], name: "scatolame party", private: true, range: 124 }
|
let room: Room = { id: "asd", coords: [0.123, 0.456], name: "scatolame party", private: true, range: 124 }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col items-center gap-2">
|
||||||
|
<div class="flex flex-row items-center gap-4 py-4">
|
||||||
|
<img src="icon_small.png" class="h-12 w-12" alt="chillbox icon" />
|
||||||
|
<span class="lilita-one-regular text-6xl font-bold">ChillBox</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img src="/smerdo_radar_bonus.gif" alt="radar" class="h-64 w-64" />
|
||||||
|
<span class="animate-pulse text-sm italic">Scanning for rooms near you...</span>
|
||||||
|
|
||||||
|
<button class="fixed right-4 bottom-4 flex flex-row gap-1 rounded-xl bg-light-pine-blue p-2 text-dark-pine-text sm:right-20 md:right-40 lg:right-80 dark:bg-dark-pine-blue">
|
||||||
|
<span> New Room </span>
|
||||||
|
<Plus />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<RoomComponent {room}></RoomComponent>
|
||||||
|
<RoomComponent {room}></RoomComponent>
|
||||||
|
<RoomComponent {room}></RoomComponent>
|
||||||
|
<RoomComponent {room}></RoomComponent>
|
||||||
|
<RoomComponent {room}></RoomComponent>
|
||||||
|
<RoomComponent {room}></RoomComponent>
|
||||||
|
<RoomComponent {room}></RoomComponent>
|
||||||
|
<RoomComponent {room}></RoomComponent>
|
||||||
|
<RoomComponent {room}></RoomComponent>
|
||||||
|
<RoomComponent {room}></RoomComponent>
|
||||||
|
<RoomComponent {room}></RoomComponent>
|
||||||
|
<RoomComponent {room}></RoomComponent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- <div class="h-full w-full flex-row justify-center p-4"> -->
|
<!-- <div class="h-full w-full flex-row justify-center p-4"> -->
|
||||||
<!-- <div class="relative min-h-screen justify-center justify-items-center"> -->
|
<!-- <div class="relative min-h-screen justify-center justify-items-center"> -->
|
||||||
<!-- <h1>Scan your nearby rooms</h1> -->
|
<!-- <h1>Scan your nearby rooms</h1> -->
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
let url = await getStreamingUrl(currentPlaying.uuid)
|
let url = await getStreamingUrl(currentPlaying.uuid)
|
||||||
if (audioController) {
|
if (audioController) {
|
||||||
audioController.src = url
|
audioController.src = url
|
||||||
audioController.play()
|
await audioController.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,11 +29,10 @@
|
||||||
|
|
||||||
queueSongs = songs
|
queueSongs = songs
|
||||||
playingIndex = index
|
playingIndex = index
|
||||||
|
})
|
||||||
|
|
||||||
if (audioController) {
|
$effect(() => {
|
||||||
audioController.src = await getStreamingUrl(currentPlaying.uuid)
|
playOnEnd()
|
||||||
audioController.play()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
async function playNext() {
|
async function playNext() {
|
||||||
|
@ -53,6 +52,6 @@
|
||||||
<div class="flex w-full flex-col items-center justify-center p-4 lg:p-10">
|
<div class="flex w-full flex-col items-center justify-center p-4 lg:p-10">
|
||||||
<QueueSlider {queueSongs} {playingIndex} />
|
<QueueSlider {queueSongs} {playingIndex} />
|
||||||
<button onclick={playNext}>Next</button>
|
<button onclick={playNext}>Next</button>
|
||||||
<audio controls autoplay bind:this={audioController} onended={playOnEnd}></audio>
|
<audio controls autoplay bind:this={audioController} onended={playNext}></audio>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
import { getQueueSongs, getSuggestions, joinRoom } from "$lib/utils.js"
|
import { getQueueSongs, getSuggestions, joinRoom } from "$lib/utils.js"
|
||||||
import type { FetchError } from "$lib/types.js"
|
import type { FetchError } from "$lib/types.js"
|
||||||
import { io, Socket } from "socket.io-client"
|
import { io, Socket } from "socket.io-client"
|
||||||
|
import { get_coords } from "$lib/gps.js"
|
||||||
|
import type { Coordinates } from "$lib/gps.js"
|
||||||
|
|
||||||
let { data } = $props()
|
let { data } = $props()
|
||||||
|
|
||||||
|
@ -23,8 +25,14 @@
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
// Join the room
|
// Join the room
|
||||||
|
|
||||||
|
let { coords, error } = await get_coords()
|
||||||
|
if (error || coords == null) {
|
||||||
|
// Default to Lido
|
||||||
|
coords = { latitude: 46.6769043, longitude: 11.1851585 }
|
||||||
|
}
|
||||||
|
|
||||||
let sugg, queue, index
|
let sugg, queue, index
|
||||||
;[returnError] = await joinRoom(data.roomId)
|
;[returnError] = await joinRoom(data.roomId, coords.latitude, coords.longitude)
|
||||||
if (returnError) {
|
if (returnError) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -69,7 +77,7 @@
|
||||||
{#if returnError}
|
{#if returnError}
|
||||||
<Error {returnError} />
|
<Error {returnError} />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex w-full flex-col items-center justify-center py-4 px-2 lg:p-10">
|
<div class="flex w-full flex-col items-center justify-center px-2 py-4 lg:p-10">
|
||||||
<QueueSlider {queueSongs} {playingIndex} />
|
<QueueSlider {queueSongs} {playingIndex} />
|
||||||
<div class="w-full py-6 lg:w-[30vw]">
|
<div class="w-full py-6 lg:w-[30vw]">
|
||||||
<SuggestionInput roomId={data.roomId} />
|
<SuggestionInput roomId={data.roomId} />
|
||||||
|
|
3
frontend/static/CHILLBOX.svg
Normal file
3
frontend/static/CHILLBOX.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.2 KiB |
BIN
frontend/static/icon_small.png
Normal file
BIN
frontend/static/icon_small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Loading…
Add table
Add a link
Reference in a new issue