diff --git a/README.md b/README.md index 84edd62..65f5fc8 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,3 @@ -# ChillBox +# team-1 -> *A project by Pausetta.org, Simone Tesini, Francesco De Carlo, Leonardo Segala, Matteo Peretto* - -**ChillBox** is a web app that lets you create a shared radio station with a democratic voting system, so everyone gets to enjoy their favorite music together. -Perfect for venues like swimming pools, cafรฉs, or even lively parties. - ---- - -## ๐ŸŽต Voting System - -Joining a ChillBox room is easy: users can either scan the QR code displayed on the host screen or use GPS to find nearby rooms. -Hosts can set a location range, ensuring only people physically present can add or vote for songs. - ---- - -## ๐Ÿ“Š Ranking Algorithm - -ChillBox uses a smart ranking algorithm to decide what plays next. The score of each song is based on: - -* Votes from users -* How recently similar songs (same genre or artist) have been played (less = better) -* A bit of randomness to keep things interesting -* A strong penalty for songs played too recently - ---- - -## ๐Ÿ‘ Hands-Off Experience - -ChillBox is designed to be almost entirely hands-free. -Once the host sets up a room and optionally connects a screen or projector -(to show the current track, QR code, etc.), ChillBox takes care of the rest. - -ChillBox comes with built-in automatic moderation to keep the music fair and on-theme. - -* Users canโ€™t vote for the same song multiple times. -* A cooldown prevents users from spamming song requests. -* Hosts can define preferred genres and overall mood, so no one can hijack your chill beach vibes with unexpected death metal. - -That said, hosts still have access to essential controls, like pause and skip, if needed. +Test diff --git a/SPEECH.md b/SPEECH.md deleted file mode 100644 index 2ab507f..0000000 --- a/SPEECH.md +++ /dev/null @@ -1,15 +0,0 @@ -# speech - -## Home screen -We start here in the home page. -We can see this little radar animation, which means that the app is looking for nearby ChillBox rooms to join. -It uses GPS for this feature. - -## Join room -When we join a room, the server checks our location and checks if it's within a specified range. -That way, you must physically be in the location to actually be able to add new songs - -## Talk about the host -As you can see here (and hear) on the left, the host is already playing some music. -Now i will add a song on the client side and it will pop up in the list. - diff --git a/backend/Dockerfile b/backend/Dockerfile index 3390030..c6a1412 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,6 +12,5 @@ RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 5000 -# CMD ["flask", "--app", "src.app", "run", "--debug", "--host=0.0.0.0"] -CMD ["python3", "src/app.py"] +CMD ["flask", "--app", "src.app", "run", "--debug", "--host=0.0.0.0"] # flask --app src.app run --host=0.0.0.0 --port=5001 --debug diff --git a/backend/requirements.txt b/backend/requirements.txt index cb18a70..5eae02e 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,5 +4,4 @@ flask-socketio dotenv requests qrcode -Pillow -eventlet>=0.33 +Pillow \ No newline at end of file diff --git a/backend/src/app.py b/backend/src/app.py index 28c66e9..a99444c 100644 --- a/backend/src/app.py +++ b/backend/src/app.py @@ -2,16 +2,16 @@ import uuid from dataclasses import asdict import dotenv -from connect import get_connection from flask import Flask, Response, jsonify, request from flask_cors import CORS from flask_socketio import SocketIO, join_room, leave_room -from gps import Coordinates, distance_between_coords -from qrcode_gen import generate_qr -from room import Room -from song import Song, add_song_in_db, get_song_by_title_artist, get_song_by_uuid, init_db -from song_fetch import query_search, yt_get_audio_url, yt_search_song -from state import State + +from .connect import get_connection +from .qrcode_gen import generate_qr +from .room import Room +from .song import Song, add_song_in_db, get_song_by_title_artist, init_db +from .song_fetch import download_song_mp3, lastfm_query_search +from .state import State dotenv.load_dotenv() @@ -28,14 +28,14 @@ init_db(state.db) state.rooms[1000] = Room( id=1000, - coord=Coordinates(46.6769043, 11.1851585), - name="Lido Scena", - pin=1234, - tags=set(["chill", "raggaetton", "spanish", "latino", "mexican", "rock"]), - range_size=150, - songs={}, + coord=(1.0, 5.5), + name="Test Room", + pin=None, + tags=set(), + range_size=100, + songs={"b": (Song(uuid="b", title="title", artist="art", tags=["a", "B"], image_id="img", youtube_id="yt"), 1)}, history=[], - playing=[], + playing=[Song(uuid="", title="", artist="<artist>", tags=[], image_id="<img>", youtube_id="<yt>")], playing_idx=0, ) @@ -73,7 +73,7 @@ def on_leave(data): @app.get("/api/join") def join(): room_id = request.args.get("room") - code = request.args.get("pin") + code = request.args.get("code") if room_id is None: return error("Missing room id") @@ -81,22 +81,8 @@ def join(): if (room := state.rooms.get(int(room_id))) is None: return error("Invalid room") - if room.pin is not None: - if code is None: - return error("Missing code") - if int(room.pin) != int(code): - return error("Invalid code") - - 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") + if room.pin is not None and room.pin != code: + return error("Invalid code") return {"success": True, "ws": f"/ws/{room_id}"} @@ -124,15 +110,13 @@ def queue_next(): if room.playing_idx >= len(room.playing): ## queue ended - room.renew_queue() - ended = True - else: - ended = False - data = {"success": True, "ended": ended, "index": room.playing_idx, "queue": [asdict(s) for s in room.playing]} - state.socketio.emit("queue_update", data, to=str(room.id)) + # room.renew_queue() + state.socketio.emit("update_queue", {"songs": room.playing}, to=str(room.id)) - return data + return {"success": True, "ended": True, "index": room.playing_idx, "queue": room.playing} + + return {"success": True, "ended": False, "index": room.playing_idx} @app.post("/api/room/new") @@ -154,8 +138,8 @@ def room_new(): lat, lon = room_cords.split(",") room = Room( - id=max(state.rooms or [0]) + 1, - coord=Coordinates(float(lat), float(lon)), + id=max(state.rooms or [0]) + 1, # + coord=(float(lat), float(lon)), range_size=int(room_range), name=room_name, pin=room_pin, @@ -173,14 +157,6 @@ def room_new(): @app.get("/api/room") def room(): - lat = request.args.get("lat") - lon = request.args.get("lon") - - if lat and lon: - user_coords = Coordinates(latitude=float(lat), longitude=float(lon)) - else: - return error("Missing user coordinates") - return [ { "id": room.id, @@ -188,10 +164,8 @@ def room(): "private": room.pin is not None, "coords": room.coord, "range": room.range_size, - "distance": d, } for room in state.rooms.values() - if (d := distance_between_coords(user_coords, room.coord)) <= room.range_size ] @@ -206,15 +180,12 @@ def add_song(): if (query := request.args.get("query")) is None: return error("Missing query") - if (info := query_search(query)) is None: - return error("Search failed") + info = lastfm_query_search(query) if (song := get_song_by_title_artist(info.title, info.artist)) is None: ## song not found, downolad from YT - yt_video_id = yt_search_song(info.title, info.artist) - - if yt_video_id is None: - return error("No video found on youtube") + if (res := download_song_mp3(info.title, info.artist)) is None: + return error("Cannot get info from YT") ## add in DB song = Song( @@ -223,24 +194,14 @@ def add_song(): artist=info.artist, tags=info.tags, image_id=info.img_id, - youtube_id=yt_video_id, + youtube_id=res[0], ) add_song_in_db(song) - if len(room.tags) > 0: - tag_ok = False - for tag in song.tags: - if tag in room.tags: - tag_ok = True - - if not tag_ok: - return error("Song genre does not belong to this room") - ## add the song in the room if does not exists if song.uuid not in room.songs: room.songs[song.uuid] = (song, 1) # start with one vote - socketio.emit("new_song", {"song": asdict(song) | {"upvote": 1}}, to=str(room.id)) return {"success": True, "song": song} @@ -272,7 +233,6 @@ def post_song_vote(): ## update the song room.songs[song_id] = (song_info[0], song_info[1] + int(request.args["increment"])) - socketio.emit("new_vote", {"song": asdict(song_info[0]) | {"upvote": song_info[1]}}) return {"success": True} @@ -294,19 +254,5 @@ def room_qrcode(): return Response(stream, content_type="image/jpeg") -@app.get("/api/song/audio") -def get_audio_url(): - if (song_uuid := request.args.get("song")) is None: - return error("Missing song id") - - if (song := get_song_by_uuid(song_uuid)) is None: - return error("Song not found") - - if (url := yt_get_audio_url(song.youtube_id)) is None: - return error("Cannot get audio url") - - return {"success": True, "url": url} - - if __name__ == "__main__": - socketio.run(app, host="0.0.0.0", port=5000, debug=True) + socketio.run(app, debug=True) diff --git a/backend/src/gps.py b/backend/src/gps.py deleted file mode 100644 index 98bda0c..0000000 --- a/backend/src/gps.py +++ /dev/null @@ -1,29 +0,0 @@ -import math -from dataclasses import dataclass - - -@dataclass -class Coordinates: - latitude: float - longitude: float - - -def distance_between_coords(lhs: Coordinates, rhs: Coordinates) -> float: - R = 6371000 # Earth radius in meters - - def to_rad(deg): - return (deg * math.pi) / 180 - - d_lat = to_rad(rhs.latitude - lhs.latitude) - d_lon = to_rad(rhs.longitude - lhs.longitude) - - a = (d_lat / 2) ** 2 + (to_rad(lhs.latitude) * to_rad(rhs.latitude)) * (d_lon / 2) ** 2 - - c = 2 * (a**0.5) / ((1 - a) ** 0.5) - - return R * c # distance in meters - - -def is_within_range(my_coords: Coordinates, target_coords: Coordinates, max_range: float) -> bool: - distance = distance_between_coords(my_coords, target_coords) - return distance <= max_range diff --git a/backend/src/room.py b/backend/src/room.py index cb775b6..5109884 100644 --- a/backend/src/room.py +++ b/backend/src/room.py @@ -1,8 +1,7 @@ import random from dataclasses import dataclass -from gps import Coordinates -from song import Song +from .song import Song USER_SCORE_WEIGHT = 0.7 ARTIST_WEIGHT = 0.1 @@ -31,7 +30,7 @@ class Rank: @dataclass class Room: id: int - coord: Coordinates + coord: tuple[float, float] name: str pin: int | None tags: set[str] diff --git a/backend/src/song.py b/backend/src/song.py index f95609b..7409d06 100644 --- a/backend/src/song.py +++ b/backend/src/song.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from sqlite3 import Cursor -from connect import get_connection +from .connect import get_connection def init_db(db: Cursor): diff --git a/backend/src/song_fetch.py b/backend/src/song_fetch.py index 905d126..561e18d 100644 --- a/backend/src/song_fetch.py +++ b/backend/src/song_fetch.py @@ -1,10 +1,9 @@ -import os -import os.path -import sys -import urllib.parse -from dataclasses import dataclass - import requests +import urllib.parse +import os.path +import os +import sys +from dataclasses import dataclass sys.path.append("/yt-dlp") import yt_dlp @@ -18,7 +17,7 @@ class SongInfo: tags: list[str] -def _lastfm_search(query: str) -> tuple[str, str] | None: +def _lastfm_search(query: str) -> tuple[str, str]: response = requests.get( url="https://ws.audioscrobbler.com/2.0/?method=track.search&format=json", params={"limit": 5, "track": query, "api_key": os.environ["LASTFM_API_KEY"]}, @@ -26,10 +25,7 @@ def _lastfm_search(query: str) -> tuple[str, str] | None: assert response.status_code == 200 - tracks = response.json()["results"]["trackmatches"]["track"] - if len(tracks) == 0: - return None - track_info = tracks[0] + track_info = response.json()["results"]["trackmatches"]["track"][0] return track_info["name"], track_info["artist"] @@ -46,48 +42,24 @@ def _lastfm_getinfo(name: str, artist: str) -> tuple[str, list[str]]: # ( image track_info = response.json()["track"] - image_id = "" - if "album" in track_info: - image_url = urllib.parse.urlparse(track_info["album"]["image"][0]["#text"]) - image_id = os.path.splitext(os.path.basename(image_url.path))[0] - else: - print("this song haas no image", flush=True) + image_url = urllib.parse.urlparse(track_info["album"]["image"][0]["#text"]) return ( # track_info["mbid"], - image_id, + os.path.splitext(os.path.basename(image_url.path))[0], [t["name"] for t in track_info["toptags"]["tag"]], ) -def _yt_search(query: str) -> tuple[str, str]: - ydl_opts = { - "format": "bestaudio", - "default_search": "ytsearch1", - "outtmpl": "%(title)s.%(ext)s", - "skip_download": True, - } +def lastfm_query_search(query: str) -> SongInfo: + name, artist = _lastfm_search(query) - with yt_dlp.YoutubeDL(ydl_opts) as ydl: - info = ydl.extract_info(query, download=False) - - first = info["entries"][0] - - return first["track"], first["artists"][0] - - -def query_search(query: str) -> SongInfo | None: - res = _lastfm_search(query) - if res is None: - return None - - name, artist = res img_id, tags = _lastfm_getinfo(name, artist) return SongInfo(artist=artist, title=name, img_id=img_id, tags=tags) -def yt_search_song(name: str, artist: str) -> str | None: # video id +def download_song_mp3(name: str, artist: str) -> tuple[str, str] | None: # ( id, audio ) ydl_opts = { "format": "bestaudio", "default_search": "ytsearch1", @@ -98,30 +70,12 @@ def yt_search_song(name: str, artist: str) -> str | None: # video id with yt_dlp.YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(f"{name!r} - {artist!r}", download=False) - if len(info["entries"]) == 0: - return None - - return info["entries"][0]["id"] - - -def yt_get_audio_url(video_id) -> str | None: # audio url - ydl_opts = { - "format": "bestaudio", - "default_search": "ytsearch1", - "outtmpl": "%(title)s.%(ext)s", - "skip_download": True, - } - - with yt_dlp.YoutubeDL(ydl_opts) as ydl: - info = ydl.extract_info(video_id, download=False) - - if "entries" not in info: - return info["url"] - first_entry = info["entries"][0] + video_id = first_entry["id"] + for fmt in first_entry["formats"]: if "acodec" in fmt and fmt["acodec"] != "none": - return fmt["url"] + return video_id, fmt["url"] return None diff --git a/backend/src/state.py b/backend/src/state.py index 73d05d2..e1f3e7c 100644 --- a/backend/src/state.py +++ b/backend/src/state.py @@ -4,7 +4,7 @@ from sqlite3 import Cursor from flask import Flask from flask_socketio import SocketIO -from room import Room +from .room import Room @dataclass diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a620e77..a545ce8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,7 +8,6 @@ "name": "frontend", "version": "0.0.1", "dependencies": { - "@lucide/svelte": "^0.536.0", "socket.io-client": "^4.8.1", "zod": "^4.0.14" }, @@ -31,6 +30,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -499,6 +499,7 @@ "version": "0.3.12", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -509,6 +510,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -518,27 +520,20 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.29", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@lucide/svelte": { - "version": "0.536.0", - "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.536.0.tgz", - "integrity": "sha512-YAeoWU+0B/RriFZZ3wHno1FMkbrVrFdityuo2B0YuphD0vtJWXStzZkWLGVhT3jMb7zhugmhayIg+gI4+AZu1g==", - "license": "ISC", - "peerDependencies": { - "svelte": "^5" - } - }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -836,6 +831,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^8.9.0" @@ -1211,12 +1207,26 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~7.8.0" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1229,6 +1239,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -1238,6 +1249,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -1273,6 +1285,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1432,12 +1445,14 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, "license": "MIT" }, "node_modules/esrap": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -1484,6 +1499,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.6" @@ -1752,12 +1768,14 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, "license": "MIT" }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -2175,6 +2193,7 @@ "version": "5.37.1", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.37.1.tgz", "integrity": "sha512-h8arWpQZ+3z8eahyBT5KkiBOUsG6xvI5Ykg0ozRr9xEdImgSMUPUlOFWRNkUsT7Ti0DSUCTEbPoped0aoxFyWA==", + "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -2296,6 +2315,15 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/vite": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz", @@ -2434,6 +2462,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "dev": true, "license": "MIT" }, "node_modules/zod": { diff --git a/frontend/package.json b/frontend/package.json index f639403..d5aa485 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,8 +28,7 @@ "vite": "^7.0.4" }, "dependencies": { - "@lucide/svelte": "^0.536.0", - "socket.io-client": "^4.8.1", - "zod": "^4.0.14" + "zod": "^4.0.14", + "socket.io-client": "^4.8.1" } } diff --git a/frontend/src/app.css b/frontend/src/app.css index cb4fb29..90d8258 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -1,4 +1,3 @@ -@import url('https://fonts.googleapis.com/css2?family=Lilita+One&display=swap'); @import 'tailwindcss'; @keyframes spin-slower { @@ -14,53 +13,3 @@ .spin-slower { 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 { - --color-dark-pine-base: hsl(249deg, 22%, 12%); - --color-dark-pine-surface: hsl(247deg, 23%, 15%); - --color-dark-pine-overlay: hsl(248deg, 25%, 18%); - --color-dark-pine-muted: hsl(249deg, 12%, 47%); - --color-dark-pine-subtle: hsl(248deg, 15%, 61%); - --color-dark-pine-text: hsl(245deg, 50%, 91%); - --color-dark-pine-red: hsl(343deg, 76%, 68%); - --color-dark-pine-yellow: hsl(35deg, 88%, 72%); - --color-dark-pine-pink: hsl(2deg, 55%, 83%); - --color-dark-pine-blue: hsl(197deg, 49%, 38%); - --color-dark-pine-green: hsl(189deg, 43%, 73%); - --color-dark-pine-purple: hsl(267deg, 57%, 78%); - --color-dark-pine-highlight-low: hsl(244deg, 18%, 15%); - --color-dark-pine-highlight-med: hsl(249deg, 15%, 28%); - --color-dark-pine-highlight-high: hsl(248deg, 13%, 36%); -} - -@theme { - --color-light-pine-base: hsl(32deg, 57%, 95%); - --color-light-pine-surface: hsl(35deg, 100%, 98%); - --color-light-pine-overlay: hsl(33deg, 43%, 91%); - --color-light-pine-muted: hsl(257deg, 9%, 61%); - --color-light-pine-subtle: hsl(248deg, 12%, 52%); - --color-light-pine-text: hsl(248deg, 19%, 40%); - --color-light-pine-red: hsl(343deg, 35%, 55%); - --color-light-pine-yellow: hsl(35deg, 81%, 56%); - --color-light-pine-pink: hsl(3deg, 53%, 67%); - --color-light-pine-blue: hsl(197deg, 53%, 34%); - --color-light-pine-green: hsl(189deg, 30%, 48%); - --color-light-pine-purple: hsl(268deg, 21%, 57%); - --color-light-pine-highlight-low: hsl(25deg, 35%, 93%); - --color-light-pine-highlight-med: hsl(10deg, 9%, 86%); - --color-light-pine-highlight-high: hsl(315deg, 4%, 80%); -} diff --git a/frontend/src/app.html b/frontend/src/app.html index 0e6ef01..7af2f6d 100644 --- a/frontend/src/app.html +++ b/frontend/src/app.html @@ -1,13 +1,15 @@ <!doctype html> <html lang="en"> - <head> - <meta charset="utf-8" /> - <link rel="icon" href="%sveltekit.assets%/favicon.ico" /> - <link rel="manifest" href="manifest.json" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - %sveltekit.head% - </head> - <body data-sveltekit-preload-data="hover" class="h-max"> - <div style="display: contents">%sveltekit.body%</div> - </body> + +<head> + <meta charset="utf-8" /> + <link rel="icon" href="%sveltekit.assets%/favicon.svg" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + %sveltekit.head% +</head> + +<body data-sveltekit-preload-data="hover" class="h-max"> + <div style="display: contents">%sveltekit.body%</div> +</body> + </html> diff --git a/frontend/src/lib/components/Error.svelte b/frontend/src/lib/components/Error.svelte index 414657e..47e6a19 100644 --- a/frontend/src/lib/components/Error.svelte +++ b/frontend/src/lib/components/Error.svelte @@ -1,10 +1,8 @@ <script lang="ts"> - import type { FetchError } from "$lib/types" - - let { returnError }: { returnError: FetchError } = $props() + let { code, message } = $props() </script> <div class="flex h-screen w-full flex-col items-center justify-center"> - <h1 class="p-2 text-xl">Error {returnError.code}</h1> - <p>{returnError.message}</p> + <h1 class="p-2 text-xl">Error {code}</h1> + <p>{message}</p> </div> diff --git a/frontend/src/lib/components/QueueSlider.svelte b/frontend/src/lib/components/QueueSlider.svelte index 5df1e8d..430a9bc 100644 --- a/frontend/src/lib/components/QueueSlider.svelte +++ b/frontend/src/lib/components/QueueSlider.svelte @@ -1,17 +1,13 @@ <script lang="ts"> import { type Song, createEmptySong } from "$lib/types" - let { queueSongs, playingIndex } = $props() + let { songs, playing } = $props() let displaySongs = $derived<Song[]>([ - playingIndex > 0 ? queueSongs[playingIndex - 1] : createEmptySong(), - queueSongs[playingIndex], - playingIndex == queueSongs.length - 1 ? createEmptySong() : queueSongs[playingIndex + 1], + playing > 0 && playing < songs.length ? songs[playing - 1] : createEmptySong(), + songs[playing], + playing == songs.length - 1 ? createEmptySong() : songs[playing + 1], ]) - - $effect(() => { - console.log(displaySongs) - }) </script> <div class="relative flex w-full justify-center overflow-hidden"> @@ -23,22 +19,20 @@ class={`flex h-[60vw] max-h-[250px] w-[60vw] max-w-[250px] items-center justify-center ${i === 1 ? "spin-slower rounded-full border-2 border-black" : "rounded"} object-cover`} > {#if i === 1} - <div class="absolute z-20 h-16 w-16 rounded-full border-2 border-black bg-light-pine-base dark:bg-dark-pine-base"></div> + <div class="absolute z-20 h-16 w-16 rounded-full border-2 border-black bg-white"></div> {/if} - <img - class={`h-full overflow-hidden ${i === 1 ? "rounded-full" : "rounded"}`} - src={song.image_id != "" - ? `https://lastfm.freetls.fastly.net/i/u/174s/${song.image_id}.png` - : "https://s2.qwant.com/thumbr/474x474/f/6/b50687db1ebb262ac78b98a8f3c56a1e62235aaeebe0346dd27d4fbf1edec8/OIP.kXN41HyriW5dLTkjm0QQoAHaHa.jpg?u=https%3A%2F%2Ftse.mm.bing.net%2Fth%2Fid%2FOIP.kXN41HyriW5dLTkjm0QQoAHaHa%3Fpid%3DApi&q=0&b=1&p=0&a=0"} - alt="Song cover" - /> + <img class={`h-full overflow-hidden ${i === 1 ? "rounded-full" : "rounded"}`} src={`https://lastfm.freetls.fastly.net/i/u/174s/${song.image_id}.png`} alt="Song cover" /> </div> {#if i === 1} - <h1 class="mt-5">{song.title} - {song.artist}</h1> + <h1 class="mt-2">{song.title} - {song.artist}</h1> {/if} </div> {:else} - <div class="flex h-[60vw] max-h-[250px] w-[60vw] max-w-[250px] items-center justify-center"></div> + <div class="flex h-[60vw] max-h-[250px] w-[60vw] max-w-[250px] items-center justify-center"> + {#if i === 1} + <p>No song in queue</p> + {/if} + </div> {/if} {/each} </div> diff --git a/frontend/src/lib/components/RoomComponent.svelte b/frontend/src/lib/components/RoomComponent.svelte deleted file mode 100644 index 0192683..0000000 --- a/frontend/src/lib/components/RoomComponent.svelte +++ /dev/null @@ -1,45 +0,0 @@ -<script lang="ts"> - import { type Room } from "$lib/types" - let { room }: { room: Room } = $props() - let showPinModal: boolean = $state(false) - let pin: number = $state() -</script> - -<div - class="flex gap-2 w-82 cursor-pointer flex-col 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" -> - <button - class="flex flex-row items-center" - onclick={() => { - if (!room.private) { - window.location.href = "/room/" + room.id - return - } - showPinModal = !showPinModal - }} - > - <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">{Math.round(room.distance)}m</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> - {#if showPinModal} - <input - placeholder="PIN (requied)" - class="p-2 text-xl rounded-md border-dark-pine-muted bg-light-pine-overlay dark:bg-dark-pine-base hover:dark:bg-light-pine-base/20 duration-100 outline-none focus:ring-2" - type="number" - bind:value={pin} - /> - <button - onclick={() => { - window.location.href = `/room/${room.id}?pin=${pin}` - }} - class="p-2 text-xl rounded-md border-dark-pine-muted bg-light-pine-overlay dark:bg-dark-pine-base hover:dark:bg-light-pine-base/20 duration-100 outline-none focus:ring-2">JOIN</button - > - {/if} -</div> diff --git a/frontend/src/lib/components/SuggestionInput.svelte b/frontend/src/lib/components/SuggestionInput.svelte index d19dd17..de8e437 100644 --- a/frontend/src/lib/components/SuggestionInput.svelte +++ b/frontend/src/lib/components/SuggestionInput.svelte @@ -1,69 +1,15 @@ <script lang="ts"> - import { LoaderCircle } from "@lucide/svelte" - - const COOLDOWN_SECS = 10 - let { roomId } = $props() - let input = $state("") - let loading: boolean = $state(false) - let cooldowned: boolean = $state(false) - let errorMsg: string = $state() - $effect(() => { - console.log("cooldowned is now", cooldowned) - }) + let input = $state("") async function sendSong() { - loading = true - const res = await fetch(`/api/addsong?room=${roomId}&query=${input}`, { method: "POST" }) - const json = await res.json() + let resp = await fetch(`/api/addsong?room=${roomId}&query=${input}`, { method: "POST" }) input = "" - loading = false - - if (!json.success) { - errorMsg = json.error - } - - cooldowned = true - setTimeout(() => { - cooldowned = false - console.log("unset cooldown") - }, COOLDOWN_SECS * 1000) } </script> -<div - class={`flex h-full w-full flex-row items-center gap-2 rounded-md border-dark-pine-muted bg-light-pine-overlay hover:bg-dark-pine-base/20 dark:bg-dark-pine-overlay hover:dark:bg-light-pine-base/20 ${loading ? "disabled" : ""}`} -> - <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) => { - errorMsg = null - if (e.key == "Enter") { - sendSong() - } - }} - disabled={loading} - /> - {#if loading} - <span class="animate-spin"> - <LoaderCircle /> - </span> - {/if} - - <button - disabled={cooldowned} - class="i-lucide-check h-[40px] w-1/4 rounded font-semibold shadow-xl duration-100 active:scale-90 {!cooldowned - ? 'cursor-pointer bg-light-pine-blue hover:scale-105 dark:bg-dark-pine-blue ' - : 'bg-light-pine-muted dark:bg-light-pine-muted'}" - onclick={sendSong}>Add</button - > - <span class="i-lucide-chevrons-left"></span> +<div class="flex h-full w-full flex-row items-center gap-2 rounded border-2 border-black p-4"> + <input type="text" placeholder="Song & Artist" class="h-full w-3/4 rounded" bind:value={input} /> + <button class="w-1/4 rounded" onclick={sendSong}>Send</button> </div> - -<p class="text-red-500 font-semibold"> - {errorMsg} -</p> diff --git a/frontend/src/lib/components/SuggestionList.svelte b/frontend/src/lib/components/SuggestionList.svelte index 6c667a3..16cc08c 100644 --- a/frontend/src/lib/components/SuggestionList.svelte +++ b/frontend/src/lib/components/SuggestionList.svelte @@ -1,62 +1,31 @@ <script lang="ts"> import type { Suggestion } from "$lib/types" - import { ThumbsUp, ThumbsDown } from "@lucide/svelte" - import { onMount } from "svelte" - let { suggestions = $bindable(), roomId }: { suggestions: Suggestion[]; roomId: string } = $props() - - let picked_suggestions: string[] = $state([]) + let { suggestions, roomId }: { suggestions: Suggestion[]; roomId: string } = $props() async function vote(amount: number, songId: string) { - if (picked_suggestions.includes(songId)) return console.log("rejecting vote") await fetch(`/api/song/voting?room=${roomId}&song=${songId}&increment=${amount}`, { method: "POST" }) - picked_suggestions.push(songId) - console.log("accepted vote") - sessionStorage.setItem("picked_suggestions", JSON.stringify(picked_suggestions)) } - - onMount(() => { - picked_suggestions = JSON.parse(sessionStorage.getItem("picked_suggestions") ?? "[]") - }) </script> <div class="flex h-full w-full flex-col items-center gap-2 overflow-y-auto"> - {#if suggestions.length == 0} - <p>No suggestions yet! Try to add a new one using the Add button</p> - {/if} - {#each suggestions as sug} - <div - class="flex h-[80px] w-full flex-row gap-2 rounded-md border-dark-pine-muted bg-light-pine-overlay p-2 shadow-md duration-100 hover:bg-dark-pine-base/20 dark:bg-dark-pine-overlay hover:dark:bg-light-pine-base/20" - > - <div class="flex w-3/4 flex-row items-center gap-2"> - <img - class="w-[60px] min-w-[60px] rounded" - alt="Song cover" - src={sug.image_id != "" - ? `https://lastfm.freetls.fastly.net/i/u/174s/${sug.image_id}.png` - : "https://s2.qwant.com/thumbr/474x474/f/6/b50687db1ebb262ac78b98a8f3c56a1e62235aaeebe0346dd27d4fbf1edec8/OIP.kXN41HyriW5dLTkjm0QQoAHaHa.jpg?u=https%3A%2F%2Ftse.mm.bing.net%2Fth%2Fid%2FOIP.kXN41HyriW5dLTkjm0QQoAHaHa%3Fpid%3DApi&q=0&b=1&p=0&a=0"} - /> - <div class="h-fit w-fit flex-col text-white"> - <b>{sug.title}</b> - <p>{sug.artist}</p> - </div> + <div class="flex h-[80px] w-full flex-row gap-2 rounded border-2 border-black p-2"> + <div class="flex w-3/4 flex-row gap-2"> + <img class="w-[60px] min-w-[60px] rounded" src={`https://lastfm.freetls.fastly.net/i/u/174s/${sug.image_id}.png`} alt="Song cover" /> + <h1>{sug.title} - {sug.artist}</h1> </div> <div class="flex w-1/4 flex-row items-center justify-center gap-2"> <button - class={!picked_suggestions.includes(sug.uuid) ? "text-light-pine-green duration-100 hover:scale-150 dark:text-dark-pine-green" : "text-light-pine-muted dark:text-dark-pine-muted"} - disabled={!!picked_suggestions.includes(sug.uuid)} - onclick={async () => { - await vote(1, sug.uuid) - }}><ThumbsUp /></button + onclick={() => { + vote(1, sug.uuid) + }}>+1</button > - <p class="font-semibold text-light-pine-text dark:text-dark-pine-text">{sug.upvote}</p> + <p>{sug.upvote}</p> <button - class={!picked_suggestions.includes(sug.uuid) ? "text-light-pine-red duration-100 hover:scale-150 dark:text-dark-pine-red" : "text-light-pine-muted dark:text-dark-pine-muted"} - disabled={!!picked_suggestions.includes(sug.uuid)} - onclick={async () => { - await vote(-1, sug.uuid) - }}><ThumbsDown /></button + onclick={() => { + vote(-1, sug.uuid) + }}>-1</button > </div> </div> diff --git a/frontend/src/lib/gps.ts b/frontend/src/lib/gps.ts deleted file mode 100644 index f07e4c1..0000000 --- a/frontend/src/lib/gps.ts +++ /dev/null @@ -1,36 +0,0 @@ -export type Coordinates = { - latitude: number - longitude: number -} - -function geolocation_to_simple_coords(coordinates: GeolocationCoordinates): Coordinates { - return { latitude: coordinates.latitude, longitude: coordinates.longitude } -} - -export function get_coords(): Promise<{ coords: Coordinates | null; error: string | null }> { - return new Promise((resolve) => { - if (!navigator.geolocation) { - resolve({ coords: null, error: "Geolocation is not supported by your browser" }) - return - } - - const error_callback = (gps_error: GeolocationPositionError) => { - console.log(gps_error) - resolve({ - coords: null, - error: `Unable to retrieve your location: (${gps_error.message})`, - }) - return - } - - const success_callback = (gps_position: GeolocationPosition) => { - resolve({ - coords: geolocation_to_simple_coords(gps_position.coords), - error: null, - }) - return - } - - navigator.geolocation.getCurrentPosition(success_callback, error_callback) - }) -} diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index 87182fc..3f10d61 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -22,7 +22,7 @@ export const createEmptySong = function (): Song { artist: "", tags: [""], image_id: "", - youtube_id: "", + youtube_id: 0, } } @@ -35,23 +35,3 @@ export const parseSuggestion = async function (sugg: any): Promise<Suggestion> { let resp = await SuggestionSchema.parseAsync(sugg) return resp } - -const RoomSchema = z.object({ - id: z.number(), - name: z.string(), - private: z.boolean(), - coords: z.object({ latitude: z.number(), longitude: z.number() }), - range: z.number().int(), - distance: z.number() -}) -export type Room = z.infer<typeof RoomSchema> - -export const parseRoom = async function (room: any): Promise<Room> { - let resp = await RoomSchema.parseAsync(room) - return resp -} - -export type FetchError = { - code: number - message: string -} diff --git a/frontend/src/lib/util.ts b/frontend/src/lib/util.ts new file mode 100644 index 0000000..df0d44e --- /dev/null +++ b/frontend/src/lib/util.ts @@ -0,0 +1 @@ +export type FetchError = { code: number, message: string } diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts deleted file mode 100644 index 470da85..0000000 --- a/frontend/src/lib/utils.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { type Coordinates } from "./gps" -import { parseSong, parseSuggestion, type FetchError, type Song, type Suggestion } from "./types" - -export const joinRoom = async function (roomId: string, coords: Coordinates, pin: string): Promise<[FetchError | null, string]> { - let res = await fetch(`/api/join?room=${roomId}&lat=${coords.latitude}&lon=${coords.longitude}&pin=${pin}`) - - if (res.status != 200) { - return [{ code: 400, message: "Cannot join the room" }, ""] - } - - return [null, "test"] -} - -export const getSuggestions = async function (roomId: string): Promise<[FetchError | null, Suggestion[]]> { - let resp = await fetch("/api/room/suggestions?room=" + roomId) - - if (resp.status != 200) { - return [{ code: 400, message: "Failed to retrieve suggestions" }, []] - } - - let json = await resp.json() - let suggestions: Suggestion[] = [] - - json["songs"].forEach(async (i: any) => { - suggestions.push(await parseSuggestion(i)) - }) - - suggestions = suggestions.sort((a, b) => { - return a.upvote - b.upvote - }) - - return [null, suggestions] -} - -export const getQueueSongs = async function (roomId: string): Promise<[FetchError | null, Song[], number]> { - let resp = await fetch("/api/queue?room=" + roomId) - if (resp.status != 200) { - return [{ code: 400, message: "Failed to load queue songs" }, [], 0] - } - - let json = await resp.json() - let songs: Song[] = [] - - json["queue"].forEach(async (i: any) => { - songs.push(await parseSong(i)) - }) - - let playingId = json["index"] - - return [null, songs, playingId] -} - -export const triggerPlayNext = async function (roomId: string): Promise<[FetchError | null, Song[], number]> { - let resp = await fetch("/api/queue/next?room=" + roomId, { method: "POST" }) - - if (resp.status != 200) { - return [{ code: 400, message: "Failed to trigger next song playback" }, [], 0] - } - - let json = await resp.json() - - let songs: Song[] = [] - - json["queue"].forEach(async (i: any) => { - songs.push(await parseSong(i)) - }) - return [null, songs, json["index"]] -} - -export const getStreamingUrl = async function (uuid: string) { - let resp = await fetch("/api/song/audio?song=" + uuid) - - let json = await resp.json() - - return json["url"] -} diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 0ab9b2b..79a3f7e 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -4,6 +4,4 @@ let { children } = $props() </script> -<div class="min-h-screen w-full bg-light-pine-base px-1 text-light-pine-text sm:px-20 md:px-40 lg:px-80 dark:bg-dark-pine-base dark:text-dark-pine-text"> - {@render children()} -</div> +{@render children()} diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index b0b35bf..3e7cfc2 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,44 +1,8 @@ <script lang="ts"> - import RoomComponent from "$lib/components/RoomComponent.svelte" - import { get_coords } from "$lib/gps" - import { parseRoom, type Room } from "$lib/types" - import { Plus } from "@lucide/svelte" - import { onMount } from "svelte" - - let rooms: Room[] = $state([]) - - onMount(async () => { - let { coords, error } = await get_coords() - if (error != null || coords == null) coords = { latitude: 46.6769043, longitude: 11.1851585 } - - let res = await fetch(`/api/room?lat=${coords.latitude}&lon=${coords.longitude}`) - let json = await res.json() - for (let room of json) rooms.push(await parseRoom(room)) - }) </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="/radar_bonus.gif" alt="radar" class="h-64 w-64" /> - <span class="animate-pulse text-sm italic">Scanning for rooms near you...</span> - - <button - onclick={() => { - window.location.href = "/room/create" - }} - 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"> - {#each rooms as room} - <RoomComponent {room} /> - {/each} +<div class="flex h-full w-full items-center justify-center p-4"> + <div class=""> + <h1>Scan your nearby rooms</h1> </div> </div> diff --git a/frontend/src/routes/admin/[id]/+page.svelte b/frontend/src/routes/admin/[id]/+page.svelte index 02f5f08..7573aa2 100644 --- a/frontend/src/routes/admin/[id]/+page.svelte +++ b/frontend/src/routes/admin/[id]/+page.svelte @@ -1,118 +1,37 @@ <script lang="ts"> import QueueSlider from "$lib/components/QueueSlider.svelte" - import { type Song } from "$lib/types" - import { onMount } from "svelte" - import type { FetchError } from "$lib/types" - import { getQueueSongs, getStreamingUrl, triggerPlayNext } from "$lib/utils.js" - import Error from "$lib/components/Error.svelte" - let { data } = $props() + let songs = $state([ + { + name: "Cisco PT - Cantarex", + image: "https://s2.qwant.com/thumbr/474x474/5/9/bcbd0c0aeb1838f6916bf452c557251d7be985a13449e49fccb567a3374d4e/OIP.pmqEiKWv47zViDGgPgbbQAHaHa.jpg?u=https%3A%2F%2Ftse.mm.bing.net%2Fth%2Fid%2FOIP.pmqEiKWv47zViDGgPgbbQAHaHa%3Fr%3D0%26pid%3DApi&q=0&b=1&p=0&a=0", + points: 0, + }, + { + name: "Io e i miei banchi - Paul Ham", + image: "https://i.prcdn.co/img?regionKey=RbtvKb5E1Cv4j1VWm2uGrw%3D%3D", + points: 0, + }, + { + name: "Ragatthi - Bersatetthi", + image: "https://s2.qwant.com/thumbr/474x474/5/9/bcbd0c0aeb1838f6916bf452c557251d7be985a13449e49fccb567a3374d4e/OIP.pmqEiKWv47zViDGgPgbbQAHaHa.jpg?u=https%3A%2F%2Ftse.mm.bing.net%2Fth%2Fid%2FOIP.pmqEiKWv47zViDGgPgbbQAHaHa%3Fr%3D0%26pid%3DApi&q=0&b=1&p=0&a=0", + points: 0, + }, + { + name: "Quarta", + image: "https://s2.qwant.com/thumbr/474x474/5/9/bcbd0c0aeb1838f6916bf452c557251d7be985a13449e49fccb567a3374d4e/OIP.pmqEiKWv47zViDGgPgbbQAHaHa.jpg?u=https%3A%2F%2Ftse.mm.bing.net%2Fth%2Fid%2FOIP.pmqEiKWv47zViDGgPgbbQAHaHa%3Fr%3D0%26pid%3DApi&q=0&b=1&p=0&a=0", + points: 0, + }, + { + name: "Quinta", + image: "https://s2.qwant.com/thumbr/474x474/5/9/bcbd0c0aeb1838f6916bf452c557251d7be985a13449e49fccb567a3374d4e/OIP.pmqEiKWv47zViDGgPgbbQAHaHa.jpg?u=https%3A%2F%2Ftse.mm.bing.net%2Fth%2Fid%2FOIP.pmqEiKWv47zViDGgPgbbQAHaHa%3Fr%3D0%26pid%3DApi&q=0&b=1&p=0&a=0", + points: 0, + }, + ]) - let queueSongs = $state<Song[]>([]) - let playingIndex = $state<number>(0) - let returnError = $state<FetchError | null>() - - let currentPlaying = $derived<Song>(queueSongs[playingIndex]) - let audioController = $state<HTMLAudioElement>() - - let playerInfo = $state({ - playing: false, - currentTime: 0, - duration: 0, - }) - - async function playOnEnd() { - let url = await getStreamingUrl(currentPlaying.uuid) - if (audioController) { - audioController.src = url - await audioController.play() - } - } - - $effect(() => { - if (audioController) { - audioController.ontimeupdate = () => { - playerInfo.currentTime = audioController?.currentTime || 0 - } - audioController.onloadedmetadata = () => { - playerInfo.duration = audioController?.duration || 0 - } - audioController.onplay = () => { - playerInfo.playing = true - } - audioController.onpause = () => { - playerInfo.playing = false - } - } - }) - - onMount(async () => { - let songs, index - ;[returnError, songs, index] = await getQueueSongs(data.roomId) - - queueSongs = songs - playingIndex = index - - if (queueSongs.length == 0) { - playNext() - } - }) - - $effect(() => { - playOnEnd() - }) - - const formatTime = (t: number) => { - const min = Math.floor(t / 60) - const sec = Math.floor(t % 60) - return `${min}:${sec.toString().padStart(2, "0")}` - } - - async function playNext() { - let songs, index - ;[returnError, songs, index] = await triggerPlayNext(data.roomId) - - if (returnError) return - - queueSongs = songs - playingIndex = index - - if (audioController) { - audioController.pause() - audioController.currentTime = 0 - } - } - - function seek(e: Event) { - const target = e.target as HTMLInputElement - const seekTime = parseFloat(target.value) - playerInfo.currentTime = seekTime - if (audioController) { - audioController.currentTime = seekTime - } - } + let playing = $state(1) </script> -{#if returnError} - <Error {returnError} /> -{:else} - <div class="flex w-full flex-col items-center justify-center p-4 lg:p-10"> - <QueueSlider {queueSongs} {playingIndex} /> - - <audio autoplay bind:this={audioController} hidden onended={playNext}></audio> - - <div class="flex w-full flex-col items-start justify-start gap-4 p-2 lg:w-[30vw]"> - <p>{formatTime(playerInfo.currentTime)} - {formatTime(playerInfo.duration)}</p> - <input type="range" min="0" max={playerInfo.duration} step="0.1" value={playerInfo.currentTime} class="w-full accent-blue-500" oninput={seek} /> - <div class="flex w-full flex-row items-center justify-center gap-6"> - <button - class="rounded-md border border-dark-pine-muted p-2 hover:scale-105 active:scale-90 dark:bg-dark-pine-blue" - onclick={() => (playerInfo.playing ? audioController?.pause() : audioController?.play())}>{playerInfo.playing ? "Pause" : "Unpause"}</button - > - <button class="rounded-md border border-dark-pine-muted p-2 hover:scale-105 active:scale-90 dark:bg-dark-pine-blue" onclick={playNext}>Next</button> - </div> - </div> - <img class="absolute right-1 bottom-1" src="/api/room/qrcode?room=1000&pin=1234" /> - <!-- @PERETTO fix here pls --> - </div> -{/if} +<div class="flex w-full flex-col items-center justify-center p-4 lg:p-10"> + <QueueSlider {songs} {playing} /> +</div> diff --git a/frontend/src/routes/admin/[id]/+page.ts b/frontend/src/routes/admin/[id]/+page.ts deleted file mode 100644 index 216a08b..0000000 --- a/frontend/src/routes/admin/[id]/+page.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { error } from "@sveltejs/kit" -import type { PageLoad } from "../../room/[id]/$types" - -export const load: PageLoad = function ({ params }) { - if (params.id) { - return { - roomId: params.id, - } - } - error(400, "Please provide a room id") -} diff --git a/frontend/src/routes/room/[id]/+page.svelte b/frontend/src/routes/room/[id]/+page.svelte index 84d8d67..eb225cd 100644 --- a/frontend/src/routes/room/[id]/+page.svelte +++ b/frontend/src/routes/room/[id]/+page.svelte @@ -2,87 +2,78 @@ import QueueSlider from "$lib/components/QueueSlider.svelte" import SuggestionInput from "$lib/components/SuggestionInput.svelte" import Error from "$lib/components/Error.svelte" - import { type Suggestion, type Song, parseSong, parseSuggestion } from "$lib/types.js" - import { onDestroy, onMount } from "svelte" + import { parseSong, parseSuggestion, type Suggestion, type Song } from "$lib/types.js" + import { onMount } from "svelte" import SuggestionList from "$lib/components/SuggestionList.svelte" - import { getQueueSongs, getSuggestions, joinRoom } from "$lib/utils.js" - import type { FetchError } from "$lib/types.js" - import { io, Socket } from "socket.io-client" - import { get_coords } from "$lib/gps.js" + import { io } from "socket.io-client" let { data } = $props() - let queueSongs = $state<Song[]>([]) - let playingIndex = $state(0) + let songs = $state<Song[]>([]) + let playing = $state(0) let suggestions = $state<Suggestion[]>([]) - let returnError = $state<FetchError | null>() - - let socket: Socket + let error = $state({ code: 0, message: "" }) onMount(async () => { // Join the room + let resp = await fetch("/api/join?room=" + data.roomId) - let { coords, error } = await get_coords() - if (error || coords == null) { - // Default to Lido - coords = { latitude: 46.6769043, longitude: 11.1851585 } - } - - let sugg, queue, index - ;[returnError] = await joinRoom(data.roomId, coords, data.pin) - if (returnError) { + if (resp.status != 200) { + error = { code: 400, message: "Failed to join the room. Maybe wrong code or location?" } return } - ;[returnError, sugg] = await getSuggestions(data.roomId) - if (returnError) return - ;[returnError, queue, index] = await getQueueSongs(data.roomId) - if (returnError) { - return - } - - queueSongs = queue - suggestions = sugg - playingIndex = index - // Setup websocket connection - socket = io("/", { path: "/ws", transports: ["websocket"] }) + let socket = io("/", { path: "/ws" }) await socket.emitWithAck("join_room", { id: data.roomId }) - socket.on("queue_update", async (d) => { - const songs = await Promise.all(d.queue.map(parseSong)) - queueSongs = songs - playingIndex = d.index + // Get room suggestions + resp = await fetch("/api/room/suggestions?room=" + data.roomId) + + if (resp.status != 200) { + error = { code: 500, message: "Failed to retrive suggestions" } + return + } + + let json = await resp.json() + + json["songs"].forEach(async (i: any) => { + suggestions.push(await parseSuggestion(i)) }) - socket.on("new_vote", async (d) => { - const updated = await parseSuggestion(d.song) - suggestions = suggestions.map((s) => (s.uuid === updated.uuid ? updated : s)) + suggestions = suggestions.sort((a, b) => { + return a.upvote - b.upvote }) - socket.on("new_song", async (d) => { - const song = await parseSuggestion(d.song) - suggestions = [...suggestions, song] + // Get the room queue + + resp = await fetch("/api/queue?room=" + data.roomId) + if (resp.status != 200) { + error = { code: 404, message: "Room not found" } + return + } + json = await resp.json() + + json["queue"].forEach(async (i: any) => { + songs.push(await parseSong(i)) }) - }) - onDestroy(() => { - if (socket) socket.disconnect() + console.log(songs) }) </script> <!-- Check if the room exists --> -{#if returnError} - <Error {returnError} /> +{#if error.code != 0} + <Error code={error.code} message={error.message} /> {:else} - <div class="flex w-full flex-col items-center justify-center px-2 py-4 lg:p-10"> - <QueueSlider {queueSongs} {playingIndex} /> + <div class="flex w-full flex-col items-center justify-center p-4 lg:p-10"> + <QueueSlider {songs} {playing} /> <div class="w-full py-6 lg:w-[30vw]"> <SuggestionInput roomId={data.roomId} /> </div> <div class="w-full py-6 lg:w-[30vw]"> - <SuggestionList bind:suggestions roomId={data.roomId} /> + <SuggestionList {suggestions} roomId={data.roomId} /> </div> </div> {/if} diff --git a/frontend/src/routes/room/[id]/+page.ts b/frontend/src/routes/room/[id]/+page.ts index f3bea98..969ffc5 100644 --- a/frontend/src/routes/room/[id]/+page.ts +++ b/frontend/src/routes/room/[id]/+page.ts @@ -1,8 +1,13 @@ +import { error, type Load, type ServerLoad } from "@sveltejs/kit" import type { PageLoad } from "./$types" +import type { FetchError } from "$lib/util" +import { parseSuggestion, type Suggestion } from "$lib/types" -export const load: PageLoad = ({ params, url }) => { - return { - roomId: params.id || "", - pin: url.searchParams.get("pin") || "", +export const load: PageLoad = async ({ params }) => { + if (params.id) { + return { + roomId: params.id, + } } + error(400, "Please provide a room id") } diff --git a/frontend/src/routes/room/create/+page.svelte b/frontend/src/routes/room/create/+page.svelte deleted file mode 100644 index d0009f3..0000000 --- a/frontend/src/routes/room/create/+page.svelte +++ /dev/null @@ -1,83 +0,0 @@ -<script lang="ts"> - import { get_coords } from "$lib/gps" - import { X, Check, Plus } from "@lucide/svelte" - import { onMount } from "svelte" - - let privateRoom: boolean = $state(true) - let coord = $state({ latitude: 0, longitude: 0 }) - let creating: boolean = $state(false) - - let name: string = $state() - let range: number = $state() - let pin: number = $state() - - async function createRoom() { - if (creating) { - return - } - - if (!name) { - return - } - - creating = true - - const res = await fetch( - `/api/room/new?name=${encodeURIComponent(name)}&coords=${coord.latitude},${coord.longitude}&range=${encodeURIComponent(range ?? "100")}&pin=${encodeURIComponent(pin ?? "")}`, - { method: "POST" } - ) - - const json = await res.json() - - window.location.href = `/admin/${json.room_id}` - } - - onMount(async () => { - const res = await get_coords() - coord = res.coords ?? { latitude: 46.6769043, longitude: 11.1851585 } - }) -</script> - -<form class="flex flex-col items-center"> - <h1 class="text-4xl my-10">Create Room</h1> - - <div class="flex flex-col gap-3 w-1/2"> - <input - bind:value={name} - placeholder="Room name (Required)" - class="{name - ? '' - : 'border-2 border-red-500'} p-2 text-xl rounded-md bg-light-pine-overlay hover:bg-dark-pine-base/20 dark:bg-dark-pine-overlay hover:dark:bg-light-pine-base/20 duration-100 outline-none focus:ring-2" - /> - - <input - bind:value={range} - type="number" - min="10" - placeholder="Range (in meters)" - class="p-2 text-xl rounded-md border-dark-pine-muted bg-light-pine-overlay hover:bg-dark-pine-base/20 dark:bg-dark-pine-overlay hover:dark:bg-light-pine-base/20 duration-100 outline-none focus:ring-2" - /> - - <p> - Room Coordinates: - <span>{coord.latitude},{coord.longitude}</span> - </p> - - <input - bind:value={pin} - type="number" - max="9999" - placeholder="PIN (none if public)" - class="p-2 text-xl rounded-md border-dark-pine-muted bg-light-pine-overlay hover:bg-dark-pine-base/20 dark:bg-dark-pine-overlay hover:dark:bg-light-pine-base/20 duration-100 outline-none focus:ring-2" - /> - - <button - type="button" - class="cursor-pointer flex flex-row items-center justify-center gap-3 border border-dark-pine-muted bg-light-pine-overlay hover:bg-dark-pine-base/20 dark:bg-dark-pine-overlay hover:dark:bg-light-pine-base/20 duration-100 rounded-lg h-10 font-bold" - onclick={createRoom} - > - CREA - <Plus /> - </button> - </div> -</form> diff --git a/frontend/src/routes/zesty/gps/+page.svelte b/frontend/src/routes/zesty/gps/+page.svelte deleted file mode 100644 index ab9eacd..0000000 --- a/frontend/src/routes/zesty/gps/+page.svelte +++ /dev/null @@ -1,17 +0,0 @@ -<script lang="ts"> - import { onMount } from "svelte" - import { get_coords, type Coordinates } from "$lib/gps" - - let lido_schenna_coords: Coordinates = { latitude: 46.6769043, longitude: 11.1851585 } - - onMount(async () => { - let { coords, error } = await get_coords() - console.log(coords) - if (error != null) { - return console.log(error) - } - if (coords == null) return - - // console.log(is_within_range(coords, lido_schenna_coords, 103)) - }) -</script> diff --git a/frontend/src/routes/zesty/icons/+page.svelte b/frontend/src/routes/zesty/icons/+page.svelte deleted file mode 100644 index 7307ebe..0000000 --- a/frontend/src/routes/zesty/icons/+page.svelte +++ /dev/null @@ -1,7 +0,0 @@ -<script lang="ts"> - import { Skull, Accessibility, Anvil } from "@lucide/svelte" -</script> - -<Skull /> -<Accessibility /> -<Anvil /> diff --git a/frontend/static/CHILLBOX.svg b/frontend/static/CHILLBOX.svg deleted file mode 100644 index 8133a23..0000000 --- a/frontend/static/CHILLBOX.svg +++ /dev/null @@ -1,3 +0,0 @@ -<svg width="374" height="70" viewBox="0 0 374 70" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M30.0373 0.935995C34.7093 0.935995 38.8053 1.608 42.3253 2.952C45.9093 4.232 47.7013 5.992 47.7013 8.232C47.7013 10.472 46.8373 13.256 45.1093 16.584C43.4453 19.848 42.0373 21.48 40.8853 21.48C40.5653 21.48 40.1813 21.32 39.7333 21C39.3493 20.616 38.3893 20.232 36.8533 19.848C35.3813 19.4 34.1013 19.176 33.0133 19.176C29.3013 19.176 26.2933 20.488 23.9893 23.112C21.7493 25.736 20.6293 29.768 20.6293 35.208C20.6293 40.584 21.7813 44.584 24.0853 47.208C26.4533 49.832 29.4293 51.144 33.0133 51.144C34.4853 51.144 36.1173 50.952 37.9093 50.568C39.7653 50.12 41.0773 49.512 41.8453 48.744C41.9733 48.616 42.2293 48.552 42.6133 48.552C42.9973 48.552 43.7013 49.288 44.7253 50.76C45.8133 52.168 46.8053 54.056 47.7013 56.424C48.5973 58.728 49.0453 60.52 49.0453 61.8C49.0453 64.296 47.1253 66.28 43.2853 67.752C39.4453 69.224 34.9013 69.96 29.6533 69.96C21.2053 69.96 14.2613 66.984 8.82125 61.032C3.38125 55.08 0.66125 46.696 0.66125 35.88C0.66125 25 3.44525 16.456 9.01325 10.248C14.6453 4.04 21.6533 0.935995 30.0373 0.935995ZM99.1138 67.64C99.1138 68.5467 96.4204 69 91.0338 69C85.6471 69 82.9538 68.5467 82.9538 67.64V49.24H71.7538V67.64C71.7538 68.5467 69.0604 69 63.6738 69C58.2871 69 55.5938 68.5467 55.5938 67.64V14.6C55.5938 13.48 58.2871 12.92 63.6738 12.92C69.0604 12.92 71.7538 13.48 71.7538 14.6V33.4H82.9538V14.76C82.9538 14.0133 84.2871 13.48 86.9538 13.16C88.3404 13.0533 89.7004 13 91.0338 13L95.1138 13.16C97.7804 13.48 99.1138 14.0133 99.1138 14.76V67.64ZM112.358 55.24V26.76H106.678C105.771 26.76 105.131 25.6133 104.758 23.32C104.598 22.2 104.518 21.0533 104.518 19.88C104.518 18.7067 104.598 17.56 104.758 16.44C105.131 14.1467 105.771 13 106.678 13H133.558C134.465 13 135.078 14.1467 135.398 16.44C135.611 17.56 135.718 18.7067 135.718 19.88C135.718 21.0533 135.611 22.2 135.398 23.32C135.078 25.6133 134.465 26.76 133.558 26.76H128.198V55.24H133.798C134.705 55.24 135.345 56.3867 135.718 58.68C135.931 59.8 136.038 60.9467 136.038 62.12C136.038 63.2933 135.931 64.44 135.718 65.56C135.345 67.8533 134.705 69 133.798 69H106.838C105.931 69 105.318 67.8533 104.998 65.56C104.785 64.44 104.678 63.2933 104.678 62.12C104.678 60.9467 104.785 59.8 104.998 58.68C105.318 56.3867 105.931 55.24 106.838 55.24H112.358ZM140.575 64.28V14.52C140.575 13.4 143.268 12.84 148.655 12.84C154.095 12.84 156.815 13.4 156.815 14.52V53.72H169.135C170.095 53.72 170.762 54.9733 171.135 57.48C171.295 58.7067 171.375 59.96 171.375 61.24C171.375 62.52 171.295 63.8267 171.135 65.16C170.708 67.72 170.015 69 169.055 69H144.415C143.242 69 142.308 68.52 141.615 67.56C140.922 66.6 140.575 65.5067 140.575 64.28ZM175.341 64.28V14.52C175.341 13.4 178.034 12.84 183.421 12.84C188.861 12.84 191.581 13.4 191.581 14.52V53.72H203.901C204.861 53.72 205.527 54.9733 205.901 57.48C206.061 58.7067 206.141 59.96 206.141 61.24C206.141 62.52 206.061 63.8267 205.901 65.16C205.474 67.72 204.781 69 203.821 69H179.181C178.007 69 177.074 68.52 176.381 67.56C175.687 66.6 175.341 65.5067 175.341 64.28ZM258.842 19.176C258.842 25.832 255.962 30.888 250.202 34.344C253.338 35.496 255.962 37.48 258.074 40.296C260.186 43.112 261.242 47.112 261.242 52.296C261.242 57.416 259.322 61.48 255.482 64.488C251.706 67.496 246.266 69 239.162 69H216.506C215.418 69 214.522 68.648 213.818 67.944C213.114 67.176 212.762 66.184 212.762 64.968V3.528C212.762 2.824 212.858 2.376 213.05 2.184C213.306 1.928 213.786 1.8 214.49 1.8H236.378C251.354 1.8 258.842 7.592 258.842 19.176ZM231.194 17.352V29.928H231.674C237.818 29.928 240.89 27.88 240.89 23.784C240.89 21.544 240.282 19.912 239.066 18.888C237.914 17.864 235.962 17.352 233.21 17.352H231.194ZM231.194 41.256V53.736H232.922C235.674 53.736 237.69 53.224 238.97 52.2C240.25 51.176 240.89 49.544 240.89 47.304C240.89 45.064 240.282 43.496 239.066 42.6C237.914 41.704 235.962 41.256 233.21 41.256H231.194ZM291.666 69.8C284.2 69.8 278.093 67.3467 273.346 62.44C268.653 57.5333 266.306 50.3867 266.306 41C266.306 31.56 268.68 24.4133 273.426 19.56C278.226 14.7067 284.386 12.28 291.906 12.28C299.48 12.28 305.586 14.68 310.226 19.48C314.866 24.2267 317.186 31.4533 317.186 41.16C317.186 50.8133 314.813 58.0133 310.066 62.76C305.32 67.4533 299.186 69.8 291.666 69.8ZM291.746 27.64C289.186 27.64 287.026 28.8133 285.266 31.16C283.56 33.5067 282.706 36.8133 282.706 41.08C282.706 45.2933 283.533 48.5467 285.186 50.84C286.84 53.08 289 54.2 291.666 54.2C294.386 54.2 296.573 53.0533 298.226 50.76C299.933 48.4667 300.786 45.1867 300.786 40.92C300.786 36.6533 299.906 33.3733 298.146 31.08C296.44 28.7867 294.306 27.64 291.746 27.64ZM353.289 15.16C353.929 13.56 357.263 12.76 363.289 12.76C364.836 12.76 366.729 12.8933 368.969 13.16C371.263 13.3733 372.409 13.5867 372.409 13.8L358.809 41.48L372.889 67.96C373.049 68.2267 371.903 68.4933 369.449 68.76C366.996 68.9733 364.916 69.08 363.209 69.08C356.436 69.08 352.729 68.1733 352.089 66.36L346.089 51.08L340.649 66.84C340.116 68.3333 336.703 69.08 330.409 69.08C328.863 69.08 326.943 68.9733 324.649 68.76C322.356 68.5467 321.316 68.2533 321.529 67.88L334.009 40.2L321.049 13.8C320.889 13.5333 321.983 13.2933 324.329 13.08C326.729 12.8133 328.756 12.68 330.409 12.68C337.129 12.68 340.809 13.64 341.449 15.56L346.969 29L353.289 15.16Z" fill="white"/> -</svg> diff --git a/frontend/static/android-chrome-192x192.png b/frontend/static/android-chrome-192x192.png deleted file mode 100644 index ddc7955..0000000 Binary files a/frontend/static/android-chrome-192x192.png and /dev/null differ diff --git a/frontend/static/android-chrome-512x512.png b/frontend/static/android-chrome-512x512.png deleted file mode 100644 index 71ec4aa..0000000 Binary files a/frontend/static/android-chrome-512x512.png and /dev/null differ diff --git a/frontend/static/apple-touch-icon.png b/frontend/static/apple-touch-icon.png deleted file mode 100644 index a60445a..0000000 Binary files a/frontend/static/apple-touch-icon.png and /dev/null differ diff --git a/frontend/static/favicon-16x16.png b/frontend/static/favicon-16x16.png deleted file mode 100644 index 6bb39f3..0000000 Binary files a/frontend/static/favicon-16x16.png and /dev/null differ diff --git a/frontend/static/favicon-32x32.png b/frontend/static/favicon-32x32.png deleted file mode 100644 index 7d938c2..0000000 Binary files a/frontend/static/favicon-32x32.png and /dev/null differ diff --git a/frontend/static/favicon.ico b/frontend/static/favicon.ico deleted file mode 100644 index f5208bb..0000000 Binary files a/frontend/static/favicon.ico and /dev/null differ diff --git a/frontend/static/favicon.svg b/frontend/static/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/frontend/static/favicon.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo \ No newline at end of file diff --git a/frontend/static/icon_small.png b/frontend/static/icon_small.png deleted file mode 100644 index f518ac7..0000000 Binary files a/frontend/static/icon_small.png and /dev/null differ diff --git a/frontend/static/manifest.json b/frontend/static/manifest.json deleted file mode 100644 index 8a414d3..0000000 --- a/frontend/static/manifest.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "Chillbox Music Player", - "short_name": "Chillbox", - "start_url": "/", - "display": "standalone", - "background_color": "#334155", - "theme_color": "#334155", - "orientation": "portrait-primary", - "icons": [ - { - "src": "/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - }, - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - } - ] -} diff --git a/frontend/static/radar.gif b/frontend/static/radar.gif deleted file mode 100644 index eacb878..0000000 Binary files a/frontend/static/radar.gif and /dev/null differ diff --git a/frontend/static/radar_bonus.gif b/frontend/static/radar_bonus.gif deleted file mode 100644 index 972945e..0000000 Binary files a/frontend/static/radar_bonus.gif and /dev/null differ diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 09f1019..3548199 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,22 +1,21 @@ -import tailwindcss from "@tailwindcss/vite" -import { sveltekit } from "@sveltejs/kit/vite" -import { defineConfig } from "vite" +import tailwindcss from '@tailwindcss/vite'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; export default defineConfig({ plugins: [tailwindcss(), sveltekit()], server: { proxy: { - "/api": { + '/api': { target: "http://backend:5000", changeOrigin: false, - secure: false, + secure: false }, - "/ws": { + '/ws': { target: "http://backend:5000", changeOrigin: false, - secure: false, - ws: true, - }, - }, - }, -}) + secure: false + } + } + } +});