diff --git a/package-lock.json b/package-lock.json index b1db142..e45aba5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,9 @@ "videojs-youtube": "^3.0.1", "web-vitals": "^2.1.4", "ws": "^8.13.0" + }, + "devDependencies": { + "@types/react-beautiful-dnd": "^13.1.8" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -4103,6 +4106,15 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-beautiful-dnd": { + "version": "13.1.8", + "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.8.tgz", + "integrity": "sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-dom": { "version": "18.2.13", "license": "MIT", diff --git a/package.json b/package.json index b29394e..82b66f5 100644 --- a/package.json +++ b/package.json @@ -64,5 +64,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/react-beautiful-dnd": "^13.1.8" } } diff --git a/src/components/MediaWidget/AddMediaPopup.tsx b/src/components/MediaWidget/AddMediaPopup.tsx index 49bf4a5..6af5793 100644 --- a/src/components/MediaWidget/AddMediaPopup.tsx +++ b/src/components/MediaWidget/AddMediaPopup.tsx @@ -1,11 +1,12 @@ import React, { useEffect, useState } from "react"; import { v4 as uuidv4 } from "uuid"; -import { Playlist } from "./types"; import axios from "axios"; import { useLoaderData } from "react-router"; import { log } from "../../logging"; +import { Playlist } from "../../logic/playlist/Playlist"; +import { Song } from "./types"; -export default function AddMediaPopup() { +export default function AddMediaPopup({ playlist }: { playlist: Playlist }) { const [showNewMediaPopup, setShowNewMediaPopup] = useState(false); const [savedPlaylists, setSavedPlaylists] = useState([]); const { recipientId } = useLoaderData(); @@ -40,7 +41,7 @@ export default function AddMediaPopup() { }; return song; }); - document.dispatchEvent(new CustomEvent("addSongs", { detail: songs })); + songs.forEach((song:Song) => playlist.addSong(song)); return list.title; }); } @@ -77,11 +78,11 @@ export default function AddMediaPopup() { src: url, type: "video/youtube", id: id, - originId: id, owner: recipientId, title: "Manually added video", + originId: null }; - document.dispatchEvent(new CustomEvent("addSongs", { detail: [song] })); + playlist.addSong(song); } document.getElementById("new-media-input").value = ""; setShowNewMediaPopup(false); diff --git a/src/components/MediaWidget/IPlayer.ts b/src/components/MediaWidget/IPlayer.ts new file mode 100644 index 0000000..a26b0ef --- /dev/null +++ b/src/components/MediaWidget/IPlayer.ts @@ -0,0 +1,12 @@ +import { Song } from "./types"; + +export enum PlayerState{ + PLAYING, + PAUSED, + STOPPED +} + +export interface IPlayer { + id: string; + play(song: Song): void; +} diff --git a/src/components/MediaWidget/IPlaylist.ts b/src/components/MediaWidget/IPlaylist.ts new file mode 100644 index 0000000..95cc008 --- /dev/null +++ b/src/components/MediaWidget/IPlaylist.ts @@ -0,0 +1,10 @@ +import { Playlist } from "../../logic/playlist/Playlist"; +import { PlaylistController } from "./PlaylistController"; + +export interface IPlaylistRenderer { + id: string; + playlistController: PlaylistController; + + bind(playlist: Playlist): void; + unbind(playlist: Playlist): void; +} diff --git a/src/components/MediaWidget/MediaWidget.css b/src/components/MediaWidget/MediaWidget.css index 60e1008..23e25b1 100644 --- a/src/components/MediaWidget/MediaWidget.css +++ b/src/components/MediaWidget/MediaWidget.css @@ -132,11 +132,36 @@ right: 10px; } +.player-header { + display: flex; + justify-content: space-between; + align-items: center; + /* flex-wrap: wrap; */ +} + +.videoImplToggler { + flex: 0 0 auto; + margin-right: 10px; + margin-left: 10px; + padding-top: 15px; +} + +.videoImpl { + margin-left: 5px; + color: white; + vertical-align: top; +} + .song-title-container { + flex: 1 1 150px; padding-left: 10px; padding-top: 10px; color: rgb(13, 110, 253); min-height: 50px; + display: inline-block; + overflow-x: hidden; + white-space: nowrap; + width: 150px; } .add-button-container .btn.add { @@ -154,6 +179,7 @@ padding-top: 15px; padding-right: 15px; font-size: 14px; + display: inline-block; } .faded { @@ -302,3 +328,15 @@ iframe { margin-right: auto; margin-bottom: 10px; } + +.videoImplToggler { + height: 100%; +} + +.videoImplToggler button { + border-radius: 5px; + background: none; + color: white; + height: 30px; + border: 1x solid rgb(110, 110, 110); +} diff --git a/src/components/MediaWidget/MediaWidget.tsx b/src/components/MediaWidget/MediaWidget.tsx index 7cd68d9..1849ef5 100644 --- a/src/components/MediaWidget/MediaWidget.tsx +++ b/src/components/MediaWidget/MediaWidget.tsx @@ -3,69 +3,65 @@ import { useRef, useEffect, useState } from "react"; import "bootstrap/dist/css/bootstrap.min.css"; import { useLoaderData, useNavigate } from "react-router"; import "./MediaWidget.css"; -import { v4 as uuidv4 } from "uuid"; -import Playlist from "./Playlist"; -import Player from "./Player"; -import { PLAYLIST_TYPE, PlaylistController } from "./PlaylistController"; -import { setupCommandListener, subscribe } from "../../socket"; +import { PlaylistController } from "./PlaylistController"; +import { setupCommandListener } from "../../socket"; import Menu from "../Menu/Menu"; import { PaymentPageConfig } from "./PaymentPageConfig"; -import { log } from "../../logging"; import RequestsDisabledWarning from "./RequestsDisabledWarning"; import MenuEventButton from "../Menu/MenuEventButton"; import MenuButton from "../Menu/MenuButton"; +import { PLAYLIST_TYPE, Playlist } from "../../logic/playlist/Playlist"; +import { IPlaylistChangesListener } from "../../logic/playlist/PlaylistChangesListener"; +import PlaylistComponent from "./PlaylistComponent"; +import VideoJSComponent from "./VideoJSComponent"; import { Song } from "./types"; +import { log } from "../../logging"; export default function MediaWidget({}: {}) { - const [playlist, setPlaylist] = useState([]); - const [current, setCurrent] = useState(0); - const playlistController = useRef(null); + const { recipientId, conf, widgetId } = useLoaderData(); + + const [playlist, setPlaylist] = useState( + new Playlist(PLAYLIST_TYPE.REQUESTED), + ); + const [playlistSize, setPlaylistSize] = useState(0); + const [index, setIndex] = useState(-1); const paymentPageConfig = useRef(); const navigate = useNavigate(); - const { recipientId, conf, widgetId } = useLoaderData(); const [activeTab, setActiveTab] = useState(PLAYLIST_TYPE.REQUESTED); - const [requestsEnabled, setRequestsEnabled] = useState(true); + const playlistController = useRef(); + const [song, setSong] = useState(null); useEffect(() => { + const playlistListener: IPlaylistChangesListener = { + id: widgetId, + trigger(playlist: Playlist) { + log.debug(`updating MediaWidget because of changes in Playlist`); + setPlaylistSize(playlist.songs().length); + setIndex(playlist.index() ?? -1); + setActiveTab(playlist.type()); + setSong(playlist.song()); + }, + }; + setupCommandListener(widgetId, () => navigate(0)); + paymentPageConfig.current = new PaymentPageConfig(); playlistController.current = new PlaylistController( recipientId, - setPlaylist, - setCurrent, - (tab: PLAYLIST_TYPE) => { - setActiveTab(tab); - log.debug(`using tab ${tab}`); - }, + widgetId, + conf, ); - subscribe(widgetId, conf.topic.media, (message) => { - let json = JSON.parse(message.body); - let song = { - src: json.url, - type: "video/youtube", - id: uuidv4(), - originId: json.id, - owner: "Аноним", - title: json.title, - }; - playlistController.current?.handleNewRequestedSongEvent(song); - message.ack(); + playlistController.current.addPlaylistRenderer({ + id: widgetId, + bind: (playlist: Playlist) => { + setPlaylist(playlist); + playlist.addListener(playlistListener); + }, + unbind: (playlist: Playlist) => { + playlist.removeListener(widgetId); + }, + playlistController: playlistController.current, }); - setupCommandListener(widgetId, () => navigate(0)); - paymentPageConfig.current = new PaymentPageConfig(); }, [recipientId, widgetId]); - useEffect(() => { - const toggle: EventListenerOrEventListenerObject = (event: { detail: boolean }) => { - log.debug(`toggle requests: ${event.detail}`); - setRequestsEnabled(event.detail); - } - log.debug("create mediawidget listener for media-requests toggler"); - document.addEventListener("toggleMediaRequests", toggle); - return () => { - log.debug("destroy mediawidget listener for media-requests toggler"); - document.removeEventListener("toggleMediaRequests", toggle); - }; - }, [widgetId]); - return ( <>