From 5a5d74dfd1689582b9c717cb7165e082f67f57cb Mon Sep 17 00:00:00 2001 From: Sergi Date: Thu, 14 Nov 2024 10:09:16 -0800 Subject: [PATCH 1/7] setlist mode kinda --- visualizer/package-lock.json | 6 + visualizer/package.json | 1 + visualizer/src/App.svelte | 4 +- .../src/components/AddSongDialog.svelte | 2 +- .../src/components/SetlistItemCard.svelte | 92 +++++++++++++++ visualizer/src/components/SongCard.svelte | 62 ++++++++++ .../src/lib/components/ui/switch/index.ts | 7 ++ .../lib/components/ui/switch/switch.svelte | 28 +++++ .../src/lib/components/ui/textarea/index.ts | 28 +++++ .../components/ui/textarea/textarea.svelte | 38 +++++++ visualizer/src/routes.ts | 5 + visualizer/src/routes/Dashboard.svelte | 65 ++--------- .../src/routes/SetlistMode/SetlistMode.svelte | 107 ++++++++++++++++++ visualizer/src/types/SetlistItem.ts | 6 + 14 files changed, 391 insertions(+), 60 deletions(-) create mode 100644 visualizer/src/components/SetlistItemCard.svelte create mode 100644 visualizer/src/components/SongCard.svelte create mode 100644 visualizer/src/lib/components/ui/switch/index.ts create mode 100644 visualizer/src/lib/components/ui/switch/switch.svelte create mode 100644 visualizer/src/lib/components/ui/textarea/index.ts create mode 100644 visualizer/src/lib/components/ui/textarea/textarea.svelte create mode 100644 visualizer/src/routes/SetlistMode/SetlistMode.svelte create mode 100644 visualizer/src/types/SetlistItem.ts diff --git a/visualizer/package-lock.json b/visualizer/package-lock.json index 550e91c..1d42039 100644 --- a/visualizer/package-lock.json +++ b/visualizer/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@coderline/alphatab": "^1.3.1", "@tanstack/svelte-query": "^5.59.20", + "@thisux/sveltednd": "^0.0.16", "axios": "^1.7.7", "daisyui": "^4.12.14", "shadcn-svelte": "^0.14.0", @@ -898,6 +899,11 @@ "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0" } }, + "node_modules/@thisux/sveltednd": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@thisux/sveltednd/-/sveltednd-0.0.16.tgz", + "integrity": "sha512-LpJNhPSsrqYvZd5qtPjIipFZx1TlPO4oxWK5R6u4v7YpIKJzrHAPIT0XJRVLmwMkLPivBbh1BEo60Xn2czre8w==" + }, "node_modules/@tsconfig/svelte": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.4.tgz", diff --git a/visualizer/package.json b/visualizer/package.json index 6e25d09..1a29edb 100644 --- a/visualizer/package.json +++ b/visualizer/package.json @@ -29,6 +29,7 @@ "dependencies": { "@coderline/alphatab": "^1.3.1", "@tanstack/svelte-query": "^5.59.20", + "@thisux/sveltednd": "^0.0.16", "axios": "^1.7.7", "daisyui": "^4.12.14", "shadcn-svelte": "^0.14.0", diff --git a/visualizer/src/App.svelte b/visualizer/src/App.svelte index 6672283..0c45716 100644 --- a/visualizer/src/App.svelte +++ b/visualizer/src/App.svelte @@ -40,10 +40,10 @@ Visualizer - Necrophagist - Extreme Unction [Live At Party.San 2005] 4K + Setlists {/if} diff --git a/visualizer/src/components/AddSongDialog.svelte b/visualizer/src/components/AddSongDialog.svelte index ef860ee..90bb5f0 100644 --- a/visualizer/src/components/AddSongDialog.svelte +++ b/visualizer/src/components/AddSongDialog.svelte @@ -67,7 +67,7 @@ + + + + {#if setlistItem.type === "song"} +
+
+ + +
+
+ {/if} + + diff --git a/visualizer/src/routes.ts b/visualizer/src/routes.ts index 90c9fff..dd039df 100644 --- a/visualizer/src/routes.ts +++ b/visualizer/src/routes.ts @@ -10,6 +10,7 @@ import CompareTabs from "./routes/CompareTabs.svelte"; import Login from "./routes/Login.svelte"; import Visualizer from "./routes/Visualizer.svelte"; import VisualizerLegacy from "./routes/VisualizerLegacy.svelte"; +import SetlistMode from "./routes/SetlistMode/SetlistMode.svelte"; function authGuard() { return get(isAuthenticated); @@ -38,6 +39,10 @@ const routes: RouteDefinition = { component: VisualizerLegacy as typeof SvelteComponent, conditions: [authGuard], }), + "/setlists": wrap({ + component: SetlistMode as typeof SvelteComponent, + conditions: [authGuard], + }), }; export default routes; diff --git a/visualizer/src/routes/Dashboard.svelte b/visualizer/src/routes/Dashboard.svelte index f48d686..374ee5b 100644 --- a/visualizer/src/routes/Dashboard.svelte +++ b/visualizer/src/routes/Dashboard.svelte @@ -21,6 +21,8 @@ import AddSongDialog from "../components/AddSongDialog.svelte"; import { formatDate } from "$lib/utils"; import DeleteSongDialog from "../components/DeleteSongDialog.svelte"; + import { Switch } from "$lib/components/ui/switch"; + import SongCard from "../components/SongCard.svelte"; const songsQuery = useFetchSongs(); const searchTerm = writable(""); @@ -71,6 +73,11 @@ bind:value={$searchTerm} class="max-w-sm" /> +
+ +

