From d7d844ee50f588b2cafcdf1170f2f16d204b09ca Mon Sep 17 00:00:00 2001 From: fourgate Date: Tue, 31 Dec 2024 16:59:49 +0100 Subject: [PATCH] feat: rework app to handle mutliple sessions concurrently --- README.md | 53 +++++---- apps/rpg-maestro-ui-e2e/src/e2e.spec.ts | 27 ++--- .../src/app/admin-ui/admin-api.ts | 67 ------------ apps/rpg-maestro-ui/src/app/app.tsx | 21 ++-- .../src/app/maestro-ui/maestro-api.ts | 92 ++++++++++++++++ .../maestro-soundboard.tsx | 37 +++++-- .../tracks-management/create-track-form.tsx | 7 +- .../tracks-management/file-upload.tsx | 12 ++- .../tracks-management/tracks-management.tsx | 38 +++++-- .../tracks-table/SearchSpecificTrack.tsx | 0 .../tracks-table/SearchTags.tsx | 0 .../tracks-table/edit-track-side-form.tsx | 8 +- .../tracks-table/quick-tag-selection.tsx | 19 +++- .../tracks-table/tracks-table.tsx | 4 +- .../audio-player-readonly.css | 3 +- .../{clients => players-ui}/clients-ui.tsx | 9 +- .../src/app/track-sync/track-sync.ts | 5 +- apps/rpg-maestro-ui/src/app/tracks-api.ts | 39 +++---- apps/rpg-maestro/examples/CreateTrack.http | 16 +-- .../examples/CreateTrackRemote.http | 44 -------- apps/rpg-maestro/examples/GetTrack.http | 4 - .../src/app/admin-api/AdminAPI.spec.ts | 96 ----------------- .../rpg-maestro/src/app/admin-api/Database.ts | 12 --- .../admin-api/ManageCurrentlyPlayingTracks.ts | 24 ----- apps/rpg-maestro/src/app/app.controller.ts | 79 ++++++++------ .../app/infrastructure/FirestoreDatabase.ts | 30 +++--- .../app/infrastructure/InMemoryDatabase.ts | 39 ++++--- ...{SessionDatabase.ts => InMemorySession.ts} | 2 +- .../src/app/maestro-api/Database.ts | 11 ++ .../src/app/maestro-api/MaestroAPI.spec.ts | 102 ++++++++++++++++++ .../ManageCurrentlyPlayingTracks.ts | 39 +++++++ .../TrackService.ts | 23 ++-- .../audio/AudioHelper.ts | 2 +- apps/rpg-maestro/src/app/model/Session.ts | 5 - libs/rpg-maestro-api-contract/src/index.ts | 4 +- .../lib/ChangeSessionPlayingTracksRequest.ts | 5 + .../src/lib/SessionPlayingTracks.ts | 5 + .../rpg-maestro-api-contract/src/lib/Track.ts | 3 +- .../src/lib/TracksFromDirectoryCreation.ts | 1 + 39 files changed, 543 insertions(+), 444 deletions(-) delete mode 100644 apps/rpg-maestro-ui/src/app/admin-ui/admin-api.ts create mode 100644 apps/rpg-maestro-ui/src/app/maestro-ui/maestro-api.ts rename apps/rpg-maestro-ui/src/app/{admin-ui => maestro-ui}/maestro-soundboard.tsx (77%) rename apps/rpg-maestro-ui/src/app/{admin-ui => maestro-ui}/tracks-management/create-track-form.tsx (94%) rename apps/rpg-maestro-ui/src/app/{admin-ui => maestro-ui}/tracks-management/file-upload.tsx (89%) rename apps/rpg-maestro-ui/src/app/{admin-ui => maestro-ui}/tracks-management/tracks-management.tsx (57%) rename apps/rpg-maestro-ui/src/app/{admin-ui => maestro-ui}/tracks-table/SearchSpecificTrack.tsx (100%) rename apps/rpg-maestro-ui/src/app/{admin-ui => maestro-ui}/tracks-table/SearchTags.tsx (100%) rename apps/rpg-maestro-ui/src/app/{admin-ui => maestro-ui}/tracks-table/edit-track-side-form.tsx (93%) rename apps/rpg-maestro-ui/src/app/{admin-ui => maestro-ui}/tracks-table/quick-tag-selection.tsx (60%) rename apps/rpg-maestro-ui/src/app/{admin-ui => maestro-ui}/tracks-table/tracks-table.tsx (95%) rename apps/rpg-maestro-ui/src/app/{clients => players-ui}/audio-player-readonly.css (95%) rename apps/rpg-maestro-ui/src/app/{clients => players-ui}/clients-ui.tsx (93%) delete mode 100644 apps/rpg-maestro/examples/CreateTrackRemote.http delete mode 100644 apps/rpg-maestro/examples/GetTrack.http delete mode 100644 apps/rpg-maestro/src/app/admin-api/AdminAPI.spec.ts delete mode 100644 apps/rpg-maestro/src/app/admin-api/Database.ts delete mode 100644 apps/rpg-maestro/src/app/admin-api/ManageCurrentlyPlayingTracks.ts rename apps/rpg-maestro/src/app/infrastructure/{SessionDatabase.ts => InMemorySession.ts} (74%) create mode 100644 apps/rpg-maestro/src/app/maestro-api/Database.ts create mode 100644 apps/rpg-maestro/src/app/maestro-api/MaestroAPI.spec.ts create mode 100644 apps/rpg-maestro/src/app/maestro-api/ManageCurrentlyPlayingTracks.ts rename apps/rpg-maestro/src/app/{admin-api => maestro-api}/TrackService.ts (82%) rename apps/rpg-maestro/src/app/{admin-api => maestro-api}/audio/AudioHelper.ts (89%) delete mode 100644 apps/rpg-maestro/src/app/model/Session.ts create mode 100644 libs/rpg-maestro-api-contract/src/lib/ChangeSessionPlayingTracksRequest.ts create mode 100644 libs/rpg-maestro-api-contract/src/lib/SessionPlayingTracks.ts diff --git a/README.md b/README.md index a48b653..840e7f2 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,58 @@ # RpgMaestro + ## TODO + +- use Creator musics, credit him !!! - Youtube to mp3 - - make a script that do conversion on my server and put it in correct directory ✅ - - upload on fourgate.cloud.private by default <--- figure out a way to either: - - have a private url for my own usage, only available in France && make the public one available to all && disable track uploading <-- demo version ? - - secure mp3 files behind auth - - crossOrigin={'use-credentials'} ? - - or add a token in url -> https://github.com/icidasset/diffuse/blob/6837490c25ec5a534fffdeb3abc818dea9386665/src/Javascript/Workers/service.js#L104 - - for now, add them to default session via API - - auto create the track in RPG maestro shared db <- this is not possible yet, might want to think about that.. like a catalogue ? + - make a script that do conversion on my server and put it in correct directory ✅ + - upload on fourgate.cloud.private by default <--- figure out a way to either: + - have a private url for my own usage, only available in France && make the public one available to all && + disable track uploading <-- demo version ? + - secure mp3 files behind auth + - crossOrigin={'use-credentials'} ? + - or add a token in + url -> https://github.com/icidasset/diffuse/blob/6837490c25ec5a534fffdeb3abc818dea9386665/src/Javascript/Workers/service.js#L104 + - for now, add them to default session via API + - auto create the track in RPG maestro shared db <- this is not possible yet, might want to think about that.. like + a catalogue ? - demo env vs my personal use ! - - a quick hack could be to have a real demo env.. with no users feat for now - - this will be done by implementing Maestro User auth feature - - there could be 2 two of users: Maestro and AdminMaestro - - a Maestro can only use existing tracks - - an AdminMaestro can upload tracks, and already have free TrackCatalogs loaded - - reminder: set a random Current song for every new sessions ! - - I need to think about Maestros enrollment - - then they have their own Admin userspace, and can share a public Session link (one maestro = one session) - - no need to secure the session links for now + - a quick hack could be to have a real demo env.. with no users feat for now + - this will be done by implementing Maestro User auth feature + - there could be 2 two of users: Maestro and AdminMaestro + - a Maestro can only use existing tracks + - an AdminMaestro can upload tracks, and already have free TrackCatalogs loaded + - reminder: set a random Current song for every new sessions ! + - TODO: think about possibility of having multiple maestro + - idea: maybe a quick hack would be for the gm to allow for others to modify its session with a simple button + and then, all Players with the URL can modify it + I will need to check userId or email vs the sessionOwner + I could simply add a list of additional Maestro on the session infos !!!!!! + - I need to think about Maestros enrollment + - then they have their own Admin userspace, and can share a public Session link (one maestro = one session) + - no need to secure the session links for now - soundboard - quick tags -- fix some tracks have unhandled "durations", example: 50717.210999999996 (this will probably negatively impact calculus and sync) +- fix some tracks have unhandled "durations", example: 50717.210999999996 (this will probably negatively impact calculus + and sync) - add a local Redis (or equivalent) ! - create a CI/CD - handle multienv (prod vs preprod-rpg-maestro.fourgate.cloud) - replace docker-build and deploy tasks by a custom executor ## run tests + ``` npx nx affected -t lint test e2e build --no-cloud ``` + ## run e2e tests + ``` npx nx e2e rpg-maestro-ui-e2e --ui --no-cloud ``` ## dev run + ``` npm i nx dev rpg-maestro @@ -44,6 +60,7 @@ nx serve rpg-maestro-ui ``` ## RPG-maestro server manual deploy + ``` echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin docker build . -t acevedor/rpg-maestro -f apps/rpg-maestro/Dockerfile --platform linux/amd64 diff --git a/apps/rpg-maestro-ui-e2e/src/e2e.spec.ts b/apps/rpg-maestro-ui-e2e/src/e2e.spec.ts index e20ff35..b14e5e5 100644 --- a/apps/rpg-maestro-ui-e2e/src/e2e.spec.ts +++ b/apps/rpg-maestro-ui-e2e/src/e2e.spec.ts @@ -1,18 +1,21 @@ import { expect, Page, test } from '@playwright/test'; -async function goToAdmin(page: Page) { - await page.goto('/admin'); - expect(await page.locator('h1').innerText()).toContain('Admin UI'); +async function goToMaestroPage(page: Page, sessionId: string) { + await page.goto(`/maestro/${sessionId}`); + expect(await page.locator('h1').innerText()).toContain('Maestro UI'); } -async function goToTracksManagement(page: Page) { - await page.goto('/admin/manage'); + +async function goToTracksManagement(page: Page, sessionId: string) { + await page.goto(`/maestro/manage/${sessionId}`); expect(await page.locator('h1').innerText()).toContain('Tracks management'); } +const A_SESSION_ID = 'a-session'; + test('a Maestro can load (via API) and play a current track for its players', async ({ page }) => { await test.step('init tracks from fileserver using api', async () => { try { - const res = await fetch('http://localhost:8099/admin/tracks', { + const res = await fetch(`http://localhost:8099/maestro/sessions/${A_SESSION_ID}/tracks`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -31,8 +34,8 @@ test('a Maestro can load (via API) and play a current track for its players', as } }); - await test.step('go to admin page, and list available tracks', async () => { - await goToAdmin(page); + await test.step('go to maestro page, and list available tracks', async () => { + await goToMaestroPage(page, A_SESSION_ID); await expect(page.locator('.MuiDataGrid-row', { hasText: 'race1' })).toBeVisible(); }); @@ -41,7 +44,7 @@ test('a Maestro can load (via API) and play a current track for its players', as }); await test.step('go to players page, current track should be displayed', async () => { - await page.goto('/'); + await page.goto(`/${A_SESSION_ID}`); expect(await page.locator('h1').innerText()).toContain('RPG-Maestro player UI'); await expect(page.getByText('race1')).toBeVisible(); }); @@ -49,14 +52,14 @@ test('a Maestro can load (via API) and play a current track for its players', as test('a Maestro can add a new track located on a remote server', async ({ page }) => { await test.step('go to Tracks management and add a track', async () => { - await goToTracksManagement(page); + await goToTracksManagement(page, A_SESSION_ID); await page.getByLabel('URL').fill('http://localhost:8099/public/light-switch-sound-198508.mp3'); await page.getByText('CREATE TRACK').click(); await expect(page.getByText('CREATE TRACK')).toBeEnabled(); }); - await test.step('track should be available on Admin UI', async () => { - await goToAdmin(page); + await test.step('track should be available on Maestro UI', async () => { + await goToMaestroPage(page, A_SESSION_ID); await expect(page.locator('.MuiDataGrid-row', { hasText: 'light-switch-sound-198508' })).toBeVisible(); }); }); diff --git a/apps/rpg-maestro-ui/src/app/admin-ui/admin-api.ts b/apps/rpg-maestro-ui/src/app/admin-ui/admin-api.ts deleted file mode 100644 index 2a14430..0000000 --- a/apps/rpg-maestro-ui/src/app/admin-ui/admin-api.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { displayError } from '../error-utils'; -import { Track, TrackCreation, TrackToPlay, TrackUpdate } from '@rpg-maestro/rpg-maestro-api-contract'; - -const rpgmaestroapiurl = import.meta.env.VITE_RPG_MAESTRO_API_URL; // TODO centralize - -export const setTrackToPlay = async (trackToPlay: TrackToPlay): Promise => { - try { - const response = await fetch(`${rpgmaestroapiurl}/admin/sessions/current/tracks`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(trackToPlay), - }); - if (!response.ok) { - console.log(response.status, response.statusText); - throw new Error('fetch failed for error: ' + response); - } - } catch (error) { - console.error(error); - displayError(`Fetch error: ${JSON.stringify(error)}`); - } -}; - -export const createTrack = async (trackCreation: TrackCreation): Promise => { - try { - const response = await fetch(`${rpgmaestroapiurl}/admin/tracks`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: JSON.stringify(trackCreation), - }); - if (!response.ok) { - console.log(response.status, response.statusText); - throw new Error('fetch failed for error: ' + response); - } - return (await response.json()) as Track; - } catch (error) { - console.error(error); - displayError(`Fetch error: ${JSON.stringify(error)}`); - return Promise.reject(); - } -}; - -export const updateTrack = async (trackId: string, trackUpdate: TrackUpdate): Promise => { - try { - const response = await fetch(`${rpgmaestroapiurl}/admin/tracks/${trackId}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: JSON.stringify(trackUpdate), - }); - if (!response.ok) { - console.log(response.status, response.statusText); - throw new Error('fetch failed for error: ' + response); - } - return (await response.json()) as Track; - } catch (error) { - console.error(error); - displayError(`Fetch error: ${JSON.stringify(error)}`); - return Promise.reject(); - } -}; diff --git a/apps/rpg-maestro-ui/src/app/app.tsx b/apps/rpg-maestro-ui/src/app/app.tsx index 401bc07..85b6e54 100644 --- a/apps/rpg-maestro-ui/src/app/app.tsx +++ b/apps/rpg-maestro-ui/src/app/app.tsx @@ -1,26 +1,29 @@ import styled from 'styled-components'; -import { Route, Routes } from 'react-router-dom'; +import { Navigate, Route, Routes } from 'react-router-dom'; import 'react-h5-audio-player/lib/styles.css'; import 'react-toastify/dist/ReactToastify.css'; -import { MaestroSoundboard } from './admin-ui/maestro-soundboard'; -import { ClientsUi } from './clients/clients-ui'; -import { TracksManagement } from './admin-ui/tracks-management/tracks-management'; +import { MaestroSoundboard } from './maestro-ui/maestro-soundboard'; +import { ClientsUi } from './players-ui/clients-ui'; +import { TracksManagement } from './maestro-ui/tracks-management/tracks-management'; const StyledApp = styled.div` - // Your style here + // Your style here `; export function App() { return ( - + {/* START: routes */} {/* These routes and navigation have been generated for you */} {/* Feel free to move and update them to fit your needs */} - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> {/* END: routes */} diff --git a/apps/rpg-maestro-ui/src/app/maestro-ui/maestro-api.ts b/apps/rpg-maestro-ui/src/app/maestro-ui/maestro-api.ts new file mode 100644 index 0000000..0108ff3 --- /dev/null +++ b/apps/rpg-maestro-ui/src/app/maestro-ui/maestro-api.ts @@ -0,0 +1,92 @@ +import { displayError } from '../error-utils'; +import { + ChangeSessionPlayingTracksRequest, + Track, + TrackCreation, + TrackUpdate, +} from '@rpg-maestro/rpg-maestro-api-contract'; + +const rpgmaestroapiurl = import.meta.env.VITE_RPG_MAESTRO_API_URL; // TODO centralize + +export const getAllTracks = async (sessionId: string): Promise => { + try { + const response = await fetch(rpgmaestroapiurl + `/maestro/sessions/${sessionId}/tracks`); + if (response.ok) { + return (await response.json()) as Track[]; + } else { + console.log(response.status, response.statusText); + console.debug(response); + throw new Error('fetch failed for error: ' + response); + } + } catch (error) { + console.error(error); + displayError(`Fetch /maestro/sessions/${sessionId}/tracks error: ${error}`); + return []; + } +}; + +export const setTrackToPlay = async ( + sessionId: string, + changeSessionPlayingTracksRequest: ChangeSessionPlayingTracksRequest +): Promise => { + try { + const response = await fetch(`${rpgmaestroapiurl}/maestro/sessions/${sessionId}/playing-tracks`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(changeSessionPlayingTracksRequest), + }); + if (!response.ok) { + console.log(response.status, response.statusText); + throw new Error('fetch failed for error: ' + response); + } + } catch (error) { + console.error(error); + displayError(`Fetch /maestro/sessions/${sessionId}/playing-tracks error: ${JSON.stringify(error)}`); + } +}; + +export const createTrack = async (sessionId: string, trackCreation: TrackCreation): Promise => { + try { + const response = await fetch(`${rpgmaestroapiurl}/maestro/sessions/${sessionId}/tracks`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify(trackCreation), + }); + if (!response.ok) { + console.log(response.status, response.statusText); + throw new Error('fetch failed for error: ' + response); + } + return (await response.json()) as Track; + } catch (error) { + console.error(error); + displayError(`Fetch error: ${JSON.stringify(error)}`); + return Promise.reject(); + } +}; + +export const updateTrack = async (sessionId: string, trackId: string, trackUpdate: TrackUpdate): Promise => { + try { + const response = await fetch(`${rpgmaestroapiurl}/maestro/sessions/${sessionId}/tracks/${trackId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify(trackUpdate), + }); + if (!response.ok) { + console.log(response.status, response.statusText); + throw new Error('fetch failed for error: ' + response); + } + return (await response.json()) as Track; + } catch (error) { + console.error(error); + displayError(`Fetch error: ${JSON.stringify(error)}`); + return Promise.reject(); + } +}; diff --git a/apps/rpg-maestro-ui/src/app/admin-ui/maestro-soundboard.tsx b/apps/rpg-maestro-ui/src/app/maestro-ui/maestro-soundboard.tsx similarity index 77% rename from apps/rpg-maestro-ui/src/app/admin-ui/maestro-soundboard.tsx rename to apps/rpg-maestro-ui/src/app/maestro-ui/maestro-soundboard.tsx index 45cd74d..568ccb8 100644 --- a/apps/rpg-maestro-ui/src/app/admin-ui/maestro-soundboard.tsx +++ b/apps/rpg-maestro-ui/src/app/maestro-ui/maestro-soundboard.tsx @@ -1,8 +1,7 @@ import { TrackFilters, TracksTable } from './tracks-table/tracks-table'; import React, { useEffect, useState } from 'react'; -import { getAllTracks } from '../tracks-api'; import { Tag, Track } from '@rpg-maestro/rpg-maestro-api-contract'; -import { setTrackToPlay } from './admin-api'; +import { getAllTracks, setTrackToPlay } from './maestro-api'; import { ToastContainer } from 'react-toastify'; import SearchSpecificTrack from './tracks-table/SearchSpecificTrack'; import { TextLinkWithIconWrapper } from '../ui-components/text-link-with-icon-wrapper'; @@ -14,11 +13,18 @@ import HolidayVillageIcon from '@mui/icons-material/HolidayVillage'; import CastleIcon from '@mui/icons-material/Castle'; import ForestIcon from '@mui/icons-material/Forest'; import HikingIcon from '@mui/icons-material/Hiking'; +import { displayError } from '../error-utils'; +import { useParams } from 'react-router'; export function MaestroSoundboard() { const [allTracks, setAllTracks] = useState(undefined); const [trackFilters, setTrackFilters] = useState({}); - + const sessionId = useParams().sessionId ?? ''; + if (sessionId === '') { + displayError('no session found in URL (it should be https://{URL}/maestro/{sessionId})'); + // TODO redirect by generating a sessionId in backend, and redirect on /maestro/{sessionId} + throw new Error('no session found in URL'); + } useEffect(() => { if (allTracks === undefined) { refreshTracks(); @@ -26,11 +32,11 @@ export function MaestroSoundboard() { }); const refreshTracks = () => { - getAllTracks().then((x) => setAllTracks(x)); + getAllTracks(sessionId).then((x) => setAllTracks(x)); }; const requestSetTrackToPlay = async (trackId: string) => { - await setTrackToPlay({ trackId }); + await setTrackToPlay(sessionId, { currentTrack: { trackId } }); }; const onTrackSearchChange = (track: Track | null) => { @@ -53,12 +59,20 @@ export function MaestroSoundboard() {
-