Setlist Mode

+
+ @@ -84,63 +91,7 @@ {:else}
{#each $filteredSongs as song (song.id)} - - -
-
- -

{song.title}

-
- - - - - - Actions - handleAction("open", song)} - > - - Open Tab - - handleAction("suggest", song)} - > - - - Suggest Changes - - handleAction("export", song)} - > - Export as PDF - - - - confirmDelete(song)} - class="text-red-600" - > - Delete Song - - - -
-
- - Last modified: {formatDate(song.tab.uploaded_at)} -
-
-
+ {/each}
{/if} diff --git a/visualizer/src/routes/SetlistMode/SetlistMode.svelte b/visualizer/src/routes/SetlistMode/SetlistMode.svelte new file mode 100644 index 0000000..161bd92 --- /dev/null +++ b/visualizer/src/routes/SetlistMode/SetlistMode.svelte @@ -0,0 +1,107 @@ + + +
+

Setlist Mode

+ + +
+ + + + +
+ + +
+ {#each $setlist as setlistItem (setlistItem.id)} +
+ +
+ {/each} +
+
diff --git a/visualizer/src/types/SetlistItem.ts b/visualizer/src/types/SetlistItem.ts new file mode 100644 index 0000000..de33f96 --- /dev/null +++ b/visualizer/src/types/SetlistItem.ts @@ -0,0 +1,6 @@ +export type SetlistItem = { + id: string; + type: "song" | "sample" | "break" | "speech"; + title: string; + notes?: string; +}; From 9492ee555fe461a0d969ca5166b4de98bbf2b3b0 Mon Sep 17 00:00:00 2001 From: Sergi Date: Thu, 14 Nov 2024 15:01:52 -0800 Subject: [PATCH 2/7] setlist mode --- diffing/models.py | 29 ++++- diffing/routers/setlists.py | 81 +++++++++++++ visualizer/src/App.svelte | 77 +++---------- .../src/components/AddSongDialog.svelte | 2 +- .../src/components/SetlistItemCard.svelte | 92 --------------- visualizer/src/components/SongCard.svelte | 1 + .../src/components/layout/LeftSidebar.svelte | 45 ++++++++ .../src/components/layout/Topbar.svelte | 20 ++++ .../setlist-item/GenericItem.svelte | 74 ++++++++++++ .../components/setlist-item/SongItem.svelte | 67 +++++++++++ .../src/lib/components/ui/select/index.ts | 34 ++++++ .../ui/select/select-content.svelte | 39 +++++++ .../components/ui/select/select-item.svelte | 40 +++++++ .../components/ui/select/select-label.svelte | 16 +++ .../ui/select/select-separator.svelte | 11 ++ .../ui/select/select-trigger.svelte | 27 +++++ .../src/lib/components/ui/tooltip/index.ts | 15 +++ .../ui/tooltip/tooltip-content.svelte | 28 +++++ visualizer/src/pages/Rider.svelte | 1 + .../src/pages/SetlistMode/SetlistMode.svelte | 109 ++++++++++++++++++ .../Dashboard.svelte => pages/Songs.svelte} | 27 +---- visualizer/src/routes.ts | 35 ++++-- visualizer/src/routes/Login.svelte | 2 +- .../src/routes/SetlistMode/SetlistMode.svelte | 107 ----------------- visualizer/src/types/SetlistItem.ts | 19 ++- 25 files changed, 694 insertions(+), 304 deletions(-) create mode 100644 diffing/routers/setlists.py delete mode 100644 visualizer/src/components/SetlistItemCard.svelte create mode 100644 visualizer/src/components/layout/LeftSidebar.svelte create mode 100644 visualizer/src/components/layout/Topbar.svelte create mode 100644 visualizer/src/components/setlist-item/GenericItem.svelte create mode 100644 visualizer/src/components/setlist-item/SongItem.svelte create mode 100644 visualizer/src/lib/components/ui/select/index.ts create mode 100644 visualizer/src/lib/components/ui/select/select-content.svelte create mode 100644 visualizer/src/lib/components/ui/select/select-item.svelte create mode 100644 visualizer/src/lib/components/ui/select/select-label.svelte create mode 100644 visualizer/src/lib/components/ui/select/select-separator.svelte create mode 100644 visualizer/src/lib/components/ui/select/select-trigger.svelte create mode 100644 visualizer/src/lib/components/ui/tooltip/index.ts create mode 100644 visualizer/src/lib/components/ui/tooltip/tooltip-content.svelte create mode 100644 visualizer/src/pages/Rider.svelte create mode 100644 visualizer/src/pages/SetlistMode/SetlistMode.svelte rename visualizer/src/{routes/Dashboard.svelte => pages/Songs.svelte} (74%) delete mode 100644 visualizer/src/routes/SetlistMode/SetlistMode.svelte diff --git a/diffing/models.py b/diffing/models.py index a151d77..b1af967 100644 --- a/diffing/models.py +++ b/diffing/models.py @@ -1,10 +1,10 @@ from pydantic import BaseModel -from sqlalchemy import Column, Integer, String, DateTime, ForeignKey +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Enum, Text from sqlalchemy.orm import relationship from datetime import datetime from diffing.database import Base from typing import Optional - +import enum # SQLAlchemy Models class SongMetadata(Base): __tablename__ = "song_metadata" @@ -13,6 +13,7 @@ class SongMetadata(Base): band_id = Column(Integer, ForeignKey("bands.id"), nullable=False) tab = relationship("TabMetadata", uselist=False, back_populates="song", cascade="all, delete") band = relationship("Band", back_populates="songs") + setlist_items = relationship("SetlistItem", back_populates="song") class TabMetadata(Base): __tablename__ = "tab_metadata" @@ -73,4 +74,26 @@ class TokenData(BaseModel): band_id: int | None = None class LoginRequest(BaseModel): - access_code: str \ No newline at end of file + access_code: str + +class SetlistItemType(str, enum.Enum): + SONG = "song" + SAMPLE = "sample" + BREAK = "break" + SPEECH = "speech" + +class SetlistItem(Base): + __tablename__ = "setlist_items" + + id = Column(Integer, primary_key=True, index=True) + type = Column(Enum(SetlistItemType), nullable=False) + title = Column(String, nullable=True) # Nullable for songs + notes = Column(Text, nullable=True) + song_id = Column(Integer, ForeignKey("song_metadata.id"), nullable=True) + + # Relationship to SongMetadata for song items + song = relationship("SongMetadata", back_populates="setlist_items") + + def __repr__(self): + return f"" + diff --git a/diffing/routers/setlists.py b/diffing/routers/setlists.py new file mode 100644 index 0000000..1689821 --- /dev/null +++ b/diffing/routers/setlists.py @@ -0,0 +1,81 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from typing import List + +from diffing.models import SetlistItem, SetlistItemCreate, SetlistItemType, SetlistItem as SetlistItemSchema +from diffing.database import get_db + +router = APIRouter(prefix="/setlist_items", tags=["setlist_items"]) + +# Get a specific setlist item by ID +@router.get("/{item_id}", response_model=SetlistItemSchema) +async def get_setlist_item(item_id: int, db: Session = Depends(get_db)): + item = db.query(SetlistItem).filter(SetlistItem.id == item_id).first() + if item is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Setlist item not found" + ) + return item + +# Get all setlist items (optionally filtered by type) +@router.get("/", response_model=List[SetlistItemSchema]) +async def list_setlist_items(type: SetlistItemType = None, db: Session = Depends(get_db)): + query = db.query(SetlistItem) + if type: + query = query.filter(SetlistItem.type == type) + items = query.all() + return items + +# Create a new setlist item +@router.post("/", response_model=SetlistItemSchema, status_code=status.HTTP_201_CREATED) +async def create_setlist_item(setlist_item: SetlistItemCreate, db: Session = Depends(get_db)): + # Additional validation based on type + if setlist_item.type == SetlistItemType.SONG and not setlist_item.song_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="song_id is required for type 'song'" + ) + if setlist_item.type != SetlistItemType.SONG and not setlist_item.title: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="title is required for non-song items" + ) + + # Create and save the setlist item + new_item = SetlistItem(**setlist_item.dict()) + db.add(new_item) + db.commit() + db.refresh(new_item) + return new_item + +# Update an existing setlist item +@router.put("/{item_id}", response_model=SetlistItemSchema) +async def update_setlist_item(item_id: int, updates: SetlistItemCreate, db: Session = Depends(get_db)): + item = db.query(SetlistItem).filter(SetlistItem.id == item_id).first() + if item is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Setlist item not found" + ) + + # Update fields based on the provided data + if updates.type == SetlistItemType.SONG: + item.song_id = updates.song_id or item.song_id + else: + item.title = updates.title or item.title + + item.notes = updates.notes if updates.notes is not None else item.notes + db.commit() + db.refresh(item) + return item + +@router.get("/first", response_model=SetlistItemSchema) +async def get_first_setlist_item(db: Session = Depends(get_db)): + item = db.query(SetlistItem).first() + if item is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="No setlist items found" + ) + return item \ No newline at end of file diff --git a/visualizer/src/App.svelte b/visualizer/src/App.svelte index 0c45716..fcd971b 100644 --- a/visualizer/src/App.svelte +++ b/visualizer/src/App.svelte @@ -1,73 +1,32 @@
- {#if $location !== "/login"} - + {/if} -
- - - -
+
+ {#if $location !== "/login"} + push(page)} + /> + {/if} + +
+ + + +
+
diff --git a/visualizer/src/components/setlist-item/SongItem.svelte b/visualizer/src/components/setlist-item/SongItem.svelte index 3df3287..94c082c 100644 --- a/visualizer/src/components/setlist-item/SongItem.svelte +++ b/visualizer/src/components/setlist-item/SongItem.svelte @@ -8,46 +8,74 @@ } from "$lib/components/ui/card"; import { Textarea } from "$lib/components/ui/textarea"; import { GripVertical, Music, Trash2 } from "lucide-svelte"; - import { draggable } from "@thisux/sveltednd"; import { useFetchSongs } from "../../queries/getSongsQuery"; import type { SetlistItem } from "../../types/SetlistItem"; import type { Selected } from "bits-ui"; import { Button } from "$lib/components/ui/button"; export let setlistItem: SetlistItem; - export let onUpdate: (id: string, updates: Partial) => void; - export let onRemove: (id: string) => void; + export let onUpdate: ( + identifier: string | undefined, + updates: Partial + ) => void; + export let onRemove: (identifier: string | undefined) => void; + const songsQuery = useFetchSongs(); function handleSongChange(selected: Selected | undefined) { - onUpdate(setlistItem.id, { songId: selected?.value as string }); + const identifier = setlistItem.id || setlistItem.tempId; + if (identifier) { + onUpdate(identifier, { songId: selected?.value as number | null }); + } + } + + function getSongName(id: number) { + const song = $songsQuery.data?.find((song) => song.id === id); + return song?.title || ""; } + + $: selectedElement = + !$songsQuery.isLoading && + !$songsQuery.isError && + setlistItem.type === "song" && + setlistItem.songId + ? { value: setlistItem.songId, label: getSongName(setlistItem.songId) } + : undefined; + + const identifier = setlistItem.id || setlistItem.tempId; -
+
- handleSongChange(e)}> - - - - - {#each $songsQuery.data || [] as song} - {song.title} - {/each} - - + {#if setlistItem.type === "song"} + {#if $songsQuery.isSuccess} + handleSongChange(e)} + selected={selectedElement} + > + + + + + {#each $songsQuery.data || [] as song} + {song.title} + {/each} + + + {/if} + {:else} + {setlistItem.title} + {/if} + - - - + + + +
+ Save +
@@ -94,16 +136,29 @@

{/if} - {#each $setlist as setlistItem} - {#if setlistItem.type === "song"} - - {:else} - - {/if} - {/each} +
+ {#each $setlist as setlistItem} +
+ {#if setlistItem.type === "song"} + + {:else} + + {/if} +
+ {/each} +
diff --git a/visualizer/src/queries/getSetlistItemsQuery.ts b/visualizer/src/queries/getSetlistItemsQuery.ts new file mode 100644 index 0000000..e3269f5 --- /dev/null +++ b/visualizer/src/queries/getSetlistItemsQuery.ts @@ -0,0 +1,34 @@ +// src/queries/useFetchSetlistItems.ts +import { createQuery } from "@tanstack/svelte-query"; +import axiosInstance from "../axiosInstance"; +import type { SetlistItem } from "../types/SetlistItem"; + +export function useFetchSetlistItems() { + return createQuery({ + queryKey: ["setlist_items"], + queryFn: async () => { + const response = await axiosInstance.get("/setlist_items"); + + const setlistItems: SetlistItem[] = response.data.map((item: any) => { + if (item.type === "song") { + return { + id: item.id, + type: "song", + songId: item.song_id, + notes: item.notes, + }; + } else { + return { + id: item.id, + type: item.type, + title: item.title, + notes: item.notes, + }; + } + }); + + return setlistItems; + }, + enabled: true, + }); +} diff --git a/visualizer/src/types/SetlistItem.ts b/visualizer/src/types/SetlistItem.ts index 8cf6941..55bdd92 100644 --- a/visualizer/src/types/SetlistItem.ts +++ b/visualizer/src/types/SetlistItem.ts @@ -1,12 +1,14 @@ export type SetlistItem = | { - id: string; + id?: string; + tempId?: string; type: "song"; - songId: string; + songId: number | null; notes?: string; } | { - id: string; + id?: string; + tempId?: string; type: "sample" | "break" | "speech"; title: string; notes?: string; From 3f40f3d42479bfd0efde095263954a5b41de96af Mon Sep 17 00:00:00 2001 From: Sergi Date: Sun, 17 Nov 2024 03:56:42 -0800 Subject: [PATCH 4/7] dnd finally working --- diffing/routers/setlists.py | 29 ++++++++++++------- .../setlist-item/GenericItem.svelte | 2 +- .../components/setlist-item/SongItem.svelte | 7 +++-- .../src/mutations/updateSetlistMutation.ts | 17 ++++++----- .../src/pages/SetlistMode/SetlistMode.svelte | 19 ++++++------ visualizer/src/types/SetlistItem.ts | 2 -- 6 files changed, 42 insertions(+), 34 deletions(-) diff --git a/diffing/routers/setlists.py b/diffing/routers/setlists.py index ad24fa0..4f5ee70 100644 --- a/diffing/routers/setlists.py +++ b/diffing/routers/setlists.py @@ -36,30 +36,37 @@ async def create_setlist_item(setlist_item: SetlistItemCreate, db: Session = Dep @router.put("/setlist", response_model=List[SetlistItemSchema]) def update_setlist( - new_setlist: List[SetlistItemSchema], # Expect Pydantic models in the request body + new_setlist: List[SetlistItemSchema], db: Session = Depends(get_db) ): - # Process new_setlist + # Validate new setlist items for item in new_setlist: if item.type == "song" and not item.song_id: raise HTTPException(status_code=400, detail="Missing songId for song type") if item.type != "song" and not item.title: raise HTTPException(status_code=400, detail="Missing title for non-song type") - # Remove stale items + # Get existing items from DB + existing_items = db.query(SetlistItem).all() + existing_ids = {item.id for item in existing_items if item.id} new_ids = {item.id for item in new_setlist if item.id} - db.query(SetlistItem).filter(~SetlistItem.id.in_(new_ids)).delete() + + # Remove stale items + stale_ids = existing_ids - new_ids + if stale_ids: + db.query(SetlistItem).filter(SetlistItem.id.in_(stale_ids)).delete() # Update or create items for item_data in new_setlist: - if item_data.id: # Update existing + if item_data.id and item_data.id in existing_ids: + # Update existing item db_item = db.query(SetlistItem).filter(SetlistItem.id == item_data.id).first() - if db_item: - for key, value in item_data.dict(exclude_unset=True).items(): - setattr(db_item, key, value) - else: # Create new - db_item = SetlistItem(**item_data.dict()) - db.add(db_item) + for key, value in item_data.dict(exclude_unset=True).items(): + setattr(db_item, key, value) + else: + # Create new item + new_db_item = SetlistItem(**item_data.dict()) + db.add(new_db_item) db.commit() diff --git a/visualizer/src/components/setlist-item/GenericItem.svelte b/visualizer/src/components/setlist-item/GenericItem.svelte index e9dfc1c..bd63ed9 100644 --- a/visualizer/src/components/setlist-item/GenericItem.svelte +++ b/visualizer/src/components/setlist-item/GenericItem.svelte @@ -19,7 +19,7 @@ ) => void; export let onRemove: (identifier: string | undefined) => void; - const identifier = setlistItem.id || setlistItem.tempId; + const identifier = setlistItem.id; console.log(identifier); function getIconForType(type: SetlistItem["type"]) { switch (type) { diff --git a/visualizer/src/components/setlist-item/SongItem.svelte b/visualizer/src/components/setlist-item/SongItem.svelte index 94c082c..5ef0dfd 100644 --- a/visualizer/src/components/setlist-item/SongItem.svelte +++ b/visualizer/src/components/setlist-item/SongItem.svelte @@ -12,6 +12,7 @@ import type { SetlistItem } from "../../types/SetlistItem"; import type { Selected } from "bits-ui"; import { Button } from "$lib/components/ui/button"; + import { dragHandle } from "svelte-dnd-action"; export let setlistItem: SetlistItem; export let onUpdate: ( @@ -23,7 +24,7 @@ const songsQuery = useFetchSongs(); function handleSongChange(selected: Selected | undefined) { - const identifier = setlistItem.id || setlistItem.tempId; + const identifier = setlistItem.id; if (identifier) { onUpdate(identifier, { songId: selected?.value as number | null }); } @@ -42,13 +43,13 @@ ? { value: setlistItem.songId, label: getSongName(setlistItem.songId) } : undefined; - const identifier = setlistItem.id || setlistItem.tempId; + const identifier = setlistItem.id; -
+
diff --git a/visualizer/src/mutations/updateSetlistMutation.ts b/visualizer/src/mutations/updateSetlistMutation.ts index caf8dd2..f3c47d1 100644 --- a/visualizer/src/mutations/updateSetlistMutation.ts +++ b/visualizer/src/mutations/updateSetlistMutation.ts @@ -7,14 +7,17 @@ interface UpdateSetlistVariables { setlist: SetlistItem[]; } -// Function to map client-side attributes to server-side attributes function mapSetlistForServer(setlist: SetlistItem[]) { return setlist.map((item) => { - const { tempId, songId, ...rest } = item; - return { - ...rest, - song_id: songId, - }; + if (item.type === "song") { + const { songId, ...rest } = item; + return { + ...rest, + song_id: songId, + }; + } + + return item; }); } @@ -24,7 +27,7 @@ export function useUpdateSetlist() { return createMutation({ mutationFn: async ({ setlist }) => { const mappedSetlist = mapSetlistForServer(setlist); - + console.log(mappedSetlist); const response = await axiosInstance.put( "/setlist_items/setlist", mappedSetlist, diff --git a/visualizer/src/pages/SetlistMode/SetlistMode.svelte b/visualizer/src/pages/SetlistMode/SetlistMode.svelte index b2ec5b3..48706d6 100644 --- a/visualizer/src/pages/SetlistMode/SetlistMode.svelte +++ b/visualizer/src/pages/SetlistMode/SetlistMode.svelte @@ -8,6 +8,7 @@ import { useFetchSetlistItems } from "../../queries/getSetlistItemsQuery"; import { useUpdateSetlist } from "../../mutations/updateSetlistMutation"; import { dragHandleZone, type DndEvent } from "svelte-dnd-action"; + import { flip } from "svelte/animate"; const setlist = writable([]); @@ -25,13 +26,13 @@ ...items, type === "song" ? { - tempId: crypto.randomUUID(), + id: crypto.randomUUID(), type: "song", songId: null, notes: "", } : { - tempId: crypto.randomUUID(), + id: crypto.randomUUID(), type, title: `New ${type.charAt(0).toUpperCase() + type.slice(1)}`, notes: "", @@ -48,7 +49,7 @@ setlist.update((items) => items.map((item) => { - if (item.id === id || item.tempId === id) { + if (item.id === id) { if (item.type === "song") { return { ...item, @@ -77,13 +78,12 @@ }) ); }; + const flipDurationMs = 300; const removeItem = (id: string | undefined) => { console.log("hello", id); if (!id) return; - setlist.update((items) => - items.filter((item) => item.id !== id && item.tempId !== id) - ); + setlist.update((items) => items.filter((item) => item.id !== id)); }; const handleSave = () => { @@ -92,7 +92,6 @@ }; function handleSort(e: CustomEvent>): void { - console.log(e.detail.items); setlist.set(e.detail.items); } @@ -137,13 +136,13 @@
{/if}
- {#each $setlist as setlistItem} -
+ {#each $setlist as setlistItem (setlistItem.id)} +
{#if setlistItem.type === "song"} Date: Sun, 17 Nov 2024 04:22:40 -0800 Subject: [PATCH 5/7] working like a charm --- diffing/models.py | 22 +++++----- diffing/routers/setlists.py | 18 +++++---- visualizer/package-lock.json | 22 ++++++++++ visualizer/package.json | 2 + visualizer/src/App.svelte | 3 ++ .../src/lib/components/ui/sonner/index.ts | 1 + .../lib/components/ui/sonner/sonner.svelte | 20 ++++++++++ .../src/mutations/updateSetlistMutation.ts | 1 + .../src/pages/SetlistMode/SetlistMode.svelte | 40 ++++++++++++++++--- visualizer/src/types/SetlistItem.ts | 2 + 10 files changed, 109 insertions(+), 22 deletions(-) create mode 100644 visualizer/src/lib/components/ui/sonner/index.ts create mode 100644 visualizer/src/lib/components/ui/sonner/sonner.svelte diff --git a/diffing/models.py b/diffing/models.py index 15f36d1..01db003 100644 --- a/diffing/models.py +++ b/diffing/models.py @@ -84,26 +84,29 @@ class SetlistItemType(str, enum.Enum): BREAK = "break" SPEECH = "speech" + class SetlistItem(Base): __tablename__ = "setlist_items" - id = Column(String, primary_key=True, index=True, default=lambda: str(uuid.uuid4())) # Change to String and use UUID - type = Column(Enum(SetlistItemType), nullable=False) - title = Column(String, nullable=True) # Nullable for songs - notes = Column(Text, nullable=True) - song_id = Column(Integer, ForeignKey("song_metadata.id"), nullable=True) + id: str = Column(String, primary_key=True, index=True, default=lambda: str(uuid.uuid4())) + type: SetlistItemType = Column(Enum(SetlistItemType), nullable=False) + title: Optional[str] = Column(String, nullable=True) + notes: Optional[str] = Column(Text, nullable=True) + song_id: Optional[int] = Column(Integer, ForeignKey("song_metadata.id"), nullable=True) + order: int = Column(Integer, nullable=False) - # Relationship to SongMetadata for song items song = relationship("SongMetadata", back_populates="setlist_items") def __repr__(self): - return f"" + return f"" class SetlistItemBase(BaseModel): type: SetlistItemType - title: Optional[str] = None # Nullable for songs + title: Optional[str] = None notes: Optional[str] = None song_id: Optional[int] = None + order: int + class SetlistItemCreate(SetlistItemBase): pass @@ -114,6 +117,7 @@ class SetlistItemSchema(BaseModel): title: Optional[str] = None notes: Optional[str] = None song_id: Optional[int] = None + order: int class Config: - from_attributes = True \ No newline at end of file + from_attributes = True \ No newline at end of file diff --git a/diffing/routers/setlists.py b/diffing/routers/setlists.py index 4f5ee70..e51e3e1 100644 --- a/diffing/routers/setlists.py +++ b/diffing/routers/setlists.py @@ -9,7 +9,7 @@ @router.get("/", response_model=List[SetlistItemSchema]) async def list_setlist_items(type: SetlistItemType = None, db: Session = Depends(get_db)): - query = db.query(SetlistItem) + query = db.query(SetlistItem).order_by(SetlistItem.order) if type: query = query.filter(SetlistItem.type == type) items = query.all() @@ -36,16 +36,19 @@ async def create_setlist_item(setlist_item: SetlistItemCreate, db: Session = Dep @router.put("/setlist", response_model=List[SetlistItemSchema]) def update_setlist( - new_setlist: List[SetlistItemSchema], + new_setlist: List[SetlistItemSchema], db: Session = Depends(get_db) ): # Validate new setlist items - for item in new_setlist: + for index, item in enumerate(new_setlist): if item.type == "song" and not item.song_id: raise HTTPException(status_code=400, detail="Missing songId for song type") if item.type != "song" and not item.title: raise HTTPException(status_code=400, detail="Missing title for non-song type") + # Assign order based on position in the list + item.order = index + # Get existing items from DB existing_items = db.query(SetlistItem).all() existing_ids = {item.id for item in existing_items if item.id} @@ -61,8 +64,9 @@ def update_setlist( if item_data.id and item_data.id in existing_ids: # Update existing item db_item = db.query(SetlistItem).filter(SetlistItem.id == item_data.id).first() - for key, value in item_data.dict(exclude_unset=True).items(): - setattr(db_item, key, value) + if db_item: + for key, value in item_data.dict(exclude_unset=True).items(): + setattr(db_item, key, value) else: # Create new item new_db_item = SetlistItem(**item_data.dict()) @@ -70,5 +74,5 @@ def update_setlist( db.commit() - # Return updated setlist - return db.query(SetlistItem).all() \ No newline at end of file + # Return updated setlist in correct order + return db.query(SetlistItem).order_by(SetlistItem.order).all() \ No newline at end of file diff --git a/visualizer/package-lock.json b/visualizer/package-lock.json index 58c3725..b93e37f 100644 --- a/visualizer/package-lock.json +++ b/visualizer/package-lock.json @@ -22,10 +22,12 @@ "bits-ui": "^0.21.16", "clsx": "^2.1.1", "lucide-svelte": "^0.456.0", + "mode-watcher": "^0.4.1", "postcss": "^8.4.47", "svelte": "^5.0.3", "svelte-check": "^4.0.5", "svelte-dnd-action": "^0.9.52", + "svelte-sonner": "^0.3.28", "tailwind-merge": "^2.5.4", "tailwind-variants": "^0.3.0", "tailwindcss": "^3.4.14", @@ -2118,6 +2120,16 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mode-watcher": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-0.4.1.tgz", + "integrity": "sha512-bNC+1NXmwEFZtziCdZSgP7HFQTpqJPcQn9GwwJQGSf6SBF3neEPYV1uRwkYuAQwbsvsXIYtzaqgedDzJ7D1mhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.1" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -2888,6 +2900,16 @@ "svelte": ">=3.23.0 || ^5.0.0-next.0" } }, + "node_modules/svelte-sonner": { + "version": "0.3.28", + "resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-0.3.28.tgz", + "integrity": "sha512-K3AmlySeFifF/cKgsYNv5uXqMVNln0NBAacOYgmkQStLa/UoU0LhfAACU6Gr+YYC8bOCHdVmFNoKuDbMEsppJg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1" + } + }, "node_modules/svelte-spa-router": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/svelte-spa-router/-/svelte-spa-router-4.0.1.tgz", diff --git a/visualizer/package.json b/visualizer/package.json index f0b6d50..bb42960 100644 --- a/visualizer/package.json +++ b/visualizer/package.json @@ -16,10 +16,12 @@ "bits-ui": "^0.21.16", "clsx": "^2.1.1", "lucide-svelte": "^0.456.0", + "mode-watcher": "^0.4.1", "postcss": "^8.4.47", "svelte": "^5.0.3", "svelte-check": "^4.0.5", "svelte-dnd-action": "^0.9.52", + "svelte-sonner": "^0.3.28", "tailwind-merge": "^2.5.4", "tailwind-variants": "^0.3.0", "tailwindcss": "^3.4.14", diff --git a/visualizer/src/App.svelte b/visualizer/src/App.svelte index fcd971b..373748e 100644 --- a/visualizer/src/App.svelte +++ b/visualizer/src/App.svelte @@ -4,6 +4,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/svelte-query"; import LeftSidebar from "./components/layout/LeftSidebar.svelte"; import Topbar from "./components/layout/Topbar.svelte"; + import { Toaster } from "$lib/components/ui/sonner"; const queryClient = new QueryClient(); @@ -29,6 +30,8 @@
+ + diff --git a/visualizer/src/lib/components/ui/sonner/index.ts b/visualizer/src/lib/components/ui/sonner/index.ts new file mode 100644 index 0000000..1ad9f4a --- /dev/null +++ b/visualizer/src/lib/components/ui/sonner/index.ts @@ -0,0 +1 @@ +export { default as Toaster } from "./sonner.svelte"; diff --git a/visualizer/src/lib/components/ui/sonner/sonner.svelte b/visualizer/src/lib/components/ui/sonner/sonner.svelte new file mode 100644 index 0000000..7d5b2f1 --- /dev/null +++ b/visualizer/src/lib/components/ui/sonner/sonner.svelte @@ -0,0 +1,20 @@ + + + diff --git a/visualizer/src/mutations/updateSetlistMutation.ts b/visualizer/src/mutations/updateSetlistMutation.ts index f3c47d1..5ae1647 100644 --- a/visualizer/src/mutations/updateSetlistMutation.ts +++ b/visualizer/src/mutations/updateSetlistMutation.ts @@ -8,6 +8,7 @@ interface UpdateSetlistVariables { } function mapSetlistForServer(setlist: SetlistItem[]) { + console.log("enter", setlist); return setlist.map((item) => { if (item.type === "song") { const { songId, ...rest } = item; diff --git a/visualizer/src/pages/SetlistMode/SetlistMode.svelte b/visualizer/src/pages/SetlistMode/SetlistMode.svelte index 48706d6..7c2c671 100644 --- a/visualizer/src/pages/SetlistMode/SetlistMode.svelte +++ b/visualizer/src/pages/SetlistMode/SetlistMode.svelte @@ -9,6 +9,7 @@ import { useUpdateSetlist } from "../../mutations/updateSetlistMutation"; import { dragHandleZone, type DndEvent } from "svelte-dnd-action"; import { flip } from "svelte/animate"; + import { toast } from "svelte-sonner"; const setlist = writable([]); @@ -20,7 +21,6 @@ setlist.set($setlistItemsQuery.data ?? []); } - // Add a new item with a temporary ID const addItem = (type: SetlistItem["type"]) => { setlist.update((items) => [ ...items, @@ -30,12 +30,14 @@ type: "song", songId: null, notes: "", + order: items.length, } : { id: crypto.randomUUID(), type, title: `New ${type.charAt(0).toUpperCase() + type.slice(1)}`, notes: "", + order: items.length, }, ]); }; @@ -81,18 +83,44 @@ const flipDurationMs = 300; const removeItem = (id: string | undefined) => { - console.log("hello", id); + console.log("Removing item with id:", id); if (!id) return; - setlist.update((items) => items.filter((item) => item.id !== id)); - }; + setlist.update((items) => { + return items + .filter((item) => item.id !== id) + .map((item, index) => ({ + ...item, + order: index, + })); + }); + }; const handleSave = () => { const currentSetlist = get(setlist); - $updateSetlistResult.mutate({ setlist: currentSetlist }); + console.log("Saving setlist:", currentSetlist); + $updateSetlistResult.mutate( + { setlist: currentSetlist }, + { + onSuccess: () => { + toast.success("Setlist saved successfully!", { + description: "Your setlist changes have been saved.", + }); + }, + onError: () => { + toast.error("Failed to save setlist.", { + description: "Please try again later.", + }); + }, + } + ); }; function handleSort(e: CustomEvent>): void { - setlist.set(e.detail.items); + const reorderedItems = e.detail.items.map((item, index) => ({ + ...item, + order: index, + })); + setlist.set(reorderedItems); } diff --git a/visualizer/src/types/SetlistItem.ts b/visualizer/src/types/SetlistItem.ts index 93db177..e2e432b 100644 --- a/visualizer/src/types/SetlistItem.ts +++ b/visualizer/src/types/SetlistItem.ts @@ -4,10 +4,12 @@ export type SetlistItem = type: "song"; songId: number | null; notes?: string; + order: number; } | { id?: string; type: "sample" | "break" | "speech"; title: string; notes?: string; + order: number; }; From 740ce696c769080987a482a20aee071792c1deac Mon Sep 17 00:00:00 2001 From: Sergi Date: Sun, 17 Nov 2024 10:03:14 -0800 Subject: [PATCH 6/7] fix order bug --- visualizer/src/components/layout/Topbar.svelte | 7 +++++++ visualizer/src/queries/getSetlistItemsQuery.ts | 2 ++ 2 files changed, 9 insertions(+) diff --git a/visualizer/src/components/layout/Topbar.svelte b/visualizer/src/components/layout/Topbar.svelte index 4e88308..6f2c977 100644 --- a/visualizer/src/components/layout/Topbar.svelte +++ b/visualizer/src/components/layout/Topbar.svelte @@ -5,11 +5,18 @@