Admin UI

+

Maestro UI

As the Maestro, control what current track is playing for the session

WIP under construction

- - + +
@@ -104,13 +118,18 @@ export function MaestroSoundboard() {
- +
or
string | null; } export function CreateTrackForm(props: CreateTrackFormProps) { - const { consumeFileUploadedEvent } = props; + const { sessionId, consumeFileUploadedEvent } = props; const [inputUrl, setInputUrl] = useState(undefined); const [inputUrlError, setInputUrlError] = useState(null); const [inputName, setInputName] = useState(undefined); @@ -40,7 +41,7 @@ export function CreateTrackForm(props: CreateTrackFormProps) { throw Error('inputUrl should be present'); } setIsCreatingTrack(true); - createTrack({ + createTrack(sessionId, { url: inputUrl, name: inputName, tags: inputTags ?? [], diff --git a/apps/rpg-maestro-ui/src/app/admin-ui/tracks-management/file-upload.tsx b/apps/rpg-maestro-ui/src/app/maestro-ui/tracks-management/file-upload.tsx similarity index 89% rename from apps/rpg-maestro-ui/src/app/admin-ui/tracks-management/file-upload.tsx rename to apps/rpg-maestro-ui/src/app/maestro-ui/tracks-management/file-upload.tsx index 947b565..290de02 100644 --- a/apps/rpg-maestro-ui/src/app/admin-ui/tracks-management/file-upload.tsx +++ b/apps/rpg-maestro-ui/src/app/maestro-ui/tracks-management/file-upload.tsx @@ -17,11 +17,13 @@ const VisuallyHiddenInput = styled('input')({ whiteSpace: 'nowrap', width: 1, }); -export interface FileUploadProps{ - onFileUploaded: (uploadedFileURL: string) => void + +export interface FileUploadProps { + onFileUploaded: (uploadedFileURL: string) => void; } + export function FileUpload(props: FileUploadProps) { - const {onFileUploaded} = props; + const { onFileUploaded } = props; const [uploadProgress, setUploadProgress] = useState(0); const onFileUploadChange = (event: React.ChangeEvent) => { @@ -48,8 +50,8 @@ export function FileUpload(props: FileUploadProps) { .post(`${audioFileUploaderAPI}/upload/audio`, formData, config) .then((response) => { console.log(response.data); - const uploadRes = response.data as {fileURL: string}; - onFileUploaded(`${uploadRes.fileURL}`) + const uploadRes = response.data as { fileURL: string }; + onFileUploaded(`${uploadRes.fileURL}`); }) .catch((error) => { console.error('Error uploading file: ', error); diff --git a/apps/rpg-maestro-ui/src/app/admin-ui/tracks-management/tracks-management.tsx b/apps/rpg-maestro-ui/src/app/maestro-ui/tracks-management/tracks-management.tsx similarity index 57% rename from apps/rpg-maestro-ui/src/app/admin-ui/tracks-management/tracks-management.tsx rename to apps/rpg-maestro-ui/src/app/maestro-ui/tracks-management/tracks-management.tsx index 63b381e..f720639 100644 --- a/apps/rpg-maestro-ui/src/app/admin-ui/tracks-management/tracks-management.tsx +++ b/apps/rpg-maestro-ui/src/app/maestro-ui/tracks-management/tracks-management.tsx @@ -5,36 +5,56 @@ import KeyboardReturnIcon from '@mui/icons-material/KeyboardReturn'; import { CreateTrackForm } from './create-track-form'; import { ArrowForward } from '@mui/icons-material'; import { ToastContainer } from 'react-toastify'; +import { displayError } from '../../error-utils'; +import { useParams } from 'react-router'; export function TracksManagement() { - const [onFileUploadedEvent, setOnFileUploadedEvent] = useState(null); + const [onFileUploadedEvent, setOnFileUploadedEvent] = useState(null); + const sessionId = useParams().sessionId ?? ''; + if (sessionId === '') { + displayError('no session found in URL'); + throw new Error('no session found in URL'); + } const createFileUploadedEvent = (uploadedFileURL: string) => { setOnFileUploadedEvent(uploadedFileURL); - } - const consumeFileUploadedEvent= () => { + }; + const consumeFileUploadedEvent = () => { const copy = onFileUploadedEvent; setOnFileUploadedEvent(null); return copy; - } + }; return (
- +

Tracks management


- +

upload your track on the common file server

-
- +
+

Then create it on the default playlist

Or directly reference a track from a remote and public URL

- +
diff --git a/apps/rpg-maestro-ui/src/app/admin-ui/tracks-table/SearchSpecificTrack.tsx b/apps/rpg-maestro-ui/src/app/maestro-ui/tracks-table/SearchSpecificTrack.tsx similarity index 100% rename from apps/rpg-maestro-ui/src/app/admin-ui/tracks-table/SearchSpecificTrack.tsx rename to apps/rpg-maestro-ui/src/app/maestro-ui/tracks-table/SearchSpecificTrack.tsx diff --git a/apps/rpg-maestro-ui/src/app/admin-ui/tracks-table/SearchTags.tsx b/apps/rpg-maestro-ui/src/app/maestro-ui/tracks-table/SearchTags.tsx similarity index 100% rename from apps/rpg-maestro-ui/src/app/admin-ui/tracks-table/SearchTags.tsx rename to apps/rpg-maestro-ui/src/app/maestro-ui/tracks-table/SearchTags.tsx diff --git a/apps/rpg-maestro-ui/src/app/admin-ui/tracks-table/edit-track-side-form.tsx b/apps/rpg-maestro-ui/src/app/maestro-ui/tracks-table/edit-track-side-form.tsx similarity index 93% rename from apps/rpg-maestro-ui/src/app/admin-ui/tracks-table/edit-track-side-form.tsx rename to apps/rpg-maestro-ui/src/app/maestro-ui/tracks-table/edit-track-side-form.tsx index 2a1f2b9..09da714 100644 --- a/apps/rpg-maestro-ui/src/app/admin-ui/tracks-table/edit-track-side-form.tsx +++ b/apps/rpg-maestro-ui/src/app/maestro-ui/tracks-table/edit-track-side-form.tsx @@ -5,17 +5,18 @@ import TextField from '@mui/material/TextField'; import Autocomplete from '@mui/material/Autocomplete'; import Button from '@mui/material/Button'; import { Track } from '@rpg-maestro/rpg-maestro-api-contract'; -import { updateTrack } from '../admin-api'; +import { updateTrack } from '../maestro-api'; export interface EditTrackSideFormProps { open: boolean; onClose: () => unknown; trackToEdit: Track; + sessionId: string; onTrackUpdated: () => unknown; } export function EditTrackSideForm(props: EditTrackSideFormProps) { - const { open, onClose, trackToEdit, onTrackUpdated } = props; + const { open, onClose, trackToEdit, onTrackUpdated, sessionId } = props; const [inputUrl, setInputUrl] = useState(trackToEdit.url); const [inputUrlError, setInputUrlError] = useState(null); const [inputName, setInputName] = useState(trackToEdit.name); @@ -39,14 +40,13 @@ export function EditTrackSideForm(props: EditTrackSideFormProps) { throw Error('inputUrl should be present'); } setIsWaitingForTrackEdition(true); - updateTrack(trackToEdit.id, { + updateTrack(sessionId, trackToEdit.id, { name: inputName, tags: inputTags ?? [], }).then((track: Track) => { console.log(`track updated: ${JSON.stringify(track)}`); setIsWaitingForTrackEdition(false); onTrackUpdated(); - }); }; return ( diff --git a/apps/rpg-maestro-ui/src/app/admin-ui/tracks-table/quick-tag-selection.tsx b/apps/rpg-maestro-ui/src/app/maestro-ui/tracks-table/quick-tag-selection.tsx similarity index 60% rename from apps/rpg-maestro-ui/src/app/admin-ui/tracks-table/quick-tag-selection.tsx rename to apps/rpg-maestro-ui/src/app/maestro-ui/tracks-table/quick-tag-selection.tsx index 00d6c12..913853c 100644 --- a/apps/rpg-maestro-ui/src/app/admin-ui/tracks-table/quick-tag-selection.tsx +++ b/apps/rpg-maestro-ui/src/app/maestro-ui/tracks-table/quick-tag-selection.tsx @@ -14,17 +14,26 @@ export interface QuickTagSelectionProps { icon: SvgIconComponent; color: Color; tags: string[]; - onQuickTagSelection:(tags: string[]) => void + onQuickTagSelection: (tags: string[]) => void; } export function QuickTagSelection(props: QuickTagSelectionProps) { - const { text, icon, tags, color, onQuickTagSelection} = props; + const { text, icon, tags, color, onQuickTagSelection } = props; const iconInstance = React.createElement(icon, { sx: { fontSize: 50 } }); return ( -