remove old src folder

Seto Elkahfi committed Aug 4, 2024 at 15:38 UTC c0d6cb2d84e0342b76238858bee73b2564919fdc
116 files changed +155 -4707
frontend/splitfire-desktop/app/_lib/params.ts new
+1
@@ -0,0 +1 @@
1 +export const PARAMS_USER_ID = "userId";
\ No newline at end of file
frontend/splitfire-desktop/app/_src/App.css deleted
-111
@@ -1,111 +0,0 @@
1 -html, body {
2 - overflow: hidden;
3 -}
4 -html,
5 -body,
6 -#root {
7 - height: 100%;
8 -}
9 -
10 -.main-container {
11 - position: absolute;
12 - top: 0;
13 - bottom: 0;
14 - left: 0;
15 - right: 0;
16 - overflow: auto;
17 -}
18 -
19 -.bd-placeholder-img {
20 - font-size: 1.125rem;
21 - text-anchor: middle;
22 - -webkit-user-select: none;
23 - -moz-user-select: none;
24 - user-select: none;
25 -}
26 -
27 -@media (min-width: 768px) {
28 - .bd-placeholder-img-lg {
29 - font-size: 3.5rem;
30 - }
31 -}
32 -
33 -/*
34 - * Globals
35 - */
36 -
37 -.title {
38 - font-size: calc(1.375rem + 1.0vw);
39 - font-weight: 300;
40 - line-height: 1.2;
41 -}
42 -
43 -/* Custom default button */
44 -.btn-secondary,
45 -.btn-secondary:hover,
46 -.btn-secondary:focus {
47 - color: #333;
48 - text-shadow: none;
49 - /* Prevent inheritance from `body` */
50 -}
51 -
52 -
53 -/*
54 - * Base structure
55 - */
56 -
57 -body {
58 - text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5);
59 - box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5);
60 -}
61 -
62 -.cover-container {
63 - max-width: 45em;
64 -}
65 -
66 -.flex-wrapper {
67 - display: flex;
68 - min-height: 82vh;
69 - flex-direction: column;
70 - justify-content: space-between;
71 -}
72 -
73 -/*
74 - * Header
75 - */
76 -
77 -.nav-masthead .nav-link {
78 - padding: .25rem 0;
79 - font-weight: 700;
80 - color: rgba(255, 255, 255, .5);
81 - background-color: transparent;
82 - border-bottom: .25rem solid transparent;
83 -}
84 -
85 -.nav-masthead .nav-link:hover,
86 -.nav-masthead .nav-link:focus {
87 - border-bottom-color: rgba(255, 255, 255, .25);
88 -}
89 -
90 -.nav-masthead .nav-link+.nav-link {
91 - margin-left: 1rem;
92 -}
93 -
94 -.nav-masthead .active {
95 - color: #fff;
96 - border-bottom-color: #fff;
97 -}
98 -
99 -/* Votes */
100 -.votes {
101 - font-size: calc(5.375rem + 1.5vw);
102 -}
103 -
104 -input[type=range]{
105 - writing-mode: bt-lr;
106 - appearance: slider-vertical;
107 - -webkit-appearance: slider-vertical;
108 - width: 10px;
109 - height: 150px;
110 - padding: 0 0px;
111 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/App.test.js deleted
-8
@@ -1,8 +0,0 @@
1 -import { render, screen } from '@testing-library/react';
2 -import App from './App';
3 -
4 -test('renders learn react link', () => {
5 - render(<App />);
6 - const linkElement = screen.getByText(/learn react/i);
7 - expect(linkElement).toBeInTheDocument();
8 -});
frontend/splitfire-desktop/app/_src/App.tsx deleted
-129
@@ -1,129 +0,0 @@
1 -import { IntlProvider } from 'react-intl'
2 -import { BrowserRouter, Route, Routes } from 'react-router-dom'
3 -import 'bootstrap/dist/css/bootstrap.min.css'
4 -import messagesEn from './translations/en.json'
5 -//import messagesId from './translations/id.json'
6 -import './App.css';
7 -import Main from './components/templates/Main';
8 -import axios from 'axios'
9 -import About from './components/pages/About'
10 -import Home from './components/pages/Home'
11 -import Login from './components/profiles/Login'
12 -import Logout from './components/profiles/Logout'
13 -import { ProfileRootView } from './components/profiles/Profile'
14 -import { ProfileFollowersRootView } from './components/profiles/ProfileFollowers'
15 -import { ProfileFollowingRootView } from './components/profiles/ProfileFollowing'
16 -import Song from './components/player/Song'
17 -import { CurrentUser, CurrentUserType, db } from './lib/db'
18 -import { ProfileNetworksRootView } from './components/profiles/ProfileNetworks'
19 -import Contact from './components/pages/Contact'
20 -import KaraokeView from './components/karaoke/KaraokeView'
21 -import { KaraokePlayerView } from './components/karaoke/KaraokePlayerView'
22 -import KaraokeLyricsView from './components/karaoke/KaraokeLyricsView'
23 -import Search from './components/pages/Search';
24 -import { SongView as SongDetailView } from './components/pages/song/SongView';
25 -import { Split } from './components/pages/esef/Split';
26 -import { SplitDetailView } from './components/pages/esef/SplitFireView';
27 -import { useEffect, useState } from 'react';
28 -import { Container, Spinner } from 'react-bootstrap';
29 -import { UserContext } from './lib/CurrentUserContext';
30 -import { PlayerAudio } from './components/player/Player';
31 -import { PlayerView } from './components/player/PlayerView';
32 -import { useLogger } from '../../lib/logger';
33 -import { LoadingView } from './components/templates/LoadingView';
34 -
35 -let i18nConfig = {
36 - language: 'en',
37 - messages: messagesEn
38 -};
39 -
40 -let host = process.env.REACT_APP_SPLITFIRE_API_HOST;
41 -
42 -axios.defaults.baseURL = `https://${host}/api/v1/`
43 -
44 -enum State {
45 - LOADING,
46 - LOADED,
47 - ERROR
48 -}
49 -
50 -export default function App() {
51 -
52 - const log = useLogger('App')
53 - const [state, setState] = useState(State.LOADING)
54 - const [user, setUser] = useState<CurrentUser | null>(null)
55 -
56 - const getCurrentUser = async () => {
57 - setState(State.LOADING)
58 - try {
59 - const result = await db.currentUser.where({ type: CurrentUserType.MAIN }).first()
60 - log.debug('Result: ', result)
61 - if (result) {
62 - setUser(result)
63 - }
64 - setState(State.LOADED)
65 - } catch (error) {
66 - log.error(error)
67 - setState(State.ERROR)
68 - }
69 - }
70 -
71 - useEffect(() => {
72 - getCurrentUser()
73 - }, [])
74 -
75 - const onUpdateUser = (user: CurrentUser | null) => {
76 - setUser(user)
77 - }
78 -
79 - const didLogout = () => {
80 - db.currentUser.clear().then(res => {
81 - window.location.href = '/'
82 - })
83 - }
84 -
85 - if (state === State.LOADING) {
86 - return <Container className='d-flex justify-content-center align-items-center' style={{ height: '100vh' }}>
87 - <LoadingView />
88 - </Container>
89 - }
90 -
91 - return (
92 - <IntlProvider locale={i18nConfig.language} messages={i18nConfig.messages}>
93 - <UserContext.Provider value={{ user, updateUser: onUpdateUser }}>
94 - <BrowserRouter>
95 - <Routes>
96 - <Route path='/' element={<Main />} >
97 - <Route index element={<Home />} />
98 - <Route path='about' element={<About />} />
99 - <Route path='contact' element={<Contact />} />
100 - <Route path='login' element={<Login />} />
101 - <Route path='logout' element={<Logout didLogout={didLogout} />} />
102 - <Route path='song' element={<Song />} >
103 - <Route path=':audioId' element={<SongDetailView />} />
104 - </Route>
105 - <Route path='split' element={<Split />} >
106 - <Route path=':providerId' element={<SplitDetailView />} />
107 - </Route>
108 - <Route path='karaoke' element={<KaraokeView />} >
109 - <Route path=':audioFileId' element={<KaraokePlayerView />} />
110 - <Route path=':audioFileId/lyrics' element={<KaraokeLyricsView />} />
111 - </Route>
112 - <Route path='player' element={<PlayerView />} >
113 - <Route path=':providerId' element={<PlayerAudio />} />
114 - </Route>
115 - <Route path='search' element={<Search />} />
116 - </Route>
117 - <Route path='/:usernameOrId' element={<Main />} >
118 - <Route index element={<ProfileRootView />} />
119 - <Route element={<ProfileNetworksRootView />}>
120 - <Route path='followers' element={<ProfileFollowersRootView />} />
121 - <Route path='following' element={<ProfileFollowingRootView />} />
122 - </Route>
123 - </Route>
124 - </Routes>
125 - </BrowserRouter>
126 - </UserContext.Provider>
127 - </IntlProvider >
128 - );
129 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/karaoke/KaraokeLyricsView.tsx deleted
-220
@@ -1,220 +0,0 @@
1 -import axios from "axios";
2 -import { Component } from "react";
3 -import { Alert, Button, Col, Container, Row } from "react-bootstrap";
4 -import { BsPlusCircle } from "react-icons/bs";
5 -import { Navigate, useParams } from "react-router-dom";
6 -import { CurrentUser } from "../../lib/db";
7 -import LyricSynch, { compare } from "../../models/lyricSynches";
8 -import { useCurrentUser } from "./KaraokeView";
9 -import LyricSynchRow from "./LyricSynchRow";
10 -
11 -export default function KaraokeLyricsView() {
12 - const { audioFileId } = useParams()
13 - const { currentUser } = useCurrentUser()
14 -
15 - if (!audioFileId) {
16 - return <p>Something wrong...</p>
17 - }
18 - return <KaraokeLyrics audioFileId={audioFileId} currentUser={currentUser} />
19 -}
20 -
21 -interface KaraokeLyricsProps {
22 - audioFileId: string
23 - currentUser: CurrentUser
24 -}
25 -
26 -interface KaraokeLyricsState {
27 - lyrics: LyricSynch[]
28 - errorMessage: string
29 - processing: boolean
30 - validationMessage: string
31 - shouldNavigateToPlayer: boolean
32 -}
33 -
34 -export class KaraokeLyrics extends Component<KaraokeLyricsProps, KaraokeLyricsState> {
35 -
36 - constructor(props: KaraokeLyricsProps) {
37 - super(props)
38 - this.state = {
39 - lyrics: [],
40 - errorMessage: '',
41 - processing: false,
42 - validationMessage: '',
43 - shouldNavigateToPlayer: false
44 - }
45 - }
46 -
47 - componentDidMount() {
48 - axios
49 - .get(`/lyric_synches/${this.props.audioFileId}`, {
50 - headers: {
51 - 'Authorization': this.props.currentUser.accessToken
52 - }
53 - })
54 - .then(res => {
55 - const lyricSynches: LyricSynch[] = res.data.lyric_synches
56 - //console.log(lyricSynches)
57 - this.setState({
58 - lyrics: lyricSynches
59 - })
60 - })
61 - .catch(error => {
62 - //console.log(error)
63 - this.setState({ errorMessage: 'Something error with this file.' })
64 - });
65 - }
66 -
67 - addLine() {
68 - const { lyrics } = this.state
69 - lyrics.push({ id: -1, lyric: '', time: '00:00' })
70 - this.setState({ lyrics })
71 - ////console.log(this.state)
72 - }
73 -
74 - removeLine(index: number) {
75 - const { lyrics } = this.state
76 - delete lyrics[index]
77 - this.setState({ lyrics })
78 - //console.log(this.state)
79 - }
80 -
81 - updateTime(index: number, time: string) {
82 - const { lyrics } = this.state
83 - lyrics[index].time = time
84 - this.setState({ lyrics })
85 - //console.log('updateTime', this.state)
86 - }
87 -
88 - updateLyric(index: number, lyric: string) {
89 - const { lyrics } = this.state
90 - lyrics[index].lyric = lyric
91 - this.setState({ lyrics })
92 - //console.log('updateLyric', this.state)
93 - }
94 -
95 - submit() {
96 - //console.log(this.state)
97 - this.setState({ processing: true })
98 - const isEmpty = (element: LyricSynch) => element.lyric.length === 0
99 - if (this.state.lyrics.some(isEmpty)) {
100 - this.setState({
101 - processing: false,
102 - validationMessage: 'Lyrics cannot be empty.'
103 - })
104 - return
105 - }
106 - const isNewLyric = (element: LyricSynch) => element.id < 0
107 - const isExistingLyric = (element: LyricSynch) => element.id > 0
108 - const newLyrics = this.state.lyrics.filter(isNewLyric)
109 - const existingLyrics = this.state.lyrics.filter(isExistingLyric)
110 -
111 - let requests = []
112 - // Find lyrics to update
113 - if (existingLyrics.length > 0) {
114 - requests.push(
115 - axios
116 - .put(`/lyric_synches/${this.props.audioFileId}`, {
117 - payload: existingLyrics
118 - }, {
119 - headers: {
120 - 'Authorization': this.props.currentUser.accessToken
121 - }
122 - })
123 - )
124 - }
125 -
126 - // Find lyrics to add
127 - if (newLyrics.length > 0) {
128 - requests.push(
129 - axios
130 - .post(`/lyric_synches/${this.props.audioFileId}`, {
131 - payload: newLyrics
132 - }, {
133 - headers: {
134 - 'Authorization': this.props.currentUser.accessToken
135 - }
136 - })
137 - )
138 - }
139 -
140 - axios
141 - .all(requests)
142 - .then(axios.spread((data1, data2) => {
143 - // output of req.
144 - //console.log('data1', data1, 'data2', data2)
145 - if ((data1 && data1.data.code === 200) || (data2 && data2.data.code === 200)) {
146 - this.setState({ shouldNavigateToPlayer: true })
147 - } else {
148 - this.setState({
149 - processing: false,
150 - validationMessage: 'Request error.'
151 - })
152 - }
153 - }))
154 - .catch(error => {
155 - //console.log(error)
156 - this.setState({
157 - processing: false,
158 - validationMessage: 'Request error.'
159 - })
160 - })
161 - }
162 -
163 - buttonTitle(): string {
164 - return this.state.processing ? 'Processing...' : 'Submit'
165 - }
166 -
167 - render() {
168 -
169 - const { lyrics, processing, errorMessage, validationMessage, shouldNavigateToPlayer } = this.state
170 -
171 - if (shouldNavigateToPlayer) {
172 - return <Navigate to={`/karaoke/${this.props.audioFileId}`} />
173 - }
174 -
175 - if (errorMessage.length > 0) {
176 - return <p>Cannot load file.</p>
177 - }
178 -
179 - let validationMessageView
180 - if (validationMessage.length > 0) {
181 - validationMessageView = <Alert variant="danger" onClose={() => this.setState({ validationMessage: ''})} dismissible><p>{validationMessage}</p></Alert>
182 - }
183 -
184 - return <Container>
185 - {validationMessageView}
186 - <Row>
187 - <Col>
188 - {lyrics.sort(compare).map((lyric, index) => (
189 - <LyricSynchRow
190 - lyricSynch={lyric}
191 - key={index}
192 - index={index}
193 - onRemove={ () => { this.removeLine(index) }}
194 - onTimeChange={ (time: string) => { this.updateTime(index, time) } }
195 - onLyricChange={ (lyric: string) => { this.updateLyric(index, lyric) } }
196 - processing={processing}
197 - />
198 - ))}
199 - </Col>
200 - </Row>
201 - <Row className="mb-4">
202 - <Col>
203 - <Button onClick={() => this.addLine()} >
204 - <BsPlusCircle color={`white`} />
205 - </Button>
206 - </Col>
207 - </Row>
208 - <Row>
209 - <Col>
210 - <Button
211 - variant="primary"
212 - onClick={() => this.submit() }
213 - disabled={processing} >
214 - {this.buttonTitle()}
215 - </Button>
216 - </Col>
217 - </Row>
218 - </Container>
219 - }
220 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/karaoke/KaraokePlayer.tsx deleted
-473
@@ -1,473 +0,0 @@
1 -import axios from "axios";
2 -import { Component } from "react";
3 -import { Spinner, Col, Row, Button } from "react-bootstrap";
4 -import { IconContext } from "react-icons";
5 -import { GiPauseButton, GiPlayButton, GiMicrophone } from "react-icons/gi";
6 -import { FormattedMessage } from "react-intl";
7 -import YouTube, { Options } from "react-youtube";
8 -import { Player, Channel } from "tone";
9 -import { AudioFiles, CurrentUser, db } from "../../lib/db";
10 -import { AudioFile, Status } from "../player/models/AudioFile";
11 -import { Mode } from "../player/models/Mode";
12 -import { Result } from "../player/models/Result";
13 -import { YouTubeEventTarget } from "../player/models/YouTubeEventTarget";
14 -import { YouTubePlayerState } from "../player/models/YouTubePlayerState";
15 -import ActionCable from "actioncable";
16 -import LyricSynch, { compare } from "../../models/lyricSynches";
17 -import { LyricRow } from "./LyricSynchRow";
18 -import { Link } from "react-router-dom";
19 -import { BsChatLeftTextFill } from "react-icons/bs";
20 -import { invoke } from '@tauri-apps/api'
21 -import { LoadingView } from "../templates/LoadingView";
22 -import isTauri from "../../lib/isTauri";
23 -
24 -interface KaraokePlayerProps {
25 - audioFileId: string
26 - currentUser: CurrentUser
27 -}
28 -
29 -interface KaraokePlayerState {
30 - errorMessage: string
31 - audioFile: AudioFile | null
32 - playerTime: number
33 - isPlayerReady: boolean
34 - isYoutubePlayerReady: boolean
35 - isPlaying: boolean
36 - vocalAudioOn: boolean
37 - guitarAudioOn: boolean
38 - bassAudioOn: boolean
39 - drumsAudioOn: boolean
40 - pianoAudioOn: boolean
41 - lyricSynches: LyricSynch[]
42 -}
43 -
44 -class KaraokePlayer extends Component<KaraokePlayerProps, KaraokePlayerState> {
45 -
46 -
47 - vocalPlayer: Player | null = null;
48 - otherPlayer: Player | null = null;
49 - bassPlayer: Player | null = null;
50 - drumsPlayer: Player | null = null;
51 - pianoPlayer: Player | null = null;
52 -
53 - youTubeEventTarget: YouTubeEventTarget | null = null
54 - updateYouTubePlayerStatusIntervalId: any | null = null
55 -
56 - debugChannel: any
57 - cableApp = ActionCable.createConsumer(`wss://${process.env.REACT_APP_SPLITFIRE_API_HOST}/cable`)
58 -
59 - constructor(props: KaraokePlayerProps) {
60 - super(props);
61 - this.state = {
62 - errorMessage: '',
63 - audioFile: null,
64 - playerTime: 0,
65 - isPlayerReady: false,
66 - isYoutubePlayerReady: false,
67 - isPlaying: false,
68 - vocalAudioOn: true,
69 - guitarAudioOn: true,
70 - bassAudioOn: true,
71 - drumsAudioOn: true,
72 - pianoAudioOn: true,
73 - lyricSynches: []
74 - }
75 -
76 - const received = (data: AudioFile) => {
77 - this._updateProgress(data)
78 - }
79 -
80 - const conected = () => { }
81 -
82 - this.debugChannel = this.cableApp.subscriptions.create({
83 - channel: 'FileProcessingChannel',
84 - audioFileId: this.state.audioFile?.id
85 - }, {
86 - conected,
87 - received
88 - })
89 - }
90 -
91 - componentDidMount() {
92 - const audioFileId = this.props.audioFileId;
93 - axios.get(`/splitfire/${audioFileId}`, {
94 - headers: {
95 - 'Authorization': this.props.currentUser.accessToken
96 - }
97 - })
98 - .then(res => {
99 - //console.log("Get results", res);
100 - const audioFile = res.data.audio_file;
101 - this.setState({ audioFile });
102 - // console.log(this.state);
103 - this._downloadAudioFilesIfNeeded();
104 - })
105 - .catch(error => {
106 - //console.log(error)
107 - this.setState({ errorMessage: 'Something error with this file.' })
108 - });
109 -
110 - axios.get(`/lyric_synches/${audioFileId}`, {
111 - headers: {
112 - 'Authorization': this.props.currentUser.accessToken
113 - }
114 - })
115 - .then(res => {
116 - const lyricSynches = res.data.lyric_synches
117 - this.setState({ lyricSynches })
118 - })
119 - .catch(error => {
120 - //console.log(error)
121 - this.setState({ errorMessage: 'Something error with this file.' })
122 - });
123 - }
124 -
125 - componentWillUnmount() {
126 - this.setState({ isPlaying: false })
127 - this.vocalPlayer?.stop()
128 - this.bassPlayer?.stop()
129 - this.drumsPlayer?.stop()
130 - this.pianoPlayer?.stop()
131 - this.otherPlayer?.stop()
132 -
133 - this.cableApp.disconnect()
134 - }
135 -
136 - _openLyricsEditor(audioFileId: number) {
137 - invoke('open_lyrics_editor', { path: `/karaoke/${audioFileId}/lyrics` })
138 - .then((response) => console.log(response))
139 - .catch((error) => console.error(error))
140 - }
141 -
142 - _updateProgress(data: AudioFile) {
143 - //console.log(data)
144 -
145 - this.setState({ audioFile: data })
146 -
147 - if (this.state.audioFile?.status === Status.DONE)
148 - window.location.reload();
149 - }
150 -
151 - _downloadAudioFilesIfNeeded() {
152 -
153 - if (this.state.audioFile?.status !== Status.DONE)
154 - return
155 -
156 - this._prepareAudio(Mode.Vocal);
157 - this._prepareAudio(Mode.Bass);
158 - this._prepareAudio(Mode.Drum);
159 - this._prepareAudio(Mode.Guitar);
160 - this._prepareAudio(Mode.Keyboard);
161 - }
162 -
163 - async _downloadFile(type: Mode) {
164 - // //console.log('_downloadFile', type);
165 - let audioFile: Result | undefined;
166 - switch (type) {
167 - case Mode.Vocal:
168 - audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('vocals') });
169 - break;
170 - case Mode.Bass:
171 - audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('bass') });
172 - break;
173 - case Mode.Drum:
174 - audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('drums') });
175 - break;
176 - case Mode.Guitar:
177 - audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('other') });
178 - break;
179 - case Mode.Keyboard:
180 - audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('piano') });
181 - break;
182 - }
183 -
184 - const audioFileId = this.props.audioFileId;
185 - // console.log("Download url", audioFile?.source_file);
186 - axios({
187 - url: audioFile?.source_file,
188 - method: 'GET',
189 - responseType: 'blob',
190 - }).then((response) => {
191 - const file = new Blob([response.data], { type: 'audio/mp3' });
192 - const item: AudioFiles = {
193 - audioFileId, type: type, file
194 - }
195 - db.audioFiles.add(item);
196 - // console.log('File Downloaded', file);
197 - this._setAudioSrc(type, file);
198 - }).catch(error => {
199 - // console.log(error);
200 - });
201 - }
202 -
203 - async _prepareAudio(type: Mode) {
204 - const audioFileId = this.props.audioFileId;
205 - db.audioFiles
206 - .get({ audioFileId: audioFileId, type: type })
207 - .then((record: AudioFiles | undefined) => {
208 - if (record?.file === undefined) {
209 - this._downloadFile(type);
210 - return
211 - }
212 - this._setAudioSrc(type, record.file)
213 - })
214 - .catch((onrejected: any) => {
215 - // console.log("Rejected", onrejected);
216 - });
217 - }
218 -
219 - _setAudioSrc(type: Mode, file: Blob) {
220 - // Get window.URL object
221 - const URL = window.URL || window.webkitURL;
222 - // Create and revoke ObjectURL
223 - const audioFileURL = URL.createObjectURL(file);
224 - const channel = new Channel().toDestination();
225 -
226 - switch (type) {
227 - case Mode.Vocal:
228 - this.vocalPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
229 - this.vocalPlayer.connect(channel);
230 - break;
231 - case Mode.Bass:
232 - this.bassPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
233 - this.bassPlayer.connect(channel);
234 - break;
235 - case Mode.Drum:
236 - this.drumsPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
237 - this.drumsPlayer.connect(channel);
238 - break;
239 - case Mode.Guitar:
240 - this.otherPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
241 - this.otherPlayer.connect(channel);
242 - break;
243 - case Mode.Keyboard:
244 - this.pianoPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
245 - this.pianoPlayer.connect(channel);
246 - break;
247 - }
248 - }
249 -
250 - _updatePlayerStatus: (() => void) = () => {
251 - if (
252 - this.vocalPlayer?.loaded &&
253 - this.drumsPlayer?.loaded &&
254 - this.otherPlayer?.loaded &&
255 - this.bassPlayer?.loaded &&
256 - this.pianoPlayer?.loaded
257 - ) {
258 - this.setState({ isPlayerReady: true });
259 - }
260 - }
261 -
262 - togglePlayAudio() {
263 - this.setState({ isPlaying: !this.state.isPlaying })
264 - if (this.state.isPlaying) {
265 - this.vocalPlayer?.stop()
266 - this.bassPlayer?.stop()
267 - this.drumsPlayer?.stop()
268 - this.pianoPlayer?.stop()
269 - this.otherPlayer?.stop()
270 - this.youTubeEventTarget?.pauseVideo()
271 - } else {
272 - this.youTubeEventTarget?.seekTo(0)
273 - this.youTubeEventTarget?.playVideo()
274 - this.vocalPlayer?.start();
275 - this.bassPlayer?.start();
276 - this.drumsPlayer?.start();
277 - this.pianoPlayer?.start();
278 - this.otherPlayer?.start();
279 - }
280 - }
281 -
282 - setToggleInstrument(mode: Mode) {
283 - switch (mode) {
284 - case Mode.Vocal:
285 - if (!this.vocalPlayer) return;
286 - this.setState({ vocalAudioOn: !this.state.vocalAudioOn });
287 - this.vocalPlayer.mute = this.state.vocalAudioOn;
288 - break;
289 - case Mode.Bass:
290 - if (!this.bassPlayer) return;
291 - this.setState({ bassAudioOn: !this.state.bassAudioOn });
292 - this.bassPlayer.mute = this.state.bassAudioOn;
293 - break;
294 - case Mode.Guitar:
295 - if (!this.otherPlayer) return;
296 - this.setState({ guitarAudioOn: !this.state.guitarAudioOn });
297 - this.otherPlayer.mute = this.state.guitarAudioOn;
298 - break;
299 - case Mode.Keyboard:
300 - if (!this.pianoPlayer) return;
301 - this.setState({ pianoAudioOn: !this.state.pianoAudioOn });
302 - this.pianoPlayer.mute = this.state.pianoAudioOn;;
303 - break;
304 - case Mode.Drum:
305 - if (!this.drumsPlayer) return;
306 - this.setState({ drumsAudioOn: !this.state.drumsAudioOn });
307 - this.drumsPlayer.mute = this.state.drumsAudioOn;
308 - break;
309 - }
310 - }
311 -
312 - render() {
313 -
314 - const { audioFile, errorMessage, isPlayerReady, isYoutubePlayerReady, isPlaying, lyricSynches } = this.state
315 -
316 - if (errorMessage.length > 0) {
317 - return (
318 - <p>Something wrong with this file.</p>
319 - )
320 - }
321 -
322 - if (audioFile?.status === Status.DOWNLOADING || audioFile?.status === Status.SPLITTING) {
323 - return <p>Processing file: {audioFile.progress}% </p>
324 - }
325 -
326 - if (isPlayerReady && audioFile) {
327 - const opts: Options = {
328 - width: '100%',
329 - playerVars: {
330 - // https://developers.google.com/youtube/player_parameters
331 - autoplay: 0,
332 - mute: 1,
333 - controls: 0,
334 - rel: 0,
335 - showinfo: 0
336 - },
337 - }
338 -
339 - let togglePlayButton = <div>
340 - <p>
341 - <FormattedMessage id="player.loadingYoutube"
342 - defaultMessage="Generating video..."
343 - description="Loading message" />
344 - </p>
345 - <Spinner animation='grow' variant="danger"></Spinner>
346 - </div>
347 -
348 - let lyricsView = <p>Loading lyrics...</p>
349 - let buttonViews = <Col><p>Loading mixer...</p></Col>
350 -
351 - if (isYoutubePlayerReady) {
352 - let button
353 - if (isPlaying) {
354 - button = <Col>
355 - <GiPauseButton
356 - color={isPlaying ? `white` : `red`}
357 - onClick={() => this.togglePlayAudio()} />
358 - </Col>
359 - } else {
360 - button = <Col>
361 - <GiPlayButton
362 - color={isPlaying ? `white` : `red`}
363 - onClick={() => this.togglePlayAudio()} />
364 - </Col>
365 - }
366 -
367 - togglePlayButton = <Col>{button}</Col>
368 - let buttonLyric
369 - if (isTauri()) {
370 - buttonLyric = <Button variant="secondary">
371 - <BsChatLeftTextFill color="white" onClick={() => this._openLyricsEditor(audioFile.id)} />
372 - </Button>
373 -
374 - } else {
375 - buttonLyric = <Link to={`/karaoke/${audioFile.id}/lyrics`} title="Add or edit lyrics" target="_blank" rel="noopener noreferrer" >
376 - <BsChatLeftTextFill color="white" />
377 - </Link>
378 - }
379 - buttonViews = <>
380 - <Col xs={6}>
381 - <GiMicrophone
382 - color={this.state.vocalAudioOn ? `white` : `red`}
383 - onClick={() => this.setToggleInstrument(Mode.Vocal)} />
384 - </Col>
385 - <Col xs={6}>
386 - {buttonLyric}
387 - </Col>
388 - </>
389 -
390 - if (lyricSynches.length > 0) {
391 - lyricsView = <>
392 - {lyricSynches.sort(compare).map((lyric, index) => (
393 - <Row key={index} >
394 - <Col>
395 - <LyricRow lyric={lyric.lyric} />
396 - </Col>
397 - </Row>
398 - ))}
399 - </>
400 - } else {
401 - lyricsView = <p>No lyrics available yet. Click above icon to add.</p>
402 - }
403 - }
404 -
405 - return (
406 - <IconContext.Provider value={{ size: "2em", color: "white", className: "global-class-name" }}>
407 - <Row className="mb-3 mt-3">
408 - <Row>
409 - <Col>
410 - <h1>{this.state.audioFile?.name}</h1>
411 - </Col>
412 - </Row>
413 - </Row>
414 - <Row className="mb-3 mt-3">
415 - {togglePlayButton}
416 - </Row>
417 - <Row className="mb-3 mt-3">
418 - {buttonViews}
419 - </Row>
420 - {lyricsView}
421 - <Row className="mt-3">
422 - <Col style={{ display: isYoutubePlayerReady ? 'block' : 'none', pointerEvents: 'none' }}>
423 - <YouTube
424 - videoId={this.state.audioFile?.youtube_video_id}
425 - opts={opts}
426 - onReady={this._onReady.bind(this)}
427 - onStateChange={this._onStateChange.bind(this)}
428 - />
429 - </Col>
430 - </Row>
431 - </IconContext.Provider>
432 - )
433 - } else {
434 - return <LoadingView />
435 - }
436 - }
437 -
438 - _onReady(event: any) {
439 - this.youTubeEventTarget = event.target
440 - this.youTubeEventTarget?.playVideo()
441 - this.updateYouTubePlayerStatusIntervalId = setInterval(() => {
442 - if (!this.state.isYoutubePlayerReady) {
443 - this._updateYouTubePlayerStatusIfNeeded()
444 - }
445 - }, 2000)
446 - }
447 -
448 - _onStateChange(event: any) {
449 - if (!this.youTubeEventTarget) return
450 -
451 - if (this.youTubeEventTarget.getPlayerState() === YouTubePlayerState.ended) {
452 - this.setState({ isPlaying: false })
453 - }
454 - }
455 -
456 - _updateYouTubePlayerStatusIfNeeded() {
457 -
458 - if (this.state.isYoutubePlayerReady) return
459 - if (!this.youTubeEventTarget) return
460 -
461 - // Let's say 5 seconds buffer is enough.
462 - if (this.youTubeEventTarget?.getCurrentTime() > 5) {
463 - this.setState({ isYoutubePlayerReady: true })
464 - this.youTubeEventTarget?.pauseVideo()
465 -
466 - if (this.updateYouTubePlayerStatusIntervalId)
467 - clearInterval(this.updateYouTubePlayerStatusIntervalId)
468 - }
469 - }
470 -
471 -}
472 -
473 -export default KaraokePlayer
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/karaoke/KaraokePlayerView.tsx deleted
-15
@@ -1,15 +0,0 @@
1 -import { useParams } from "react-router-dom"
2 -import KaraokePlayer from "./KaraokePlayer"
3 -import { useCurrentUser } from "./KaraokeView"
4 -
5 -export function KaraokePlayerView() {
6 -
7 - const { audioFileId } = useParams()
8 - const { currentUser } = useCurrentUser()
9 -
10 - if (currentUser && audioFileId) {
11 - return <KaraokePlayer audioFileId={audioFileId} currentUser={currentUser} />
12 - }
13 -
14 - return <p>Something wrong</p>
15 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/karaoke/KaraokeView.tsx deleted
-55
@@ -1,55 +0,0 @@
1 -import { useState, useEffect } from 'react'
2 -import { Spinner } from 'react-bootstrap'
3 -import { FormattedMessage } from 'react-intl'
4 -import { Navigate, Outlet, useOutletContext, useParams } from 'react-router-dom'
5 -import { db, CurrentUserType, CurrentUser } from '../../lib/db'
6 -
7 -type ContextType = { currentUser: CurrentUser };
8 -
9 -export function useCurrentUser() {
10 - return useOutletContext<ContextType>()
11 -}
12 -
13 -export default function KaraokeView() {
14 -
15 - const { audioFileId } = useParams()
16 - const [currentUser, setCurrentUser] = useState<CurrentUser | undefined>(undefined)
17 - const [isRequesting, setisRequesting] = useState(true)
18 -
19 -
20 - useEffect(() => {
21 - db.currentUser.get({ type: CurrentUserType.MAIN }).then((res: CurrentUser | undefined ) => {
22 - if (res) {
23 - setCurrentUser(res)
24 - } else {
25 - setCurrentUser(undefined)
26 - }
27 - setisRequesting(false)
28 - }).catch( error => {
29 - //console.log(error)
30 - setCurrentUser(undefined)
31 - setisRequesting(false)
32 - })
33 - })
34 -
35 - if (isRequesting) {
36 - return <div>
37 - <p>
38 - <FormattedMessage id="home.loading"
39 - defaultMessage="Loading SplitFire AI..."
40 - description="Loading message" />
41 - </p>
42 - <Spinner animation='grow' variant="danger"></Spinner>
43 - </div>
44 - }
45 -
46 - if (!currentUser) {
47 - return <Navigate to={'/login'} />
48 - }
49 -
50 - if (currentUser && audioFileId) {
51 - return <Outlet context={{ currentUser }}/>
52 - }
53 -
54 - return <p>Something wrong</p>
55 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/karaoke/LyricSynchRow.tsx deleted
-66
@@ -1,66 +0,0 @@
1 -import { useState } from "react";
2 -import { Button, Col, Form, Row } from "react-bootstrap";
3 -import { BsFillTrashFill } from "react-icons/bs";
4 -import LyricSynch from "../../models/lyricSynches";
5 -
6 -interface LyricSynchRowProps {
7 - lyricSynch: LyricSynch
8 - index: number
9 - onLyricChange: (lyric: string) => void
10 - onTimeChange: (time: string) => void
11 - onRemove: () => void
12 - processing: boolean
13 -}
14 -
15 -export default function LyricSynchRow(props: LyricSynchRowProps) {
16 -
17 - const [lyric, setLyric] = useState(props.lyricSynch.lyric)
18 - const [time, setTime] = useState(props.lyricSynch.time)
19 -
20 - let removeButton
21 - if (props.lyricSynch.id < 0) {
22 - removeButton = <Button onClick={() => props.onRemove() } disabled={props.processing} >
23 - <BsFillTrashFill color="white" />
24 - </Button>
25 - }
26 -
27 - return <Row className="mb-3">
28 - <Col xs={3}>
29 - <Form.Control
30 - type="text"
31 - value={time}
32 - onChange={(e) => {
33 - setTime(e.currentTarget.value)
34 - props.onTimeChange(e.currentTarget.value)
35 - }}
36 - disabled={props.processing}
37 - />
38 - </Col>
39 - <Col xs={8}>
40 - <Form.Control
41 - as="textarea"
42 - rows={2}
43 - value={lyric}
44 - onChange={(e) => {
45 - setLyric(e.currentTarget.value)
46 - props.onLyricChange(e.currentTarget.value)
47 - }}
48 - disabled={props.processing}
49 - />
50 - </Col>
51 - <Col xs={1}>
52 - {removeButton}
53 - </Col>
54 - </Row>
55 -}
56 -interface LyricRowProps {
57 - lyric: String
58 -}
59 -export function LyricRow(props: LyricRowProps) {
60 -
61 - return <Row>
62 - <Col>
63 - {props.lyric}
64 - </Col>
65 - </Row>
66 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/About.tsx deleted
-15
@@ -1,15 +0,0 @@
1 -import { Col } from "react-bootstrap";
2 -import { FormattedMessage } from "react-intl";
3 -
4 -export default function About() {
5 - return (
6 - <Col>
7 - <h1>SplitFire Ai</h1>
8 - <p>
9 - <FormattedMessage id="splitfire.whatIs"
10 - defaultMessage="I'm an artificial intelligence software who will split your favorite music to its separate components."
11 - description="Explanation message" />
12 - </p>
13 - </Col>
14 - )
15 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/AutoCompleteView.tsx deleted
-22
@@ -1,22 +0,0 @@
1 -import { Container, Row, Button } from "react-bootstrap"
2 -import { Link } from "react-router-dom"
3 -import { SearchAutocompleteResult } from "./Home"
4 -
5 -interface Prop {
6 - results: SearchAutocompleteResult[]
7 -}
8 -
9 -export default function AutoCompleteView(props: Prop) {
10 - if (props.results.length === 0) {
11 - return <></>
12 - }
13 - return (
14 - <Container className="text-start">{props.results.map((result, index) =>
15 - <Row key={index}>
16 - <Link to={{ pathname: `/song/${result.id}` }}>
17 - <Button className="mb-2">{result.name}</Button>
18 - </Link>
19 - </Row>
20 - )}</Container>
21 - )
22 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/Contact.tsx deleted
-33
@@ -1,33 +0,0 @@
1 -import { Col, Container, Row } from 'react-bootstrap'
2 -import { BsFacebook, BsInstagram, BsTwitter, BsYoutube } from 'react-icons/bs'
3 -import { FormattedMessage } from 'react-intl'
4 -
5 -export default function Contact() {
6 - return (
7 - <Container>
8 - <Row>
9 - <Col>
10 - <p>
11 - <FormattedMessage id="splitfire.whatIs"
12 - defaultMessage="Connect with us."
13 - description="Explanation message" />
14 - </p>
15 - <ul className='list-unstyled'>
16 - <li>
17 - <BsInstagram /> <a href='https://www.instagram.com/splitfireAI/'>SplitfireAI</a>
18 - </li>
19 - <li>
20 - <BsTwitter /> <a href='https://www.twitter.com/splitfireAI/' >SplitfireAI</a>
21 - </li>
22 - <li>
23 - <BsYoutube /> <a href='https://www.youtube.com/c/SplitFireAI/' >SplitfireAI</a>
24 - </li>
25 - <li>
26 - <BsFacebook /> <a href='https://www.facebook.com/SplitfireAI'>SplitfireAI</a>
27 - </li>
28 - </ul>
29 - </Col>
30 - </Row>
31 - </Container>
32 - )
33 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/Home.tsx deleted
-51
@@ -1,51 +0,0 @@
1 -import { Component, ReactNode } from "react"
2 -import { Container, Row } from "react-bootstrap";
3 -import RandomView from "./home/Random";
4 -import SearchView from "./home/SearchFormView";
5 -import TopVotesView from "./home/TopVotesView";
6 -import ReadyToPlayView from "./home/ReadyToPlay";
7 -
8 -type HomeProps = {}
9 -
10 -type HomeState = {}
11 -
12 -export interface SearchAutocompleteResult {
13 - name: string
14 - id: string
15 - title: string
16 -}
17 -
18 -export interface SearchResponse {
19 - code: number
20 - message: string
21 - results: SearchAutocompleteResult[]
22 -}
23 -
24 -class Home extends Component<HomeProps, HomeState> {
25 -
26 - constructor(props: HomeProps) {
27 - super(props)
28 - this.state = {}
29 - }
30 -
31 - render(): ReactNode {
32 - return (
33 - <Container className="mb-5">
34 - <Row className="mb-3">
35 - <SearchView />
36 - </Row>
37 - <Row className="mb-3">
38 - <ReadyToPlayView />
39 - </Row>
40 - <Row className="mb-3">
41 - <RandomView />
42 - </Row>
43 - <Row className="mb-3">
44 - <TopVotesView />
45 - </Row>
46 - </Container >
47 - )
48 - }
49 -}
50 -
51 -export default Home
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/Search.tsx deleted
-74
@@ -1,74 +0,0 @@
1 -import axios from "axios";
2 -import { useEffect, useState } from "react";
3 -import { Col, Container, Row, Spinner } from "react-bootstrap";
4 -import { useSearchParams } from "react-router-dom";
5 -import { SearchResponse, SearchAutocompleteResult } from "./Home";
6 -import { SearchResultView } from "./SearchResultView";
7 -import { useLogger } from "../../../../lib/logger";
8 -
9 -enum State {
10 - IDLE,
11 - LOADED,
12 - ERROR
13 -}
14 -
15 -export default function Search() {
16 -
17 - const log = useLogger('Search')
18 - const [searchParams] = useSearchParams();
19 - const [state, setState] = useState(State.IDLE)
20 - const [searchResults, setSearchResults] = useState<SearchAutocompleteResult[]>([])
21 -
22 - const q = searchParams.get('q')
23 -
24 - useEffect(() => {
25 - axios
26 - .get(`/search?q=${q}&ac=false&client=sf-desktop`)
27 - .then(res => {
28 - log.debug(res)
29 - let response: SearchResponse = res.data
30 - if (response.code === 200) {
31 - setState(State.LOADED)
32 - setSearchResults(response.results)
33 - } else {
34 - setState(State.ERROR)
35 - }
36 - })
37 - .catch(error => {
38 - log.error(error)
39 - setState(State.ERROR)
40 - })
41 -
42 - })
43 -
44 - let resultView: JSX.Element = <div>
45 - <Spinner animation='grow' variant="danger"></Spinner>
46 - </div>
47 - let title = `Searching '${q}'...`
48 -
49 - if (state === State.ERROR) {
50 - resultView = <div>
51 - <p>Something went wrong.</p>
52 - </div>
53 - }
54 -
55 - if (state === State.LOADED) {
56 - resultView = <SearchResultView results={searchResults} />
57 - title = `Results for '${q}'.`
58 - }
59 -
60 - return (
61 - <Container className="mb-5">
62 - <Row className="mb-3">
63 - <Col>
64 - <h1>{title}</h1>
65 - </Col>
66 - </Row>
67 - <Row className="mb-3">
68 - <Col>
69 - {resultView}
70 - </Col>
71 - </Row>
72 - </Container>
73 - )
74 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/SearchResultView.tsx deleted
-31
@@ -1,31 +0,0 @@
1 -import { Row, Col, Container } from "react-bootstrap"
2 -import { SearchAutocompleteResult } from "./Home"
3 -
4 -interface Props {
5 - results: SearchAutocompleteResult[]
6 -}
7 -export function SearchResultView(props: Props) {
8 -
9 - let resultView
10 - if (props.results.length === 0) {
11 - resultView = <Row>
12 - <Col>No results...</Col>
13 - </Row>
14 - } else {
15 - resultView = <div>
16 - {
17 - props.results.map(result => (
18 - <Row>
19 - <Col>{result.title}</Col>
20 - </Row>
21 - ))
22 - }
23 - </div>
24 - }
25 -
26 - return (
27 - <Container>
28 - {resultView}
29 - </Container>
30 - )
31 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/esef/Split.tsx deleted
-12
@@ -1,12 +0,0 @@
1 -import { Col, Container, Row } from "react-bootstrap";
2 -import { Outlet } from "react-router-dom";
3 -
4 -export function Split() {
5 - return <Container>
6 - <Row>
7 - <Col>
8 - <Outlet />
9 - </Col>
10 - </Row>
11 - </Container>
12 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/esef/SplitFireView.tsx deleted
-81
@@ -1,81 +0,0 @@
1 -import axios from "axios"
2 -import { useState, useEffect } from "react"
3 -import { Spinner } from "react-bootstrap"
4 -import { useParams } from "react-router-dom"
5 -import { SongProvider } from "../../../models/SongResponse"
6 -import { SongProviderVote } from "../../../models/SongVotesDetailResponse"
7 -import SongVotesDetailView from "../song/SongVotesDetailView"
8 -
9 -enum State {
10 - LOADING,
11 - LOADED,
12 - ERROR
13 -}
14 -
15 -enum SFErrorCode {
16 - NOT_FOUND = 404,
17 - INTERNAL_SERVER_ERROR = 500
18 -}
19 -
20 -interface SFError {
21 - code: SFErrorCode,
22 - message: string
23 -}
24 -
25 -export enum HTTPStatusCode {
26 - OK = 200,
27 - NOT_FOUND = 404,
28 - INTERNAL_SERVER_ERROR = 500
29 -}
30 -
31 -export interface SongBridgeResponse {
32 - code: HTTPStatusCode,
33 - message: string,
34 - error: SFError | null,
35 - song_provider: SongProvider
36 - votes: SongProviderVote[]
37 -}
38 -
39 -export function SplitDetailView() {
40 -
41 - const { providerId } = useParams()
42 - const [state, setState] = useState(State.LOADING)
43 - const [songProvider, setSongProvider] = useState<SongProvider | null>(null)
44 - const [votes, setVotes] = useState<SongProviderVote[]>([])
45 -
46 - useEffect(() => {
47 - setState(State.LOADING)
48 - axios.get(`/song-bridge/${providerId}/detail`)
49 - .then(res => {
50 - const response: SongBridgeResponse = res.data
51 - if (response.code === HTTPStatusCode.OK) {
52 -
53 - console.log(response)
54 - setSongProvider(response.song_provider)
55 - setVotes(response.votes)
56 - setState(State.LOADED)
57 - } else {
58 - console.log('error')
59 - setState(State.ERROR)
60 - }
61 - })
62 - .catch(error => {
63 - console.log(error)
64 - setState(State.ERROR)
65 - })
66 - }, [providerId])
67 -
68 - if (state === State.LOADED && songProvider) {
69 - return <SongVotesDetailView songProvider={songProvider} votes={votes} />
70 - }
71 -
72 - if (state === State.LOADING) {
73 - return <div>
74 - <Spinner animation="border" role="status">
75 - <span className="visually-hidden">Loading...</span>
76 - </Spinner>
77 - </div>
78 - }
79 -
80 - return <p>Error</p>
81 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/home/Random.tsx deleted
-83
@@ -1,83 +0,0 @@
1 -import axios from "axios";
2 -import { useEffect, useState } from "react";
3 -import { Button, Carousel, Col, Row, Spinner } from "react-bootstrap";
4 -import { FormattedMessage } from "react-intl";
5 -import { Link } from "react-router-dom";
6 -import { SongProvider } from "../../../models/SongResponse";
7 -import { useLogger } from "../../../../../lib/logger";
8 -import ErrorView from "../../templates/Error";
9 -
10 -enum State {
11 - LOADING,
12 - LOADED,
13 - ERROR
14 -}
15 -
16 -export default function RandomView() {
17 -
18 - const log = useLogger('CarouselView')
19 - const [state, setState] = useState(State.LOADING)
20 - const [files, setFiles] = useState<SongProvider[]>([])
21 -
22 - useEffect(() => {
23 - setState(State.LOADING)
24 - axios.get(`/carousel`)
25 - .then(res => {
26 - const files = res.data.audio_files;
27 - log.debug(files)
28 - setFiles(files)
29 - setState(State.LOADED)
30 - })
31 - .catch(error => {
32 - log.error(error)
33 - setState(State.ERROR)
34 - })
35 - }, [])
36 -
37 - if (state === State.LOADING) {
38 - return <div>
39 - <p>
40 - <FormattedMessage id="home.loading"
41 - defaultMessage="Loading SplitFire AI..."
42 - description="Loading message" />
43 - </p>
44 - <Spinner animation='grow' variant="danger"></Spinner>
45 - </div>
46 - }
47 -
48 - if (state === State.ERROR) {
49 - return <ErrorView errorMessage="Something went wrong..." />
50 - }
51 -
52 - if (files.length === 0) {
53 - return <div>
54 - No content found
55 - </div>
56 - }
57 -
58 - return (
59 - <div>
60 - <Row>
61 - <Col xs={4}>
62 - <p className="h3">Random</p>
63 - </Col>
64 - </Row>
65 - <Carousel>
66 - {files.slice(0, 5).map(item => (
67 - <Carousel.Item key={item.id} style={{ padding: '20px' }}>
68 - <img
69 - className="d-block w-100"
70 - src={item.image_url}
71 - alt="First slide"
72 - />
73 - <Carousel.Caption>
74 - <Link to={{ pathname: `/split/${item.id}` }}>
75 - <Button variant="dark">{item.name}</Button>
76 - </Link>
77 - </Carousel.Caption>
78 - </Carousel.Item>
79 - ))}
80 - </Carousel>
81 - </div>
82 - )
83 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/home/ReadyToPlay.tsx deleted
-77
@@ -1,77 +0,0 @@
1 -import { useEffect, useState } from "react"
2 -import { SongProvider } from "../../../models/SongResponse"
3 -import axios from "axios"
4 -import { FormattedMessage } from "react-intl"
5 -import { Button, Carousel, Col, Row, Spinner } from "react-bootstrap"
6 -import { Link } from "react-router-dom"
7 -
8 -enum State {
9 - LOADING,
10 - LOADED,
11 - ERROR
12 -}
13 -
14 -export default function ReadyToPlayView() {
15 -
16 - const [state, setState] = useState(State.LOADING)
17 - const [files, setFiles] = useState<SongProvider[]>([])
18 -
19 - useEffect(() => {
20 - setState(State.LOADING)
21 - axios.get(`/ready-to-play`)
22 - .then(res => {
23 - const files = res.data.audio_files;
24 - console.log(files)
25 - setFiles(files)
26 - setState(State.LOADED)
27 - })
28 - .catch(error => {
29 - console.log(error)
30 - setState(State.ERROR)
31 - })
32 - }, [])
33 -
34 - if (state === State.LOADING) {
35 - return <div>
36 - <p>
37 - <FormattedMessage id="home.loading"
38 - defaultMessage="Loading top votes..."
39 - description="Loading top votes message" />
40 - </p>
41 - <Spinner animation='grow' variant="danger"></Spinner>
42 - </div>
43 - }
44 -
45 - if (state === State.ERROR) {
46 - return <></>
47 - }
48 -
49 - return (
50 - <div>
51 - <Row>
52 - <Col xs={4}>
53 - <p className="h3">Ready to Play!</p>
54 - </Col>
55 - </Row>
56 - <Row>
57 - <Carousel>
58 - {files.map((file) => (
59 - <Carousel.Item key={file.id} style={{ padding: '20px' }}>
60 - <img
61 - className="d-block w-100"
62 - src={file.image_url}
63 - alt="First slide"
64 - />
65 - <Carousel.Caption>
66 - <Link to={{ pathname: `/split/${file.id}` }}>
67 - <Button variant="dark">{file.name}</Button>
68 - </Link>
69 - </Carousel.Caption>
70 - </Carousel.Item>
71 - ))}
72 - </Carousel>
73 - </Row>
74 - </div>
75 - )
76 -
77 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/home/SearchFormView.tsx deleted
-102
@@ -1,102 +0,0 @@
1 -import debounce from "lodash.debounce";
2 -import { Form, Row, Col, Button, Alert, Spinner } from "react-bootstrap";
3 -import { SearchAutocompleteResult, SearchResponse } from "../Home";
4 -import { useState } from "react";
5 -import axios from "axios";
6 -import AutoCompleteView from "../AutoCompleteView";
7 -import { Navigate } from "react-router-dom";
8 -
9 -enum State {
10 - IDLE,
11 - TO_SEARCH,
12 - LOADING,
13 - LOADED,
14 - ERROR
15 -}
16 -
17 -export default function SearchView() {
18 -
19 - const [state, setState] = useState(State.IDLE)
20 - const [query, setQuery] = useState('')
21 - const [searchResults, setSearchResults] = useState<SearchAutocompleteResult[]>([])
22 - const [errorMessage, setErrorMessage] = useState('')
23 - const [validQ, setValidQ] = useState(false)
24 -
25 - const debouncedSearch = debounce(async (query: string) => {
26 - setState(State.LOADING)
27 - axios
28 - .get(`/search?q=${query}&ac=true&client=sf-desktop`)
29 - .then(res => {
30 - //console.log(res)
31 - let response: SearchResponse = res.data
32 -
33 - console.log(response)
34 - if (res.status === 200) {
35 - setSearchResults(response.results)
36 - setState(State.LOADED)
37 - } else {
38 - setState(State.ERROR)
39 - }
40 - })
41 - .catch(error => {
42 - console.log(error)
43 - setState(State.ERROR)
44 - })
45 - }, 300); // 300ms is the debounce delay
46 -
47 - const onChange = (query: string) => {
48 - if (query.length === 0) {
49 - setErrorMessage('Query is required')
50 - setValidQ(false)
51 - return
52 - }
53 - if (query.length < 3) {
54 - setErrorMessage('Query must be at least 3 characters')
55 - setValidQ(false)
56 - return
57 - }
58 - setValidQ(true)
59 - setQuery(query)
60 - debouncedSearch(query)
61 - }
62 -
63 - const onSubmit = () => {
64 - console.log('onSubmit')
65 - setState(State.TO_SEARCH)
66 - }
67 -
68 - if (state === State.TO_SEARCH) {
69 - return <Navigate to={`/search?q=${query}`} />
70 - }
71 -
72 - let validationText = <></>
73 - if (state === State.ERROR) {
74 - validationText = <Alert variant="danger" onClose={() => setErrorMessage('')} dismissible><p>{errorMessage}</p></Alert>
75 - }
76 -
77 - let autoCompleteView = <></>
78 - if (state === State.LOADING) {
79 - autoCompleteView = <Spinner animation='grow' variant="info"></Spinner>
80 - } else if (state === State.LOADED) {
81 - autoCompleteView = <AutoCompleteView results={searchResults} />
82 - }
83 -
84 - return (
85 - <Form onSubmit={(e) => e.preventDefault()}>
86 - <Row>
87 - <Col xs={8}>
88 - <Form.Control
89 - placeholder="Search song..."
90 - className="mb-3"
91 - onChange={(e) => onChange(e.target.value)}
92 - />
93 - {validationText}
94 - {autoCompleteView}
95 - </Col>
96 - <Col xs={{ span: 3, offset: 1 }}>
97 - <Button variant="primary" onClick={() => onSubmit()} disabled={!validQ}>Search</Button>
98 - </Col >
99 - </Row>
100 - </Form >
101 - )
102 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/home/TopVotesView.tsx deleted
-77
@@ -1,77 +0,0 @@
1 -import { useEffect, useState } from "react"
2 -import { SongProvider } from "../../../models/SongResponse"
3 -import axios from "axios"
4 -import { FormattedMessage } from "react-intl"
5 -import { Button, Carousel, Col, Row, Spinner } from "react-bootstrap"
6 -import { Link } from "react-router-dom"
7 -
8 -enum State {
9 - LOADING,
10 - LOADED,
11 - ERROR
12 -}
13 -
14 -export default function TopVotesView() {
15 -
16 - const [state, setState] = useState(State.LOADING)
17 - const [files, setFiles] = useState<SongProvider[]>([])
18 -
19 - useEffect(() => {
20 - setState(State.LOADING)
21 - axios.get(`/top-votes`)
22 - .then(res => {
23 - const files = res.data.audio_files;
24 - console.log(files)
25 - setFiles(files)
26 - setState(State.LOADED)
27 - })
28 - .catch(error => {
29 - console.log(error)
30 - setState(State.ERROR)
31 - })
32 - }, [])
33 -
34 - if (state === State.LOADING) {
35 - return <div>
36 - <p>
37 - <FormattedMessage id="home.loading"
38 - defaultMessage="Loading top votes..."
39 - description="Loading top votes message" />
40 - </p>
41 - <Spinner animation='grow' variant="danger"></Spinner>
42 - </div>
43 - }
44 -
45 - if (state === State.ERROR) {
46 - return <></>
47 - }
48 -
49 - return (
50 - <div>
51 - <Row>
52 - <Col xs={4}>
53 - <p className="h3">Top Votes</p>
54 - </Col>
55 - </Row>
56 - <Row>
57 - <Carousel>
58 - {files.map((file) => (
59 - <Carousel.Item key={file.id} style={{ padding: '20px' }}>
60 - <img
61 - className="d-block w-100"
62 - src={file.image_url}
63 - alt="First slide"
64 - />
65 - <Carousel.Caption>
66 - <Link to={{ pathname: `/split/${file.id}` }}>
67 - <Button variant="dark">{file.name}</Button>
68 - </Link>
69 - </Carousel.Caption>
70 - </Carousel.Item>
71 - ))}
72 - </Carousel>
73 - </Row>
74 - </div>
75 - )
76 -
77 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/song/SongView.tsx deleted
-125
@@ -1,125 +0,0 @@
1 -import axios from "axios";
2 -import { useCallback, useEffect, useState } from "react"
3 -import { Col, Container, Image, Row, Spinner } from "react-bootstrap";
4 -import { FormattedMessage } from "react-intl";
5 -import { useParams } from "react-router-dom";
6 -import { SongResponse } from "../../../models/SongResponse";
7 -import SongViewItems from "./SongViewItems";
8 -import SongViewConnectProvider from "./SongViewConnectProvider";
9 -import { useLogger } from "../../../../../lib/logger";
10 -
11 -export interface Payload {
12 - reference_id: string
13 - provider_id: string
14 - name: string
15 - provider: string
16 -}
17 -
18 -enum State {
19 - LOADING,
20 - LOADED,
21 - ERROR,
22 - NEED_UPDATE
23 -}
24 -
25 -export function SongView() {
26 -
27 - const log = useLogger('SongView')
28 - // Reference ID
29 - const { audioId } = useParams() as { audioId: string }
30 - const [state, setState] = useState(State.LOADING)
31 - const [songResponse, setSongResponse] = useState<SongResponse | null>(null)
32 -
33 - const fetchSong = useCallback(() => {
34 - setState(State.LOADING)
35 - axios.get(`/song-bridge/${audioId}`)
36 - .then(res => {
37 - log.debug(res)
38 - const response: SongResponse = res.data;
39 - log.debug(response)
40 - setSongResponse(response)
41 - setState(State.LOADED)
42 - })
43 - .catch(error => {
44 - log.error(error)
45 - setState(State.ERROR)
46 - })
47 -
48 - }, [audioId])
49 -
50 - useEffect(() => {
51 - fetchSong()
52 - }, [audioId, fetchSong])
53 -
54 - const onAddedProvider = (payload: Payload) => {
55 - fetchSong()
56 - }
57 -
58 - let mainView = <></>
59 - if (state === State.LOADING) {
60 - mainView = <div>
61 - <p>
62 - <FormattedMessage id="song.loading"
63 - defaultMessage="Searching for songs..."
64 - description="Loading message" />
65 - </p>
66 - <Spinner animation='grow' variant="warning"></Spinner>
67 - </div>
68 - }
69 -
70 - if (state === State.ERROR) {
71 - mainView = <div>
72 - <p>
73 - <FormattedMessage id="song.error"
74 - defaultMessage="Something wrong..."
75 - description="Loading message" />
76 - </p>
77 - </div>
78 - }
79 - if (state === State.LOADED && songResponse) {
80 - const { name, album_name, images, items } = songResponse
81 - log.debug(items)
82 - let topView = <></>
83 - if (songResponse?.items.length > 0) {
84 - topView = <SongViewItems items={items} referenceId={audioId} />
85 - } else {
86 - topView = <Row className="mb-3">
87 - <Col>
88 - <p>
89 - <FormattedMessage id="song.no_results"
90 - defaultMessage="No results found."
91 - description="No results message" />
92 - </p>
93 - </Col>
94 - </Row>
95 - }
96 - let imageView = <></>
97 - if (images.length > 0) {
98 - imageView = <Image
99 - className="w-100"
100 - src={images[0].url}
101 - alt="Image of the song"
102 - />
103 - }
104 -
105 - mainView = <>
106 - <Row className="mb-3">
107 - <Col xs={4}>
108 - {imageView}
109 - </Col>
110 - <Col xs={8}>
111 - <h1>{name}</h1>
112 - <h2>{album_name}</h2>
113 - </Col>
114 - </Row>
115 - {topView}
116 - <SongViewConnectProvider name={name} referenceId={audioId} onAddedProvider={onAddedProvider} />
117 - </>
118 - }
119 -
120 - return (
121 - <Container className="mb-5">
122 - {mainView}
123 - </Container>
124 - )
125 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/song/SongViewConnectProvider.tsx deleted
-139
@@ -1,139 +0,0 @@
1 -import { useEffect, useState } from "react";
2 -import { Form, Row, Col, Button, Alert } from "react-bootstrap";
3 -import { FormattedMessage } from "react-intl";
4 -import { CurrentUserType, db } from "../../../lib/db";
5 -import { Navigate } from "react-router-dom";
6 -import { Payload } from "./SongView";
7 -import axios from "axios";
8 -
9 -interface Props {
10 - referenceId: string
11 - name: string
12 - onAddedProvider: (payload: Payload) => void
13 -}
14 -
15 -enum State {
16 - IDLE,
17 - LOADING,
18 - LOADED,
19 - ERROR
20 -}
21 -
22 -export default function SongViewConnectProvider(props: Props) {
23 -
24 - const [validUrl, setValidUrl] = useState(false)
25 - const [url, setUrl] = useState("")
26 - const [errorMessage, setErrorMessage] = useState("")
27 - const [accessToken, setAccessToken] = useState<string | null>(null)
28 - const [redirectToLogin, setRedirectToLogin] = useState(false)
29 - const [, setState] = useState(State.IDLE)
30 -
31 - useEffect(() => {
32 - db
33 - .currentUser
34 - .where({ type: CurrentUserType.MAIN })
35 - .first()
36 - .then(res => {
37 - console.log(res)
38 - if (res?.accessToken) {
39 - setAccessToken(res?.accessToken)
40 - }
41 - }).catch(error => {
42 - console.log(error)
43 - })
44 - }, [])
45 -
46 - const validateYoutubeUrl = (url: string) => {
47 - if (url.length === 0) {
48 - setErrorMessage('URL is required')
49 - setValidUrl(false)
50 - }
51 - if (url.startsWith("https://www.youtube.com/watch?v=")) {
52 - setValidUrl(true)
53 - setUrl(url)
54 - setErrorMessage('')
55 - } else {
56 - setErrorMessage('Invalid YouTube URL')
57 - setValidUrl(false)
58 - }
59 - }
60 -
61 - const onAddUrl = () => {
62 - if (!accessToken) {
63 - setRedirectToLogin(true)
64 - return
65 - }
66 - if (validUrl) {
67 - const videoId = url.replace("https://www.youtube.com/watch?v=", "")
68 - console.log(videoId)
69 -
70 - addSongProvider({
71 - reference_id: props.referenceId,
72 - provider_id: videoId,
73 - name: props.name,
74 - provider: 'youtube'
75 - }, accessToken)
76 - }
77 - }
78 -
79 - const addSongProvider = (payload: Payload, accessToken: string) => {
80 - console.log(payload)
81 - setState(State.LOADING)
82 - axios
83 - .post(`/song-bridge`, payload, {
84 - headers: {
85 - 'Content-Type': 'application/json',
86 - 'Authorization': accessToken,
87 - }
88 - })
89 - .then(res => {
90 - console.log("Get results", res);
91 - setState(State.LOADED)
92 - setUrl('')
93 - props.onAddedProvider(payload)
94 - })
95 - .catch(error => {
96 - //console.log(error)
97 - setState(State.ERROR)
98 - setErrorMessage('Something went wrong')
99 - })
100 - }
101 -
102 - if (redirectToLogin) {
103 - return <Navigate to={'/login'} />
104 - }
105 -
106 - let validationText: any = ''
107 - if (errorMessage.length > 0) {
108 - validationText = <Alert variant="danger" onClose={() => setErrorMessage('')} dismissible><p>{errorMessage}</p></Alert>
109 - }
110 -
111 - return (
112 - <Row>
113 - <Col xs={3}>
114 - <h4>
115 - <FormattedMessage id="song.connect-provider"
116 - defaultMessage="Add more"
117 - description="Connect provider" />
118 - </h4>
119 - </Col>
120 - <Col xs={9}>
121 - <Form onSubmit={(e) => e.preventDefault()}>
122 - <Row>
123 - <Col xs={8}>
124 - <Form.Control
125 - placeholder="Put YouTube URL here..."
126 - className="mb-3"
127 - onChange={(e) => validateYoutubeUrl(e.target.value)}
128 - />
129 - {validationText}
130 - </Col>
131 - <Col xs={{ span: 3, offset: 1 }}>
132 - <Button variant="primary" onClick={() => onAddUrl()} disabled={!validUrl}>Add</Button>
133 - </Col >
134 - </Row>
135 - </Form >
136 - </Col>
137 - </Row>
138 - )
139 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/song/SongViewItems.tsx deleted
-152
@@ -1,152 +0,0 @@
1 -import { Row, Col, Container, Image } from "react-bootstrap";
2 -import { GiPlayButton, GiTimeBomb } from "react-icons/gi";
3 -import { MdCallSplit } from "react-icons/md";
4 -import { Payload } from "./SongView";
5 -import { useEffect, useState } from "react";
6 -import { db, CurrentUserType } from "../../../lib/db";
7 -import { Link, Navigate } from "react-router-dom";
8 -import axios from "axios";
9 -import { SongProvider } from "../../../models/SongResponse";
10 -import { Status } from "../../player/models/AudioFile";
11 -
12 -interface SongViewItemsProps {
13 - referenceId: string
14 - items: SongProvider[]
15 -}
16 -
17 -enum State {
18 - IDLE,
19 - LOADING,
20 - LOADED,
21 - ERROR
22 -}
23 -
24 -export default function SongViewItems(props: SongViewItemsProps) {
25 - return (
26 - <>
27 - <Row className="mb-3">
28 - <Col>
29 - <Container className="mb-5">
30 - <Row className="mb-3">
31 - <Col>
32 - <h3>Play this song!</h3>
33 - </Col>
34 - </Row>
35 - {props.items.map((video, index) =>
36 - <SongViewItem key={index} provider={video} referenceId={props.referenceId} />
37 - )}
38 - </Container>
39 - </Col>
40 - </Row>
41 - </>
42 - )
43 -}
44 -
45 -interface SongViewItemProps {
46 - referenceId: string
47 - provider: SongProvider
48 -}
49 -
50 -function SongViewItem(props: SongViewItemProps) {
51 -
52 - useEffect(() => {
53 - db
54 - .currentUser
55 - .where({ type: CurrentUserType.MAIN })
56 - .first()
57 - .then(res => {
58 - console.log(res)
59 - if (res?.accessToken) {
60 - setAccessToken(res?.accessToken)
61 - }
62 - }).catch(error => {
63 - console.log(error)
64 - })
65 - }, [])
66 -
67 - const [state, setstate] = useState(State.IDLE)
68 - const [accessToken, setAccessToken] = useState<string | null>(null)
69 - const [redirectToLogin, setRedirectToLogin] = useState(false)
70 -
71 - const onRequestSplit = (videoId: string) => {
72 - if (!accessToken) {
73 - setRedirectToLogin(true)
74 - return
75 - }
76 -
77 - addSongProvider({
78 - reference_id: props.referenceId,
79 - provider_id: videoId,
80 - name: props.provider.name,
81 - provider: 'youtube'
82 - }, accessToken)
83 - }
84 -
85 - const addSongProvider = (payload: Payload, accessToken: string) => {
86 - console.log(payload)
87 - setstate(State.LOADING)
88 - axios
89 - .post(`/split`, { id: props.provider.id, provider_id: payload.provider_id, provider: payload.provider }, {
90 - headers: {
91 - 'Content-Type': 'application/json',
92 - 'Authorization': accessToken,
93 - }
94 - })
95 - .then(res => {
96 - console.log("Get results", res);
97 - setstate(State.IDLE)
98 - // Refresh the list to update the split button
99 - })
100 - .catch(error => {
101 - console.log(error)
102 - setstate(State.ERROR)
103 - })
104 - }
105 -
106 - if (redirectToLogin) {
107 - return <Navigate to={'/login'} />
108 - }
109 - let splitRequestButton = <></>
110 - console.log(props.provider)
111 - if (state === State.IDLE && !props.provider.audio_file) {
112 - splitRequestButton = <MdCallSplit
113 - onClick={() => onRequestSplit(props.provider.provider_id)}
114 - size={50}
115 - style={{ cursor: 'pointer' }}
116 - title="Request split"
117 - />
118 - } else if (
119 - state === State.IDLE &&
120 - props.provider.audio_file &&
121 - props.provider.audio_file.status === Status.DONE
122 - ) {
123 - splitRequestButton = <Link to={`/splitfire/${props.provider.id}`} aria-current="page">
124 - <GiPlayButton size={50} style={{ cursor: 'pointer' }} title="Play" />
125 - </Link>
126 - } else if (
127 - state === State.IDLE &&
128 - props.provider.audio_file &&
129 - (props.provider.audio_file.status === Status.SPLITTING || props.provider.audio_file.status === Status.DOWNLOADING)
130 - ) {
131 - splitRequestButton = <Link to={`/song/votes/${props.provider.id}`} aria-current="page">
132 - <GiTimeBomb size={50} style={{ cursor: 'pointer' }} title="Waiting for split" />
133 - </Link>
134 - }
135 -
136 -
137 - if (state === State.LOADING) {
138 - splitRequestButton = <div>
139 - <p>Loading...</p>
140 - </div>
141 - }
142 -
143 - return (
144 - <Row className="mb-3 align-items-center">
145 - <Col xs={9} className="text-start">
146 - <h4>{props.provider.name}</h4>
147 - <Image src={`https://img.youtube.com/vi/${props.provider.provider_id}/mqdefault.jpg`} thumbnail />
148 - </Col>
149 - <Col xs={3}>{splitRequestButton}</Col>
150 - </Row >
151 - )
152 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/song/SongVotes.tsx deleted
-7
@@ -1,7 +0,0 @@
1 -import { Outlet } from "react-router-dom";
2 -
3 -export default function SongVotes() {
4 - return (
5 - <Outlet />
6 - )
7 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/song/SongVotesDetailView.tsx deleted
-21
@@ -1,21 +0,0 @@
1 -import { Container } from "react-bootstrap"
2 -import { SongProvider } from "../../../models/SongResponse"
3 -import { SongProviderVote } from "../../../models/SongVotesDetailResponse"
4 -import { YoutubeView } from "./components/YoutubeView"
5 -import { TitleView } from "./components/TitleView"
6 -import { UpDownVotesView } from "./components/UpDownVotes"
7 -
8 -interface Props {
9 - songProvider: SongProvider,
10 - votes: SongProviderVote[]
11 -}
12 -
13 -export default function SongVotesDetailView(props: Props) {
14 - return (
15 - <Container className="mb-5">
16 - <TitleView name={props.songProvider.name} />
17 - <UpDownVotesView votes={props.votes} providerId={props.songProvider.id} audioFile={props.songProvider.audio_file} />
18 - <YoutubeView provider_id={props.songProvider.provider_id} />
19 - </Container>
20 - )
21 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/song/SongVotesView.tsx deleted
-7
@@ -1,7 +0,0 @@
1 -export default function SongVotesView() {
2 - return (
3 - <div>
4 - <h1>19</h1>
5 - </div>
6 - )
7 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/song/components/ButtonSplit.tsx deleted
-99
@@ -1,99 +0,0 @@
1 -import { useContext, useState } from "react"
2 -import { AudioFile, Status } from "../../../player/models/AudioFile"
3 -import { UserContext } from "../../../../lib/CurrentUserContext"
4 -import requestSplitService, { SplitResponse } from "../../../../lib/requestSplitService"
5 -import { Col, Spinner } from "react-bootstrap"
6 -import { GiSandsOfTime, GiSpaceShuttle } from "react-icons/gi"
7 -import { MdNotificationImportant } from "react-icons/md"
8 -import { Navigate } from "react-router-dom"
9 -import { HTTPStatusCode } from "../../esef/SplitFireView"
10 -import { BsPlayCircle } from "react-icons/bs"
11 -
12 -enum State {
13 - LOADING,
14 - LOADED,
15 - ERROR,
16 -}
17 -
18 -export function ButtonSplit(props: { providerId: string, audioFile: AudioFile | null, aggregateVotes: number }) {
19 -
20 - const [state, setState] = useState(State.LOADED)
21 - const { user } = useContext(UserContext)
22 - const [goToLogin, setGoToLogin] = useState(false)
23 - const [audioFile, setAudioFile] = useState<AudioFile | null>(null)
24 -
25 - useState(() => {
26 - setAudioFile(props.audioFile)
27 - })
28 -
29 - const splitRequest = () => {
30 - if (!user || !user.accessToken) {
31 - setGoToLogin(true)
32 - return
33 - }
34 -
35 - setState(State.LOADING)
36 - requestSplitService(props.providerId, user.accessToken)
37 - .then(res => {
38 - console.log(res)
39 - const response: SplitResponse = res.data
40 - if (response.code === HTTPStatusCode.OK) {
41 - setAudioFile(response.audio_file)
42 - setState(State.LOADED)
43 - } else {
44 - setState(State.ERROR)
45 - }
46 - })
47 - .catch(error => {
48 - console.log(error)
49 - setState(State.ERROR)
50 - })
51 - }
52 -
53 - // Default to production value.
54 - const splitTreshold = process.env.REACT_APP_SPLIT_THRESHOLD ? parseInt(process.env.REACT_APP_SPLIT_THRESHOLD) : 5
55 -
56 - const isDoneSplitting = audioFile && audioFile.status === Status.DONE
57 - const isCurrentlySplitting = audioFile && (audioFile.status === Status.SPLITTING || audioFile.status === Status.DOWNLOADING)
58 - const isReadyToSplit = props.aggregateVotes > splitTreshold
59 -
60 - if (goToLogin) {
61 - return <Navigate to={'/login'} />
62 - }
63 -
64 - if (state === State.LOADING) {
65 - return <Col className="align-self-center" xs={12}>
66 - <Spinner animation="border" role="status">
67 - <span className="visually-hidden">Loading...</span>
68 - </Spinner>
69 - </Col>
70 - }
71 - // Check if we have processed the split request.
72 - if (isDoneSplitting) {
73 - return <Col className="align-self-center mb-3 mt-3" xs={12}>
74 - <h1>Let's Play!</h1>
75 - </Col>
76 - } else if (isCurrentlySplitting) {
77 - return <Col className="align-self-center mb-3 mt-3" xs={12}>
78 - <GiSandsOfTime
79 - size={40}
80 - className="mb-3"
81 - color="green"
82 - title="Split request in progress..." />
83 - </Col>
84 - } else if (isReadyToSplit) {
85 - return <Col className="align-self-center mb-3 mt-3" xs={12}>
86 - <GiSpaceShuttle
87 - size={40}
88 - className="mb-3"
89 - onClick={splitRequest}
90 - style={{ cursor: "pointer" }}
91 - color="green"
92 - title="Split request..." />
93 - </Col>
94 - }
95 -
96 - return <Col className="align-self-center mb-3 mt-3" xs={12}>
97 - <MdNotificationImportant size={40} className="mb-3" color="green" title="Not enough votes to split..." />
98 - </Col>
99 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/song/components/TitleView.tsx deleted
-11
@@ -1,11 +0,0 @@
1 -import { Col, Row } from "react-bootstrap";
2 -
3 -export function TitleView(props: { name: string }) {
4 - return (
5 - <Row className="mb-5">
6 - <Col>
7 - <h1 className="title text-center">{`Upvotes "${props.name}" so we can jam together!`}</h1>
8 - </Col>
9 - </Row>
10 - )
11 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/song/components/UpDownVotes.tsx deleted
-213
@@ -1,213 +0,0 @@
1 -import axios from "axios"
2 -import { CSSProperties, useState, useContext } from "react"
3 -import { Row, Col, Spinner } from "react-bootstrap"
4 -import { BsArrowUpCircleFill, BsArrowDownCircleFill, BsPlayBtnFill } from "react-icons/bs"
5 -import { Navigate } from "react-router-dom"
6 -import { UserContext } from "../../../../lib/CurrentUserContext"
7 -import { CurrentUser } from "../../../../lib/db"
8 -import { SongProviderVote } from "../../../../models/SongVotesDetailResponse"
9 -import { AudioFile, Status } from "../../../player/models/AudioFile"
10 -import { HTTPStatusCode, SongBridgeResponse } from "../../esef/SplitFireView"
11 -import { ButtonSplit } from "./ButtonSplit"
12 -import { VoterGravatarsViews } from "./VoterGravatarsViews"
13 -
14 -enum State {
15 - LOADING,
16 - LOADED,
17 - ERROR,
18 -}
19 -
20 -interface UpDownVotesViewProps {
21 - providerId: string
22 - votes: SongProviderVote[]
23 - audioFile: AudioFile | null
24 -}
25 -
26 -export enum VoteType {
27 - UP = 'up',
28 - DOWN = 'down'
29 -}
30 -
31 -interface VoteState {
32 - aggregate: number
33 - buttonUpStyle: CSSProperties
34 - buttonDownStyle: CSSProperties
35 - upVotes: SongProviderVote[]
36 - downVotes: SongProviderVote[]
37 -}
38 -
39 -export function UpDownVotesView(props: UpDownVotesViewProps) {
40 -
41 - const [state, setState] = useState(State.LOADED)
42 - const [goToLogin, setGoToLogin] = useState(false)
43 - const [goToPlayer, setGoToPlayer] = useState(false)
44 - const { user } = useContext(UserContext)
45 -
46 - const buttonStyle = (votes: SongProviderVote[], type: VoteType): CSSProperties => {
47 - if (!user) {
48 - return { cursor: "pointer" }
49 - }
50 -
51 - if (type === VoteType.UP) {
52 - if (shouldDisableButton(votes, VoteType.UP, user)) {
53 - return { pointerEvents: "none", opacity: "0.4" }
54 - } else {
55 - return { cursor: "pointer" }
56 - }
57 - } else {
58 - if (shouldDisableButton(votes, VoteType.DOWN, user)) {
59 - return { pointerEvents: "none", opacity: "0.4" }
60 - } else {
61 - return { cursor: "pointer" }
62 - }
63 - }
64 - }
65 -
66 - const calculateVotes = (votes: SongProviderVote[]): number => {
67 - if (votes.length === 0) {
68 - return 0
69 - }
70 -
71 - const up = votes.filter((x) => x.vote_type === VoteType.UP).length
72 - const down = votes.filter((x) => x.vote_type === VoteType.DOWN).length
73 - //console.log('upvotes', up)
74 - //console.log('downvotes', down)
75 - const votesAggregate = up - down
76 - return votesAggregate
77 - }
78 -
79 - const shouldDisableButton = (votes: SongProviderVote[], type: VoteType, user: CurrentUser) => {
80 - if (!user) {
81 - return false
82 - }
83 - const x = votes.filter((y) => {
84 - if (y.user_id === user.user.id && y.vote_type === type)
85 - return true
86 - return false
87 - })
88 - return x.length > 0
89 - }
90 -
91 - const [voteState, setVoteState] = useState<VoteState>({
92 - aggregate: calculateVotes(props.votes),
93 - buttonUpStyle: buttonStyle(props.votes, VoteType.UP),
94 - buttonDownStyle: buttonStyle(props.votes, VoteType.DOWN),
95 - upVotes: props.votes.filter((x) => x.vote_type === VoteType.UP),
96 - downVotes: props.votes.filter((x) => x.vote_type === VoteType.DOWN)
97 - })
98 -
99 - const vote = (type: VoteType) => {
100 -
101 - if (!user || !user.accessToken || user.accessToken.length <= 1) {
102 - setGoToLogin(true)
103 - return
104 - }
105 -
106 - setState(State.LOADING)
107 - axios
108 - .post(`/song-bridge/${props.providerId}/vote`, { vote_type: type, provider_id: props.providerId },
109 - {
110 - headers: {
111 - 'Content-Type': 'application/json',
112 - 'Authorization': user.accessToken,
113 - }
114 - })
115 - .then(res => {
116 - const response: SongBridgeResponse = res.data
117 - //console.log(response.votes)
118 - if (response.code === HTTPStatusCode.OK) {
119 - setVoteState({
120 - aggregate: calculateVotes(response.votes),
121 - buttonUpStyle: buttonStyle(response.votes, VoteType.UP),
122 - buttonDownStyle: buttonStyle(response.votes, VoteType.DOWN),
123 - upVotes: response.votes.filter((x) => x.vote_type === VoteType.UP),
124 - downVotes: response.votes.filter((x) => x.vote_type === VoteType.DOWN)
125 - })
126 - setState(State.LOADED)
127 - } else {
128 - console.log('error')
129 - setState(State.ERROR)
130 - }
131 - })
132 - .catch(error => {
133 - console.log(error)
134 - setState(State.ERROR)
135 - })
136 - }
137 -
138 - const isDoneSplitting = props.audioFile && props.audioFile.status === Status.DONE
139 -
140 - if (goToLogin) {
141 - return <Navigate to={'/login'} />
142 - }
143 -
144 - if (goToPlayer) {
145 - return <Navigate to={`/player/${props.audioFile?.id}`} />
146 -}
147 -
148 - if (state === State.LOADING) {
149 - return <Row className="mb-3" style={{ minHeight: 250 }}>
150 - <Col className="align-self-center">
151 - <Spinner animation="border" role="status">
152 - <span className="visually-hidden">Loading...</span>
153 - </Spinner>
154 - </Col>
155 - </Row>
156 - }
157 -
158 - const mainView = isDoneSplitting ? <LetsPlayView onClick={() => setGoToPlayer(true)}/>
159 - : <MainVotesView voteState={voteState} vote={vote} />
160 -
161 - return (
162 - <Row className="mb-3 mt-3" style={{ minHeight: 250 }}>
163 - <ButtonSplit providerId={props.providerId} audioFile={props.audioFile} aggregateVotes={voteState.aggregate} />
164 - {mainView}
165 - </Row>
166 - )
167 -}
168 -
169 -function LetsPlayView(props: { onClick: () => void }) {
170 -
171 - const { onClick } = props
172 -
173 - return <Col className="align-self-center" xs={12}>
174 - <BsPlayBtnFill onClick={onClick} size={50} cursor={'pointer'} color="green"/>
175 - </Col>
176 -}
177 -
178 -function MainVotesView(props: { voteState: VoteState, vote: (type: VoteType) => void }) {
179 -
180 - const { voteState, vote } = props
181 -
182 - return <>
183 - <Col xs={5}>
184 - <VoterGravatarsViews
185 - voters={voteState.upVotes}
186 - type={VoteType.UP}
187 - />
188 - </Col>
189 - <Col xs={2}>
190 - <BsArrowUpCircleFill
191 - size={40}
192 - className="mb-3"
193 - style={voteState.buttonUpStyle}
194 - onClick={() => vote(VoteType.UP)}
195 - color="green"
196 - />
197 - <h1 className="votes">{voteState.aggregate}</h1>
198 - <BsArrowDownCircleFill
199 - size={40}
200 - className="mt-3"
201 - style={voteState.buttonDownStyle}
202 - onClick={() => vote(VoteType.DOWN)}
203 - color="green"
204 - />
205 - </Col>
206 - <Col xs={5} className="align-self-end">
207 - <VoterGravatarsViews
208 - voters={voteState.downVotes}
209 - type={VoteType.DOWN}
210 - />
211 - </Col>
212 -</>
213 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/song/components/VoterGravatarsViews.tsx deleted
-21
@@ -1,21 +0,0 @@
1 -import { Col, Container, Row, Image } from "react-bootstrap"
2 -import { Link } from "react-router-dom"
3 -import { SongProviderVote } from "../../../../models/SongVotesDetailResponse"
4 -import { VoteType } from "./UpDownVotes"
5 -
6 -export function VoterGravatarsViews(props: { voters: SongProviderVote[], type: VoteType }) {
7 - const className = props.type === VoteType.DOWN ? "justify-content-end" : "justify-content-start"
8 - return (
9 - <Container>
10 - <Row className={className}>
11 - {props.voters.map((x, i) => {
12 - return <Col xs={2} key={i} >
13 - <Link to={`/@${x.voter_username_or_id}`}>
14 - <Image roundedCircle src={x.voter_gravatar} width={24} />
15 - </Link>
16 - </Col>
17 - })}
18 - </Row>
19 - </Container>
20 - )
21 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/pages/song/components/YoutubeView.tsx deleted
-26
@@ -1,26 +0,0 @@
1 -import { Col, Row } from "react-bootstrap"
2 -import YouTube, { Options } from "react-youtube"
3 -
4 -export function YoutubeView(props: { provider_id: string }) {
5 - const opts: Options = {
6 - width: '100%',
7 - playerVars: {
8 - // https://developers.google.com/youtube/player_parameters
9 - autoplay: 0,
10 - mute: 0,
11 - controls: 0,
12 - rel: 0,
13 - showinfo: 0
14 - },
15 - }
16 - return (
17 - <Row className="mt-5">
18 - <Col>
19 - <YouTube
20 - videoId={props.provider_id}
21 - opts={opts}
22 - />
23 - </Col>
24 - </Row>
25 - )
26 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/player/AudioFile.tsx deleted
-7
@@ -1,7 +0,0 @@
1 -export default function AudioFile() {
2 - return (
3 - <div>
4 - AudioFile
5 - </div>
6 - )
7 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/player/AudioPlayer.tsx deleted
-442
@@ -1,442 +0,0 @@
1 -/* eslint-disable no-script-url */
2 -/* eslint-disable jsx-a11y/anchor-is-valid */
3 -import axios from 'axios';
4 -import { CSSProperties } from 'react';
5 -import { Component } from 'react';
6 -import { Col, Row, Spinner } from 'react-bootstrap';
7 -import YouTube, { Options } from 'react-youtube';
8 -import { GiMicrophone, GiDrumKit, GiGuitarHead, GiGuitarBassHead, GiPianoKeys, GiPlayButton, GiPauseButton } from 'react-icons/gi';
9 -import { IconContext } from 'react-icons';
10 -import { db, AudioFiles } from '../../lib/db';
11 -import { FormattedMessage } from 'react-intl';
12 -import { Channel, Player } from 'tone';
13 -import ActionCable from 'actioncable';
14 -import { Result } from './models/Result';
15 -import { AudioFile, Status } from './models/AudioFile';
16 -import { YouTubeEventTarget } from './models/YouTubeEventTarget';
17 -import { YouTubePlayerState } from './models/YouTubePlayerState';
18 -import { Mode } from './models/Mode';
19 -import { LoadingView } from '../templates/LoadingView';
20 -
21 -const buttonStyle: CSSProperties = {
22 -
23 -}
24 -
25 -type AudioProps = {
26 - audioId: string
27 -}
28 -
29 -type AudioState = {
30 - errorMessage: string
31 - audioFile: AudioFile | null
32 - playerTime: number
33 - isPlayerReady: boolean
34 - isYoutubePlayerReady: boolean
35 - isPlaying: boolean
36 - vocalAudioOn: boolean
37 - guitarAudioOn: boolean
38 - bassAudioOn: boolean
39 - drumsAudioOn: boolean
40 - pianoAudioOn: boolean
41 -}
42 -
43 -const actionCableHost = process.env.REACT_APP_ACTION_CABLE_URL || 'ws://localhost:3000/cable'
44 -
45 -class AudioPlayer extends Component<AudioProps, AudioState> {
46 -
47 - vocalPlayer: Player | null = null;
48 - otherPlayer: Player | null = null;
49 - bassPlayer: Player | null = null;
50 - drumsPlayer: Player | null = null;
51 - pianoPlayer: Player | null = null;
52 -
53 - youTubeEventTarget: YouTubeEventTarget | null = null
54 - updateYouTubePlayerStatusIntervalId: any | null = null
55 -
56 - debugChannel: any
57 - cableApp = ActionCable.createConsumer(actionCableHost)
58 -
59 - constructor(props: AudioProps) {
60 - super(props);
61 - this.state = {
62 - errorMessage: '',
63 - audioFile: null,
64 - playerTime: 0,
65 - isPlayerReady: false,
66 - isYoutubePlayerReady: false,
67 - isPlaying: false,
68 - vocalAudioOn: true,
69 - guitarAudioOn: true,
70 - bassAudioOn: true,
71 - drumsAudioOn: true,
72 - pianoAudioOn: true,
73 - }
74 -
75 - const received = (data: AudioFile) => {
76 - this._updateProgress(data)
77 - }
78 -
79 - const conected = () => { }
80 -
81 - this.debugChannel = this.cableApp.subscriptions.create({
82 - channel: 'FileProcessingChannel',
83 - audioFileId: this.state.audioFile?.id
84 - }, {
85 - conected,
86 - received
87 - })
88 - }
89 -
90 - componentDidMount() {
91 - const audioFileId = this.props.audioId;
92 - axios.get(`/splitfire/${audioFileId}`)
93 - .then(res => {
94 - // console.log("Get results", res);
95 - const audioFile = res.data.audio_file;
96 - this.setState({ audioFile });
97 - // console.log(this.state);
98 - this._downloadAudioFilesIfNeeded();
99 - })
100 - .catch(error => {
101 - //console.log(error)
102 - this.setState({ errorMessage: 'Something error with this file.' })
103 - });
104 - }
105 -
106 - componentWillUnmount() {
107 - this.setState({ isPlaying: false })
108 - this.vocalPlayer?.stop()
109 - this.bassPlayer?.stop()
110 - this.drumsPlayer?.stop()
111 - this.pianoPlayer?.stop()
112 - this.otherPlayer?.stop()
113 -
114 - this.cableApp.disconnect()
115 - }
116 -
117 - _updateProgress(data: AudioFile) {
118 - //console.log(data)
119 -
120 - this.setState({ audioFile: data })
121 -
122 - if (this.state.audioFile?.status === Status.DONE)
123 - window.location.reload();
124 - }
125 -
126 - _downloadAudioFilesIfNeeded() {
127 -
128 - if (this.state.audioFile?.status !== Status.DONE)
129 - return
130 -
131 - this._prepareAudio(Mode.Vocal);
132 - this._prepareAudio(Mode.Bass);
133 - this._prepareAudio(Mode.Drum);
134 - this._prepareAudio(Mode.Guitar);
135 - this._prepareAudio(Mode.Keyboard);
136 - }
137 -
138 - async _downloadFile(type: Mode) {
139 - // //console.log('_downloadFile', type);
140 - let audioFile: Result | undefined;
141 - switch (type) {
142 - case Mode.Vocal:
143 - audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('vocals') });
144 - break;
145 - case Mode.Bass:
146 - audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('bass') });
147 - break;
148 - case Mode.Drum:
149 - audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('drums') });
150 - break;
151 - case Mode.Guitar:
152 - audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('other') });
153 - break;
154 - case Mode.Keyboard:
155 - audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('piano') });
156 - break;
157 - }
158 -
159 - const audioFileId = this.props.audioId;
160 - // console.log("Download url", audioFile?.source_file);
161 - axios({
162 - url: audioFile?.source_file,
163 - method: 'GET',
164 - responseType: 'blob',
165 - }).then((response) => {
166 - const file = new Blob([response.data], { type: 'audio/mp3' });
167 - const item: AudioFiles = {
168 - audioFileId, type: type, file
169 - }
170 - db.audioFiles.add(item);
171 - // console.log('File Downloaded', file);
172 - this._setAudioSrc(type, file);
173 - }).catch(error => {
174 - // console.log(error);
175 - });
176 - }
177 -
178 - async _prepareAudio(type: Mode) {
179 - const audioFileId = this.props.audioId;
180 - db.audioFiles
181 - .get({ audioFileId: audioFileId, type: type })
182 - .then((record: AudioFiles | undefined) => {
183 - if (record?.file === undefined) {
184 - this._downloadFile(type);
185 - return
186 - }
187 - this._setAudioSrc(type, record.file)
188 - })
189 - .catch((onrejected: any) => {
190 - // console.log("Rejected", onrejected);
191 - });
192 - }
193 -
194 - _setAudioSrc(type: Mode, file: Blob) {
195 - // Get window.URL object
196 - const URL = window.URL || window.webkitURL;
197 - // Create and revoke ObjectURL
198 - const audioFileURL = URL.createObjectURL(file);
199 - const channel = new Channel().toDestination();
200 -
201 - switch (type) {
202 - case Mode.Vocal:
203 - this.vocalPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
204 - this.vocalPlayer.connect(channel);
205 - break;
206 - case Mode.Bass:
207 - this.bassPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
208 - this.bassPlayer.connect(channel);
209 - break;
210 - case Mode.Drum:
211 - this.drumsPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
212 - this.drumsPlayer.connect(channel);
213 - break;
214 - case Mode.Guitar:
215 - this.otherPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
216 - this.otherPlayer.connect(channel);
217 - break;
218 - case Mode.Keyboard:
219 - this.pianoPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
220 - this.pianoPlayer.connect(channel);
221 - break;
222 - }
223 - }
224 -
225 - _updatePlayerStatus: (() => void) = () => {
226 - if (
227 - this.vocalPlayer?.loaded &&
228 - this.drumsPlayer?.loaded &&
229 - this.otherPlayer?.loaded &&
230 - this.bassPlayer?.loaded &&
231 - this.pianoPlayer?.loaded
232 - ) {
233 - this.setState({ isPlayerReady: true });
234 - }
235 - }
236 -
237 - togglePlayAudio() {
238 - this.setState({ isPlaying: !this.state.isPlaying })
239 - if (this.state.isPlaying) {
240 - this.vocalPlayer?.stop()
241 - this.bassPlayer?.stop()
242 - this.drumsPlayer?.stop()
243 - this.pianoPlayer?.stop()
244 - this.otherPlayer?.stop()
245 - this.youTubeEventTarget?.pauseVideo()
246 - } else {
247 - this.youTubeEventTarget?.seekTo(0)
248 - this.youTubeEventTarget?.playVideo()
249 - this.vocalPlayer?.start();
250 - this.bassPlayer?.start();
251 - this.drumsPlayer?.start();
252 - this.pianoPlayer?.start();
253 - this.otherPlayer?.start();
254 - }
255 - }
256 -
257 - setToggleInstrument(mode: Mode) {
258 - switch (mode) {
259 - case Mode.Vocal:
260 - if (!this.vocalPlayer) return;
261 - this.setState({ vocalAudioOn: !this.state.vocalAudioOn });
262 - this.vocalPlayer.mute = this.state.vocalAudioOn;
263 - break;
264 - case Mode.Bass:
265 - if (!this.bassPlayer) return;
266 - this.setState({ bassAudioOn: !this.state.bassAudioOn });
267 - this.bassPlayer.mute = this.state.bassAudioOn;
268 - break;
269 - case Mode.Guitar:
270 - if (!this.otherPlayer) return;
271 - this.setState({ guitarAudioOn: !this.state.guitarAudioOn });
272 - this.otherPlayer.mute = this.state.guitarAudioOn;
273 - break;
274 - case Mode.Keyboard:
275 - if (!this.pianoPlayer) return;
276 - this.setState({ pianoAudioOn: !this.state.pianoAudioOn });
277 - this.pianoPlayer.mute = this.state.pianoAudioOn;;
278 - break;
279 - case Mode.Drum:
280 - if (!this.drumsPlayer) return;
281 - this.setState({ drumsAudioOn: !this.state.drumsAudioOn });
282 - this.drumsPlayer.mute = this.state.drumsAudioOn;
283 - break;
284 - }
285 - }
286 -
287 - render() {
288 -
289 - const { audioFile, errorMessage, isPlayerReady, isYoutubePlayerReady, isPlaying } = this.state
290 -
291 - if (errorMessage.length > 0) {
292 - return (
293 - <p>Something wrong with this file.</p>
294 - )
295 - }
296 -
297 - if (audioFile?.status === Status.DOWNLOADING || audioFile?.status === Status.SPLITTING) {
298 - return <p>Processing file: {audioFile.progress}% </p>
299 - }
300 -
301 - if (isPlayerReady) {
302 - const opts: Options = {
303 - width: '100%',
304 - playerVars: {
305 - // https://developers.google.com/youtube/player_parameters
306 - autoplay: 0,
307 - mute: 1,
308 - controls: 0,
309 - rel: 0,
310 - showinfo: 0
311 - },
312 - };
313 - let togglePlayButton = <div>
314 - <p>
315 - <FormattedMessage id="player.loadingYoutube"
316 - defaultMessage="Generating video..."
317 - description="Loading message" />
318 - </p>
319 - <Spinner animation='grow' variant="danger"></Spinner>
320 - </div>
321 - if (isYoutubePlayerReady) {
322 - let button
323 - if (isPlaying) {
324 - button = <Col>
325 - <GiPauseButton
326 - color={isPlaying ? `white` : `red`}
327 - onClick={() => this.togglePlayAudio()}
328 - style={buttonStyle} />
329 - </Col>
330 - } else {
331 - button = <Col>
332 - <GiPlayButton
333 - color={isPlaying ? `white` : `red`}
334 - onClick={() => this.togglePlayAudio()}
335 - style={buttonStyle} />
336 - </Col>
337 - }
338 - togglePlayButton = <Col>
339 - <p>
340 - <FormattedMessage id="player.instruction"
341 - defaultMessage="Click isntrument icon to mute part of the song."
342 - description="Instruction message" />
343 - </p>
344 - {button}
345 - </Col>
346 - }
347 -
348 - return (
349 - <IconContext.Provider value={{ size: "2em", color: "white", className: "global-class-name" }}>
350 - <Row className="mb-3 mt-3">
351 - <Col>
352 - <h1>{this.state.audioFile?.name}</h1>
353 - </Col>
354 - </Row>
355 - <Row className="mb-3 mt-3">
356 - {togglePlayButton}
357 - </Row>
358 - <Row className="mb-3 mt-3">
359 - <Col>
360 - <GiMicrophone
361 - color={this.state.vocalAudioOn ? `white` : `red`}
362 - onClick={() => this.setToggleInstrument(Mode.Vocal)}
363 - style={buttonStyle} />
364 - </Col>
365 - <Col>
366 - <GiDrumKit
367 - color={this.state.drumsAudioOn ? `white` : `red`}
368 - onClick={() => this.setToggleInstrument(Mode.Drum)}
369 - style={buttonStyle} />
370 - </Col>
371 - <Col>
372 - <GiGuitarBassHead
373 - color={this.state.bassAudioOn ? `white` : `red`}
374 - onClick={() => this.setToggleInstrument(Mode.Bass)}
375 - style={buttonStyle} />
376 - </Col>
377 - <Col>
378 - <GiGuitarHead
379 - color={this.state.guitarAudioOn ? `white` : `red`}
380 - onClick={() => this.setToggleInstrument(Mode.Guitar)}
381 - style={buttonStyle} />
382 - </Col>
383 - <Col>
384 - <GiPianoKeys
385 - color={this.state.pianoAudioOn ? `white` : `red`}
386 - onClick={() => this.setToggleInstrument(Mode.Keyboard)}
387 - style={buttonStyle} />
388 - </Col>
389 - </Row>
390 - <Row>
391 - <Col style={{ display: isYoutubePlayerReady ? 'block' : 'none', pointerEvents: 'none' }}>
392 - <YouTube
393 - videoId={this.state.audioFile?.youtube_video_id}
394 - opts={opts}
395 - onReady={this._onReady.bind(this)}
396 - onStateChange={this._onStateChange.bind(this)}
397 - />
398 - </Col>
399 - </Row>
400 - </IconContext.Provider>
401 - )
402 - } else {
403 - return <LoadingView />
404 - }
405 - }
406 -
407 - _onReady(event: any) {
408 - this.youTubeEventTarget = event.target
409 - this.youTubeEventTarget?.playVideo()
410 - this.updateYouTubePlayerStatusIntervalId = setInterval(() => {
411 - if (!this.state.isYoutubePlayerReady) {
412 - this._updateYouTubePlayerStatusIfNeeded()
413 - }
414 - }, 2000)
415 - }
416 -
417 - _onStateChange(event: any) {
418 - if (!this.youTubeEventTarget) return
419 -
420 - if (this.youTubeEventTarget.getPlayerState() === YouTubePlayerState.ended) {
421 - this.setState({ isPlaying: false })
422 - }
423 - }
424 -
425 - _updateYouTubePlayerStatusIfNeeded() {
426 -
427 - if (this.state.isYoutubePlayerReady) return
428 - if (!this.youTubeEventTarget) return
429 -
430 - // Let's say 5 seconds buffer is enough.
431 - if (this.youTubeEventTarget?.getCurrentTime() > 5) {
432 - this.setState({ isYoutubePlayerReady: true })
433 - this.youTubeEventTarget?.pauseVideo()
434 -
435 - if (this.updateYouTubePlayerStatusIntervalId)
436 - clearInterval(this.updateYouTubePlayerStatusIntervalId)
437 - }
438 - }
439 -
440 -}
441 -
442 -export default AudioPlayer;
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/player/AudioPlayerView.tsx deleted
-20
@@ -1,20 +0,0 @@
1 -import { useContext } from "react";
2 -import { Navigate, useParams } from "react-router-dom";
3 -import AudioPlayer from "./AudioPlayer";
4 -import { UserContext } from "../../lib/CurrentUserContext";
5 -
6 -export function AudioPlayerView() {
7 -
8 - const { audioId } = useParams()
9 - const { user } = useContext(UserContext)
10 -
11 - if (!user) {
12 - return <Navigate to={'/login'} />
13 - }
14 -
15 - if (user && audioId) {
16 - return <AudioPlayer audioId={audioId} />
17 - }
18 -
19 - return <p>Something wrong</p>
20 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/player/ControlButtons.tsx deleted
-43
@@ -1,43 +0,0 @@
1 -import { Col, Row } from "react-bootstrap"
2 -import { BsPlayBtnFill, BsStopBtnFill, BsFillRecordBtnFill, BsPauseBtnFill } from "react-icons/bs"
3 -
4 -export function ControlButtonsView(props: { isPlaying: boolean, isRecording: boolean, onClick: () => void, onStop: () => void, onRecord: () => void }) {
5 -
6 - const { isPlaying, isRecording, onClick, onStop, onRecord } = props
7 -
8 - const _onPlayOrPause = () => {
9 - if (isRecording) return
10 -
11 - onClick()
12 - }
13 -
14 - return <Row>
15 - <Col xs={{span: 1, offset: 4}} className="mb-3 mt-3" color="red" >
16 - { isPlaying ? <BsPauseBtnFill onClick={_onPlayOrPause} size={32} style={ { cursor: isRecording ? undefined : "pointer"}} title="Pause" color={ isRecording ? "grey" : "white"} />
17 - : <BsPlayBtnFill onClick={_onPlayOrPause} size={32} style={ { cursor: isRecording ? undefined : "pointer"}} title="Play" color={ isRecording ? "grey" : "white" } />
18 - }
19 - </Col>
20 - <Col xs={{span: 1}} className="mb-3 mt-3">
21 - <BsStopBtnFill
22 - onClick={() => {
23 - if (isRecording) return
24 -
25 - onStop()
26 - }}
27 - size={32}
28 - style={{ cursor: isRecording ? undefined : "pointer"}}
29 - title="Stop"
30 - color={ isRecording ? "grey" : "white" }
31 - />
32 - </Col>
33 - <Col xs={{span: 1}} className="mb-3 mt-3">
34 - <BsFillRecordBtnFill
35 - onClick={onRecord}
36 - size={32}
37 - style={{ cursor: "pointer"}}
38 - title={ isRecording ? "Stop" : "Record"}
39 - color={ isRecording ? "green" : "red" }
40 - />
41 - </Col>
42 - </Row>
43 -}
frontend/splitfire-desktop/app/_src/components/player/CountDownView.tsx deleted
-24
@@ -1,24 +0,0 @@
1 -import { useEffect } from "react"
2 -import { useCountDown } from "../../lib/useCountDown"
3 -import { Col, Row } from "react-bootstrap"
4 -
5 -export function CountDownView(props: { seconds: number, type: string, onCountDownEnd?: () => void}) {
6 - const { secondsLeft, startTimer } = useCountDown()
7 -
8 - useEffect(() => {
9 - startTimer(props.seconds)
10 - }, [])
11 -
12 - if (secondsLeft === 0 && props.onCountDownEnd) {
13 - console.log('CountDownView: onCountDownEnd')
14 - props.onCountDownEnd()
15 - }
16 -
17 - const displayText = secondsLeft === 0 ? props.type : secondsLeft
18 -
19 - return <Row>
20 - <Col className="align-self-center" xs={12}>
21 - <h1>{displayText}</h1>
22 - </Col>
23 - </Row>
24 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/player/HideShowToggle.tsx deleted
-12
@@ -1,12 +0,0 @@
1 -import { MdArrowLeft, MdArrowDropDown } from "react-icons/md"
2 -
3 -export function HideShowToggleView(props: { hideVolumeSliders: boolean, setHideVolumeSliders: (hideVolumeSliders: boolean) => void }) {
4 -
5 - const { hideVolumeSliders, setHideVolumeSliders } = props
6 -
7 - if (hideVolumeSliders) {
8 - return <MdArrowLeft onClick={() => setHideVolumeSliders(!hideVolumeSliders)} size={30} style={{cursor: "pointer"}} />
9 - }
10 -
11 - return <MdArrowDropDown onClick={() => setHideVolumeSliders(!hideVolumeSliders)} size={30} style={{cursor: "pointer"}} />
12 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/player/Player.tsx deleted
-280
@@ -1,280 +0,0 @@
1 -import { useCallback, useContext, useEffect, useState } from "react"
2 -import { Col, Container, Row } from "react-bootstrap"
3 -import { useParams } from "react-router-dom"
4 -import { ModeDemucs } from "./models/Mode"
5 -import { useLogger } from "../../../../lib/logger"
6 -import { CountDownView } from "./CountDownView"
7 -import ErrorView from "../templates/Error"
8 -import { LoadingView } from "../templates/LoadingView"
9 -import { invoke } from '@tauri-apps/api'
10 -import { UserContext } from "../../lib/CurrentUserContext"
11 -import { TAURI_PLAYER_RECORD, TAURI_PLAYER_SET_VOLUME, TAURI_PLAYER_PAUSED, TAURI_PLAYER_UNMOUNT, TAURI_PLAYER_PREPARE, TAURI_PLAYER_STOP, TAURI_PLAYER_RESUMED, TAURI_PLAYER_PLAY, TAURI_PLAYER_RECORDING_LENGTH, TAURI_PLAYER_RECORD_STOP } from "../../lib/tauriHandler"
12 -import { ControlButtonsView } from "./ControlButtons"
13 -import { VolumeSliderView } from "./VolumeSliderView"
14 -import { HideShowToggleView } from "./HideShowToggle"
15 -import { RecordingView } from "./RecordingView"
16 -import { PlayerPrepareResponse } from "@/models/content"
17 -import { TauriResponse } from "@/models/shared"
18 -
19 -enum State {
20 - LOADING,
21 - LOADED,
22 - ERROR,
23 -}
24 -
25 -export enum PlayerState {
26 - STOPPED,
27 - PLAYING,
28 - PAUSED,
29 - RECORDING
30 -}
31 -
32 -export interface PlayerVolume {
33 - mode: ModeDemucs,
34 - volume: string
35 -}
36 -
37 -export function PlayerAudio() {
38 -
39 - const log = useLogger('PlayerAudio')
40 - const { providerId } = useParams()
41 - const { user } = useContext(UserContext)
42 -
43 - const [state, setState] = useState(State.LOADING)
44 - const [title, setTitle] = useState<string>('Loading audio ...')
45 - const [isCountingCountdown, setIsCountingCountdown] = useState(false)
46 - const [recordingLength, setRecordingLength] = useState<number>(0)
47 - const [hideVolumeSliders, setHideVolumeSliders] = useState(false)
48 - const [playerState, setPlayerState] = useState<PlayerState>(PlayerState.STOPPED)
49 -
50 - // Volumes
51 - const [vocalsVolume, setVocalsVolume] = useState<string>('100')
52 - const [drumsVolume, setDrumsVolume] = useState<string>('100')
53 - const [bassVolume, setBassVolume] = useState<string>('100')
54 - const [otherVolume, setOtherVolume] = useState<string>('100')
55 -
56 - const COUNTING_DOWN_NUMBER = 3
57 -
58 - const _onVolumeChange = (mode: ModeDemucs, value: string) => {
59 - log.debug('_onVolumeChange', mode, value)
60 - switch (mode) {
61 - case ModeDemucs.Vocals:
62 - setVocalsVolume(value)
63 - break;
64 - case ModeDemucs.Drums:
65 - setDrumsVolume(value)
66 - break;
67 - case ModeDemucs.Bass:
68 - setBassVolume(value)
69 - break;
70 - case ModeDemucs.Other:
71 - setOtherVolume(value)
72 - break;
73 - }
74 - invoke(TAURI_PLAYER_SET_VOLUME, { mode: mode, volume: value })
75 - }
76 -
77 - const _playerVolumes = (): PlayerVolume[] => {
78 - return [
79 - { mode: ModeDemucs.Vocals, volume: vocalsVolume },
80 - { mode: ModeDemucs.Drums, volume: drumsVolume },
81 - { mode: ModeDemucs.Bass, volume: bassVolume },
82 - { mode: ModeDemucs.Other, volume: otherVolume },
83 - ]
84 - }
85 -
86 - const _toggleRecording = async () => {
87 - try {
88 - // If we are playing playbacks, stop it before recording.
89 - if (playerState === PlayerState.PLAYING) {
90 - setPlayerState(PlayerState.STOPPED)
91 - const result = await invoke(TAURI_PLAYER_STOP, { audioId: providerId, userId: user?.user.id })
92 - log.debug(TAURI_PLAYER_STOP, result)
93 - }
94 -
95 - // If we're already recording, we want to stop it and return.
96 - if (playerState === PlayerState.RECORDING) {
97 - _stopRecording()
98 - return
99 - }
100 -
101 - // Otherwise, we're good to go.
102 - // Get the length of the audio file.
103 -
104 - let length: number = await invoke(TAURI_PLAYER_RECORDING_LENGTH, { audioId: providerId, userId: user?.user.id })
105 - log.debug(TAURI_PLAYER_RECORDING_LENGTH, length)
106 - setRecordingLength(length)
107 - setIsCountingCountdown(true)
108 -
109 - setTimeout(async () => {
110 - log.debug(TAURI_PLAYER_RECORD, 'Start recording')
111 - setPlayerState(PlayerState.RECORDING)
112 - setIsCountingCountdown(false)
113 - const result = await invoke(TAURI_PLAYER_RECORD, { audioId: providerId, userId: user?.user.id, playerVolumes: _playerVolumes() })
114 - log.debug(TAURI_PLAYER_RECORD, result)
115 - }, COUNTING_DOWN_NUMBER * 1000)
116 - } catch (error) {
117 - log.error(error)
118 - }
119 - }
120 -
121 - const _stopRecording = async () => {
122 - setRecordingLength(0)
123 - setPlayerState(PlayerState.STOPPED)
124 - const stop_recording_result = await invoke(TAURI_PLAYER_RECORD_STOP, { audioId: providerId, userId: user?.user.id })
125 - const result = await invoke(TAURI_PLAYER_STOP, { audioId: providerId, userId: user?.user.id })
126 - log.debug(TAURI_PLAYER_STOP, result)
127 - log.debug(TAURI_PLAYER_RECORD_STOP, stop_recording_result)
128 - }
129 -
130 - const _stopPlayer = async () => {
131 - try {
132 - setPlayerState(PlayerState.STOPPED)
133 - setRecordingLength(0)
134 - const result = await invoke(TAURI_PLAYER_STOP, { audioId: providerId, userId: user?.user.id })
135 - log.debug(TAURI_PLAYER_STOP, result)
136 - } catch (error) {
137 - log.error(error)
138 - }
139 - }
140 -
141 - const _togglePlayAudio = async () => {
142 - // Should disabled when recording
143 - if (playerState === PlayerState.RECORDING) {
144 - return
145 - }
146 - try {
147 - switch (playerState) {
148 - case PlayerState.STOPPED:
149 - log.debug(TAURI_PLAYER_PLAY, 'Start playing')
150 - setIsCountingCountdown(true)
151 - setTimeout(async () => {
152 - setIsCountingCountdown(false)
153 - setPlayerState(PlayerState.PLAYING)
154 - await invoke(TAURI_PLAYER_PLAY, { playerVolumes: _playerVolumes() })
155 - }, COUNTING_DOWN_NUMBER * 1000)
156 - break;
157 - case PlayerState.PLAYING:
158 - log.debug(TAURI_PLAYER_PAUSED, 'Paused')
159 - setPlayerState(PlayerState.PAUSED)
160 - await invoke(TAURI_PLAYER_PAUSED)
161 - break;
162 - case PlayerState.PAUSED:
163 - log.debug(TAURI_PLAYER_RESUMED, 'Resumed')
164 - setPlayerState(PlayerState.PLAYING)
165 - await invoke(TAURI_PLAYER_PLAY, { playerVolumes: _playerVolumes() })
166 - }
167 - } catch (error) {
168 - log.error(error)
169 - }
170 - }
171 -
172 - const preparePlayer = useCallback(async () => {
173 - if (!providerId) {
174 - log.error('providerId is null')
175 - setState(State.ERROR)
176 - return
177 - }
178 - log.debug('preparePlayer', providerId)
179 - try {
180 - const result: PlayerPrepareResponse = await invoke(TAURI_PLAYER_PREPARE, { providerId: providerId })
181 - log.debug('PreparePlayerResponse', result)
182 - switch (result.status) {
183 - case TauriResponse.SUCCESS:
184 - if (result.audio_file_name)
185 - setTitle(result.audio_file_name)
186 -
187 - setState(State.LOADED)
188 - break;
189 - case TauriResponse.ERROR:
190 - setState(State.ERROR)
191 - break;
192 - }
193 - } catch (error) {
194 - log.error(error)
195 - setState(State.ERROR)
196 - }
197 - }, [])
198 -
199 - const onFinishedRecording = () => {
200 - log.debug('Recording finished')
201 - //_toggleRecording()
202 - }
203 -
204 - const _willUnmount = () => {
205 - try {
206 - const result = invoke(TAURI_PLAYER_UNMOUNT, { audioId: providerId, userId: user?.user.id })
207 - log.debug(TAURI_PLAYER_UNMOUNT, result)
208 - }
209 - catch (error) {
210 - log.error(error)
211 - }
212 - }
213 -
214 - useEffect(() => {
215 - preparePlayer()
216 -
217 - return () => {
218 - _willUnmount()
219 - }
220 - }, [])
221 -
222 - if (state === State.LOADING) {
223 - return <LoadingView />
224 - }
225 -
226 - if (state === State.ERROR) {
227 - return <ErrorView errorMessage="Something went wrong ..." />
228 - }
229 -
230 - let buttonOrCounting = <></>
231 - if (isCountingCountdown) {
232 - buttonOrCounting = <CountDownView seconds={COUNTING_DOWN_NUMBER} type="" />
233 - } else {
234 - buttonOrCounting = <ControlButtonsView
235 - isPlaying={playerState === PlayerState.PLAYING}
236 - isRecording={playerState === PlayerState.RECORDING}
237 - onClick={_togglePlayAudio}
238 - onStop={_stopPlayer}
239 - onRecord={_toggleRecording}
240 - />
241 - }
242 -
243 - let audioWaveform = <></>
244 - if (playerState === PlayerState.PLAYING || playerState === PlayerState.PAUSED) {
245 - audioWaveform = <>
246 - <CountDownView seconds={recordingLength} type="Practicing!!!" />
247 - </>
248 - } else if (playerState === PlayerState.RECORDING) {
249 - audioWaveform = <RecordingView length={recordingLength} onRecordingEnd={onFinishedRecording} />
250 - }
251 -
252 - return <Container>
253 - <Row>
254 - <Col xs={12} className="mb-3 mt-3">
255 - <h1>{title}</h1>
256 - </Col>
257 - </Row>
258 - {buttonOrCounting}
259 - <Row className="h-100 d-inline-block">
260 - <Col xs={12} className="mb-3 mt-3">
261 - <Container style={{ height: 300 }}>
262 - <div className="d-flex align-items-center justify-content-center h-100">
263 - <div className="d-flex flex-column">
264 - {audioWaveform}
265 - </div>
266 - </div>
267 - </Container>
268 - </Col>
269 - </Row>
270 - <Row className="border border-light">
271 - <Col xs={2} className="mb-3 mt-3">
272 - <p color="red">Volume</p>
273 - </Col>
274 - <Col xs={{ span: 2, offset: 8 }} className="mb-3 mt-3">
275 - <HideShowToggleView hideVolumeSliders={hideVolumeSliders} setHideVolumeSliders={setHideVolumeSliders} />
276 - </Col>
277 - <VolumeSliderView _onVolumeChange={_onVolumeChange} isHidden={hideVolumeSliders} />
278 - </Row>
279 - </Container>
280 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/player/PlayerView.tsx deleted
-28
@@ -1,28 +0,0 @@
1 -import { useContext } from "react";
2 -import { Navigate, Outlet, useParams } from "react-router-dom";
3 -import { UserContext } from "../../lib/CurrentUserContext";
4 -import { Col, Container, Row } from "react-bootstrap";
5 -import ErrorView from "../templates/Error";
6 -
7 -export function PlayerView() {
8 -
9 - const { providerId } = useParams()
10 - const { user } = useContext(UserContext)
11 -
12 - if (!user) {
13 - return <Navigate to={'/login'} />
14 - }
15 -
16 - if (user && providerId) {
17 - return <Container>
18 - <Row>
19 - <Col xs={12} className="mb-3 mt-3">
20 - <h1>Let's play!</h1>
21 - </Col>
22 - </Row>
23 - <Outlet context={{ user }} />
24 - </Container>
25 - }
26 -
27 - return <ErrorView errorMessage="Something went wrong ..." />
28 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/player/RecordingView.tsx deleted
-9
@@ -1,9 +0,0 @@
1 -import { CountDownView } from "./CountDownView"
2 -
3 -export function RecordingView(props: { length: number, onRecordingEnd: () => void }) {
4 - const { length, onRecordingEnd } = props
5 - return <div>
6 - <h1>Recording...</h1>
7 - <CountDownView seconds={length} type="" onCountDownEnd={onRecordingEnd} />
8 - </div>
9 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/player/Song.tsx deleted
-7
@@ -1,7 +0,0 @@
1 -import { Outlet } from "react-router-dom";
2 -
3 -export default function Song() {
4 - return (
5 - <Outlet />
6 - )
7 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/player/VolumeSliderView.tsx deleted
-28
@@ -1,28 +0,0 @@
1 -import { Container, Row, Col, Form } from "react-bootstrap"
2 -import { ModeDemucs } from "./models/Mode"
3 -
4 -export function VolumeSliderView(props: { _onVolumeChange: (mode: ModeDemucs, value: string) => void, isHidden: boolean }) {
5 -
6 - const { _onVolumeChange, isHidden } = props
7 -
8 - return <Container hidden={isHidden} className="mb-3">
9 - <Row>
10 - <Col xs={3}>
11 - <p>Vocals</p>
12 - <Form.Range onChange={e => _onVolumeChange(ModeDemucs.Vocals, e.target.value) } defaultValue={100}/>
13 - </Col>
14 - <Col xs={3}>
15 - <p>Drums</p>
16 - <Form.Range onChange={e => _onVolumeChange(ModeDemucs.Drums, e.target.value) } defaultValue={100} />
17 - </Col>
18 - <Col xs={3}>
19 - <p>Bass</p>
20 - <Form.Range onChange={e => _onVolumeChange(ModeDemucs.Bass, e.target.value) } defaultValue={100} />
21 - </Col>
22 - <Col xs={3}>
23 - <p>Other</p>
24 - <Form.Range onChange={e => _onVolumeChange(ModeDemucs.Other, e.target.value) } defaultValue={100} />
25 - </Col>
26 - </Row>
27 - </Container>
28 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/player/models/YouTubeEventTarget.tsx deleted
-10
@@ -1,10 +0,0 @@
1 -import { YouTubePlayerState } from "./YouTubePlayerState";
2 -
3 -export interface YouTubeEventTarget {
4 - pauseVideo: () => void,
5 - playVideo: () => void,
6 - seekTo: (arg0: number) => void,
7 - getPlayerState: () => YouTubePlayerState,
8 - getCurrentTime: () => number
9 -
10 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/player/models/YouTubePlayerState.tsx deleted
-4
@@ -1,4 +0,0 @@
1 -// https://developers.google.com/youtube/iframe_api_reference#Playback_status
2 -export enum YouTubePlayerState {
3 - unstarted = -1, ended, playing, paused, buffering, videoCued
4 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/profiles/BackView.tsx deleted
-24
@@ -1,24 +0,0 @@
1 -import { Row, Col } from "react-bootstrap"
2 -import { BsArrowLeft } from "react-icons/bs"
3 -import { Link } from "react-router-dom"
4 -import User, { usernameOrId } from "../../models/user"
5 -
6 -interface BackViewProps {
7 - user: User
8 -}
9 -
10 -export default function BackView(props: BackViewProps) {
11 - return (
12 - <Row className="mb-2">
13 - <Col xs={1} className="text-start">
14 - <Link to={`/${usernameOrId(props.user)}`}>
15 - <BsArrowLeft></BsArrowLeft>
16 - </Link>
17 - </Col>
18 - <Col xs={11} className="text-start">
19 - <div className="">{`${props.user.name}`}</div>
20 - <div className="">{`${usernameOrId(props.user)}`}</div>
21 - </Col>
22 - </Row>
23 - )
24 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/profiles/Login.tsx deleted
-140
@@ -1,140 +0,0 @@
1 -import axios from "axios"
2 -import { Component } from "react"
3 -import { Alert, Button, Col, Container, Form, Row } from "react-bootstrap"
4 -import { Navigate } from "react-router-dom"
5 -import { CurrentUser, CurrentUserType, db } from "../../lib/db"
6 -import User, { ProfileResponse, usernameOrId } from '../../models/user'
7 -import { Mode } from "../player/models/Mode"
8 -import { UserContext } from "../../lib/CurrentUserContext"
9 -
10 -interface LoginProps { }
11 -
12 -interface LoginState {
13 - user: User | null
14 - usernameOrEmail: string
15 - password: string
16 - errorMessage: string
17 - processing: boolean
18 -}
19 -
20 -export default class Login extends Component<LoginProps, LoginState> {
21 -
22 - static contextType = UserContext
23 -
24 - constructor(props: LoginProps) {
25 - super(props)
26 - this.state = {
27 - user: null,
28 - usernameOrEmail: '',
29 - password: '',
30 - errorMessage: '',
31 - processing: false
32 - }
33 - }
34 -
35 - componentDidMount() {
36 - const user = this.context.user
37 - if (user) {
38 - this.setState({ user: user })
39 - }
40 - }
41 -
42 - login(update: (user: CurrentUser) => void) {
43 - //console.log(this.state)
44 - this.setState({ processing: true })
45 - axios
46 - .post(`/login`, {
47 - username: this.state.usernameOrEmail,
48 - password: this.state.password
49 - })
50 - .then(res => {
51 - const response: ProfileResponse = res.data
52 - if (response.code === 200) {
53 - let currentUser: CurrentUser = {
54 - accessToken: res.headers.authorization,
55 - type: CurrentUserType.MAIN,
56 - user: response.user,
57 - mode: Mode.Vocal
58 - }
59 - db.currentUser.add(currentUser)
60 - this.setState({
61 - user: response.user
62 - })
63 - update(currentUser)
64 - //this.props.didLogin(response.user)
65 - } else {
66 - //console.log(res)
67 - this.setState({
68 - processing: false,
69 - errorMessage: response.message
70 - })
71 - }
72 - })
73 - .catch(error => {
74 - //console.log(error)
75 - this.setState({
76 - processing: false,
77 - errorMessage: 'Unkown error.'
78 - })
79 - })
80 - }
81 -
82 - _buttonTitle(): string {
83 - if (this.state.processing) {
84 - return 'Logging in...'
85 - }
86 - return 'Submit'
87 - }
88 -
89 - render() {
90 - const { user, errorMessage } = this.state
91 -
92 - if (user) {
93 - return <Navigate to={`/${usernameOrId(user)}`} />
94 - }
95 -
96 - let validationText: any = ''
97 - if (errorMessage.length > 0) {
98 - validationText = <Alert variant="danger" onClose={() => this.setState({ errorMessage: '' })} dismissible>
99 - <p>{errorMessage}</p>
100 - <p><a href={`${process.env.REACT_APP_MUSIK88_BASE_URL}/users/password/new`} target="__blank">Forget password?</a></p>
101 - </Alert >
102 - }
103 -
104 - return (
105 - <UserContext.Consumer>
106 - {({ user, updateUser: update }) => (
107 - <Row className="justify-content-md-center">
108 - <Col>
109 - <Container>
110 - <Form>
111 - {validationText}
112 - <Form.Group className="mb-3" controlId="formBasicEmail">
113 - <Form.Label>Email address</Form.Label>
114 - <Form.Control type="email" placeholder="Enter email" value={this.state.usernameOrEmail} onChange={(e) => this.setState({ usernameOrEmail: e.currentTarget.value })} />
115 - </Form.Group>
116 - <Form.Group className="mb-3" controlId="formBasicPassword">
117 - <Form.Label>Password</Form.Label>
118 - <Form.Control type="password" placeholder="Password" value={this.state.password} onChange={(e) => this.setState({ password: e.currentTarget.value })} />
119 - </Form.Group>
120 - <Button variant="primary" onClick={() => this.login(update)} disabled={this.state.processing}>
121 - {this._buttonTitle()}
122 - </Button>
123 - <p>or</p>
124 - <Form.Group className="mb-3">
125 - <a href={`${process.env.REACT_APP_MUSIK88_BASE_URL}/signup`} target={`__blank`}>
126 - <Button variant="success">
127 - Register
128 - </Button>
129 - </a>
130 - <p>Account managed by Musik88.</p>
131 - </Form.Group>
132 - </Form>
133 - </Container>
134 - </Col>
135 - </Row>
136 - )}
137 - </UserContext.Consumer>
138 - )
139 - }
140 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/profiles/Logout.tsx deleted
-39
@@ -1,39 +0,0 @@
1 -import { useContext, useEffect } from "react"
2 -import { Spinner } from "react-bootstrap"
3 -import axios from "../../lib/axios"
4 -import { UserContext } from "../../lib/CurrentUserContext"
5 -
6 -interface LogoutProps {
7 - didLogout: () => void
8 -}
9 -
10 -export default function Logout(props: LogoutProps) {
11 -
12 - const currentUser = useContext(UserContext)
13 -
14 - useEffect(() => {
15 - console.log(currentUser)
16 - if (currentUser.user?.accessToken) {
17 - axios
18 - .delete(`/logout`, {
19 - headers: {
20 - 'Authorization': currentUser.user.accessToken
21 - }
22 - })
23 - .then(res => {
24 - props.didLogout()
25 - })
26 - .catch(error => {
27 - console.log(error)
28 - })
29 - }
30 - })
31 -
32 - return (
33 - <div>
34 - <Spinner animation="border" role="status">
35 - <span className="visually-hidden">Loading...</span>
36 - </Spinner>
37 - </div>
38 - )
39 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/profiles/Profile.tsx deleted
-137
@@ -1,137 +0,0 @@
1 -import axios from "axios"
2 -import { Component } from "react"
3 -import { Col, Container, Row, Spinner } from "react-bootstrap"
4 -import { useParams } from "react-router-dom"
5 -import { db, CurrentUserType } from "../../lib/db"
6 -import User, { ProfileResponse } from '../../models/user'
7 -import { Mode } from "../player/models/Mode"
8 -import ProfileView from "./ProfileView"
9 -import { UserContext } from "../../lib/CurrentUserContext"
10 -import { Logger } from "tslog"
11 -import { MdBugReport } from "react-icons/md"
12 -import ErrorView from "../templates/Error"
13 -
14 -export function ProfileRootView() {
15 - const { usernameOrId } = useParams()
16 -
17 - if (!usernameOrId) {
18 - return (
19 - <p>Something is wrong...</p>
20 - )
21 - }
22 -
23 - return (
24 - <Profile usernameOrId={usernameOrId} />
25 - )
26 -}
27 -
28 -interface ProfileProps {
29 - usernameOrId: string
30 -}
31 -
32 -interface ProfileState {
33 - user: User | null
34 - isCurrentUser: boolean
35 - errorMessage: string
36 - mode: Mode
37 - isUpdatingMode: boolean
38 -}
39 -
40 -class Profile extends Component<ProfileProps, ProfileState> {
41 -
42 - static contextType = UserContext
43 - log = new Logger({ name: 'Profile', type: 'pretty' })
44 -
45 - constructor(props: ProfileProps) {
46 - super(props)
47 - this.state = {
48 - user: null,
49 - isCurrentUser: false,
50 - errorMessage: '',
51 - mode: Mode.Vocal,
52 - isUpdatingMode: false
53 - }
54 - }
55 -
56 - componentDidUpdate(prevProps: ProfileProps) {
57 - if (this.props.usernameOrId !== prevProps.usernameOrId) {
58 - this._fetchUserDetail()
59 - }
60 - }
61 -
62 - onChangeMode(mode: Mode): Promise<Mode> {
63 - this.log.debug(mode)
64 - this.setState({ isUpdatingMode: true })
65 - return new Promise<Mode>((resolve, reject) => {
66 - db
67 - .currentUser
68 - .where({ type: CurrentUserType.MAIN })
69 - .modify({ mode })
70 - .finally(() => {
71 - this.setState({ isUpdatingMode: false })
72 - resolve(mode)
73 - })
74 - })
75 - }
76 -
77 - componentDidMount() {
78 - this._fetchUserDetail()
79 - }
80 -
81 - _fetchUserDetail() {
82 - axios.get(`/profile/${this.props.usernameOrId}`)
83 - .then(res => {
84 - this.log.debug(res.data)
85 - const response: ProfileResponse = res.data
86 - if (response.code === 200) {
87 - this.setState({ user: response.user })
88 - this._updateCurrentUserState()
89 - } else {
90 - this.setState({ errorMessage: response.message })
91 - }
92 - })
93 - .catch(error => {
94 - this.log.error(error)
95 - this.setState({ errorMessage: 'Something went wrong...' })
96 - })
97 - }
98 -
99 - _updateCurrentUserState() {
100 - const user = this.context.user
101 - const isCurrentUser = user.user.id === this.state.user?.id
102 - this.setState({ isCurrentUser, mode: user?.mode })
103 - }
104 -
105 - render() {
106 -
107 - const { user, isCurrentUser, errorMessage, mode, isUpdatingMode } = this.state
108 - this.log.debug(mode)
109 - let content = <Row>
110 - <Col>
111 - <Spinner animation='grow' variant='danger'></Spinner>
112 - </Col>
113 - </Row>
114 -
115 - if (user) {
116 - content = <ProfileView
117 - user={user}
118 - isCurrentUser={isCurrentUser}
119 - mode={mode}
120 - onChangeMode={this.onChangeMode.bind(this)}
121 - isUpdatingMode={isUpdatingMode}
122 - />
123 - }
124 -
125 - if (errorMessage.length > 0) {
126 - return <ErrorView errorMessage={errorMessage} />
127 - }
128 -
129 - return (
130 - <Container>
131 - {content}
132 - </Container>
133 - )
134 - }
135 -}
136 -
137 -export default Profile
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/profiles/ProfileFollowers.tsx deleted
-75
@@ -1,75 +0,0 @@
1 -import axios from "axios"
2 -import { Component, ReactNode } from "react"
3 -import { Col, Row, Spinner } from "react-bootstrap"
4 -import { useParams } from "react-router-dom"
5 -import User, { FollowersResponse } from "../../models/user"
6 -import ProfileFollowingView from "./ProfileListView"
7 -import TabMenu, { Tab } from "./Tabs"
8 -
9 -export function ProfileFollowersRootView() {
10 - const { usernameOrId } = useParams()
11 -
12 - if (!usernameOrId) {
13 - return (
14 - <p>Something is wrong...</p>
15 - )
16 - }
17 -
18 - return (
19 - <ProfileFollowers usernameOrId={usernameOrId} />
20 - )
21 -}
22 -
23 -interface ProfileFollowersProps {
24 - usernameOrId: string
25 -}
26 -
27 -interface ProfileFollowersState {
28 - user: User | null
29 - followers: User[] | null
30 -}
31 -
32 -class ProfileFollowers extends Component<ProfileFollowersProps, ProfileFollowersState> {
33 -
34 - constructor(props: ProfileFollowersProps) {
35 - super(props)
36 - this.state = {
37 - user: null,
38 - followers: null
39 - }
40 - }
41 -
42 - componentDidMount() {
43 - axios.get(`/profile/${this.props.usernameOrId}/followers`)
44 - .then(res => {
45 - //console.log(res.data)
46 -
47 - const response: FollowersResponse = res.data
48 - if (response.code === 200) {
49 - this.setState({ user: response.user, followers: response.followers })
50 - } else {
51 - //console.log("Error")
52 - }
53 - })
54 - .catch(error => {
55 - //console.log(error)
56 - })
57 - }
58 -
59 - render(): ReactNode {
60 - let content = <Row>
61 - <Col>
62 - <Spinner animation='grow' variant='danger'></Spinner>
63 - </Col>
64 - </Row>
65 -
66 - if (this.state.user && this.state.followers) {
67 - content = <ProfileFollowingView following={this.state.followers} />
68 - }
69 -
70 - return <div>
71 - <TabMenu active={Tab.Followers} usernameOrId={this.props.usernameOrId} />
72 - {content}
73 - </div>
74 - }
75 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/profiles/ProfileFollowing.tsx deleted
-77
@@ -1,77 +0,0 @@
1 -import axios from "axios"
2 -import { Component, ReactNode } from "react"
3 -import { Col, Row, Spinner } from "react-bootstrap"
4 -import { useParams } from "react-router-dom"
5 -import User, { FollowingResponse } from '../../models/user'
6 -import ProfileFollowingView from "./ProfileListView"
7 -import TabMenu, { Tab } from "./Tabs"
8 -
9 -export function ProfileFollowingRootView() {
10 - const { usernameOrId } = useParams()
11 -
12 - if (!usernameOrId) {
13 - return (
14 - <p>Something is wrong...</p>
15 - )
16 - }
17 -
18 - return (
19 - <ProfileFollowing usernameOrId={usernameOrId} />
20 - )
21 -}
22 -
23 -interface ProfileFollowingProps {
24 - usernameOrId: string
25 -}
26 -
27 -interface ProfileFollowingState {
28 - user: User | null
29 - following: User[] | null
30 -}
31 -
32 -class ProfileFollowing extends Component<ProfileFollowingProps, ProfileFollowingState> {
33 -
34 - constructor(props: ProfileFollowingProps) {
35 - super(props)
36 - this.state = {
37 - user: null,
38 - following: null
39 - }
40 - }
41 -
42 - componentDidMount() {
43 - axios.get(`/profile/${this.props.usernameOrId}/following`)
44 - .then(res => {
45 - //console.log(res.data)
46 -
47 - const response: FollowingResponse = res.data
48 - if (response.code === 200) {
49 - this.setState({ user: response.user, following: response.following })
50 - } else {
51 - //console.log('Error')
52 - }
53 - })
54 - .catch(error => {
55 - //console.log(error)
56 - })
57 - }
58 -
59 - render(): ReactNode {
60 - let content = <Row>
61 - <Col>
62 - <Spinner animation='grow' variant='danger'></Spinner>
63 - </Col>
64 - </Row>
65 -
66 - if (this.state.user && this.state.following) {
67 - content = <ProfileFollowingView following={this.state.following} />
68 - }
69 -
70 - return <div>
71 - <TabMenu active={Tab.Following} usernameOrId={this.props.usernameOrId} />
72 - {content}
73 - </div>
74 - }
75 -}
76 -
77 -export default ProfileFollowing
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/profiles/ProfileListView.tsx deleted
-63
@@ -1,63 +0,0 @@
1 -import { Row, Col, Container, Image } from "react-bootstrap"
2 -import { Link } from "react-router-dom"
3 -import User, { usernameOrId } from "../../models/user"
4 -
5 -interface ProfileListViewProps {
6 - key: number
7 - user: User
8 -}
9 -
10 -interface ProfileFollowingViewProps {
11 - following: User[]
12 -}
13 -
14 -export default function ProfileFollowingView(props: ProfileFollowingViewProps) {
15 - let content = <p>Nothing here :(</p>
16 - if (props.following.length > 0) {
17 - content = <div>
18 - {props.following.map( user =>
19 - (<ProfileListView user={user} key={user.id} />)
20 - )}
21 - </div>
22 - }
23 - return (
24 - content
25 - )
26 -}
27 -
28 -function ProfileListView(props: ProfileListViewProps) {
29 - return (
30 - <Row className="m-3">
31 - <Col xs={2}>
32 - <Image roundedCircle thumbnail src={props.user.gravatar_url} sizes={'24x24'}/>
33 - </Col>
34 - <Col xs={10} className="text-start">
35 - <Container>
36 - <Row>
37 - <Col xs={8} className="mt-3 font-weight-bold">
38 - <div>
39 - <Link to={`/${usernameOrId(props.user)}`}>
40 - {props.user.name}
41 - </Link>
42 - </div>
43 - <div>
44 - {`${usernameOrId(props.user)}`}
45 - </div>
46 - </Col>
47 - <Col xs={4} className="mt-3 font-weight-light float-end">
48 - <span className="badge rounded-pill bg-primary">{`${props.user.followers_count}`} Followers</span>
49 - <span className="badge rounded-pill bg-primary">{`${props.user.following_count}`} Following</span>
50 - </Col>
51 - </Row>
52 - </Container>
53 - <Container className="blockquote-footer">
54 - <Row>
55 - <Col>
56 - {props.user.about}
57 - </Col>
58 - </Row>
59 - </Container>
60 - </Col>
61 - </Row>
62 - )
63 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/profiles/ProfileNetworks.tsx deleted
-79
@@ -1,79 +0,0 @@
1 -import axios from "axios"
2 -import { Component, ReactNode } from "react"
3 -import { Row, Col, Spinner, Container } from "react-bootstrap"
4 -import { useParams, Outlet } from "react-router-dom"
5 -import User, { ProfileResponse } from "../../models/user"
6 -import BackView from "./BackView"
7 -import { Tab } from "./Tabs"
8 -
9 -export function ProfileNetworksRootView() {
10 - const { usernameOrId } = useParams()
11 -
12 - if (!usernameOrId) {
13 - return (
14 - <p>Something is wrong...</p>
15 - )
16 - }
17 - return (
18 - <ProfileNetworksView usernameOrId={usernameOrId} />
19 - )
20 -}
21 -
22 -interface ProfileNetworksProps {
23 - usernameOrId: string
24 -}
25 -
26 -interface ProfileNetworksState {
27 - user: User | null
28 - tab: Tab | null
29 - isError: boolean
30 -}
31 -
32 -class ProfileNetworksView extends Component<ProfileNetworksProps, ProfileNetworksState> {
33 -
34 - constructor(props: ProfileNetworksProps) {
35 - super(props)
36 - this.state = {
37 - user: null,
38 - tab: null,
39 - isError: false
40 - }
41 - }
42 -
43 - componentDidMount() {
44 - axios.get(`/profile/${this.props.usernameOrId}`)
45 - .then(res => {
46 - //console.log(res.data)
47 - const response: ProfileResponse = res.data
48 - if (response.code === 200) {
49 - this.setState({ user: response.user })
50 - } else {
51 - //console.log("Error")
52 - this.setState({ isError: true })
53 - }
54 - })
55 - .catch(error => {
56 - //console.log(error)
57 - this.setState({ isError: true })
58 - })
59 - }
60 -
61 - render(): ReactNode {
62 - const { user } = this.state
63 -
64 - let content = <Row>
65 - <Col>
66 - <Spinner animation='grow' variant='danger'></Spinner>
67 - </Col>
68 - </Row>
69 -
70 - if (user) {
71 - content = <div>
72 - <BackView user={user} />
73 - <Outlet />
74 - </div>
75 - }
76 -
77 - return <Container>{content}</Container>
78 - }
79 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/profiles/ProfileView.tsx deleted
-99
@@ -1,99 +0,0 @@
1 -import { useState } from "react"
2 -import User, { usernameOrId } from "../../models/user"
3 -import { Mode } from "../player/models/Mode"
4 -import { Button, Col, Form, Row, Spinner, Image } from "react-bootstrap"
5 -import { Link } from "react-router-dom"
6 -import { useLogger } from "../../../../lib/logger"
7 -
8 -
9 -interface ProfileViewProps {
10 - user: User
11 - isCurrentUser: boolean
12 - isUpdatingMode: boolean
13 - mode: Mode
14 - onChangeMode: (mode: Mode) => Promise<Mode>
15 -}
16 -
17 -export default function ProfileView(props: ProfileViewProps) {
18 -
19 - const log = useLogger('ProfileView')
20 - const [updatingMode, setUpdatingMode] = useState(props.isUpdatingMode)
21 - const [currentMode, setCurrentMode] = useState(props.mode)
22 -
23 - log.debug(props.mode)
24 - log.debug(currentMode)
25 -
26 - const modeChangeHander = (stringMode: string) => {
27 - setUpdatingMode(true)
28 - props
29 - .onChangeMode(stringMode as Mode)
30 - .then(mode => {
31 - setUpdatingMode(false)
32 - setCurrentMode(mode)
33 - })
34 - }
35 -
36 - let logoutButton
37 -
38 - if (props.isCurrentUser) {
39 - logoutButton = <Col xs={12} className="mt-3 font-weight-bold">
40 - <Link to={'/logout'} ><Button variant="danger">Logout</Button></Link>
41 - </Col>
42 - }
43 - let modeView
44 - if (!updatingMode && props.isCurrentUser) {
45 - let options = []
46 - for (const value in Mode) {
47 - if (typeof value !== "string") {
48 - continue;
49 - }
50 - if (value === 'Keyboard') {
51 - continue;
52 - }
53 - options.push(<option value={value} key={value}> {value}</option >)
54 - }
55 - modeView = <Col xs={{ span: 4, offset: 4 }} className="mt-3">
56 - <Form.Select onChange={(e) => modeChangeHander(e.currentTarget.value)} value={currentMode}>
57 - {options}
58 - </Form.Select>
59 - </Col>
60 - }
61 - if (updatingMode) {
62 - modeView = <Col xs={{ span: 4, offset: 4 }} className="mt-3">
63 - <Spinner animation='grow' variant='danger'></Spinner>
64 - </Col>
65 - }
66 -
67 - return (
68 - <div className="mt-3">
69 - <Row>
70 - <Col xs={12}>
71 - <Image roundedCircle thumbnail src={props.user.gravatar_url} />
72 - </Col>
73 - <Col xs={12} className="mt-3 font-weight-bold">
74 - {props.user.name}
75 - </Col>
76 - <Col xs={12} className="mt-3 font-weight-light">
77 - {usernameOrId(props.user)}
78 - </Col>
79 - {modeView}
80 - <Col xs={12} className="mt-3 blockquote-footer">
81 - {props.user.about}
82 - </Col>
83 - {logoutButton}
84 - </Row>
85 - <Row className="mt-3">
86 - <Col xs={6}>
87 - <Link to={`/${usernameOrId(props.user)}/followers`} className="text-white" aria-current="page">
88 - {props.user.followers_count} Followers
89 - </Link>
90 - </Col>
91 - <Col xs={6}>
92 - <Link to={`/${usernameOrId(props.user)}/following`} className="text-white" aria-current="page">
93 - {props.user.following_count} Following
94 - </Link>
95 - </Col>
96 - </Row>
97 - </div>
98 - )
99 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/profiles/Tabs.tsx deleted
-34
@@ -1,34 +0,0 @@
1 -import { Link } from "react-router-dom"
2 -
3 -export enum Tab {
4 - Following = 0,
5 - Followers
6 -}
7 -
8 -interface TabMenuProps {
9 - active: Tab
10 - usernameOrId: string
11 -}
12 -
13 -export default function TabMenu(props: TabMenuProps) {
14 - return (
15 - <ul className={'nav nav-tabs nav-justified'}>
16 - <li className={'nav-item'}>
17 - <Link
18 - to={`/@${props.usernameOrId}/followers`}
19 - className={`nav-link${props.active === Tab.Followers ? ' active' : ''}`}
20 - aria-current="page">
21 - Followers
22 - </Link>
23 - </li>
24 - <li className={'nav-item'}>
25 - <Link
26 - to={`/@${props.usernameOrId}/following`}
27 - className={`nav-link${props.active === Tab.Following ? ' active' : ''}`}
28 - aria-current="page">
29 - Following
30 - </Link>
31 - </li>
32 - </ul>
33 - )
34 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/templates/Error.tsx deleted
-15
@@ -1,15 +0,0 @@
1 -import { Container, Row, Col } from "react-bootstrap";
2 -import { MdBugReport } from "react-icons/md";
3 -
4 -export default function ErrorView(props: { errorMessage: string }) {
5 - return (
6 - <Container>
7 - <Row>
8 - <Col>
9 - <MdBugReport size={50} color="red" />
10 - <p>{props.errorMessage}</p>
11 - </Col>
12 - </Row>
13 - </Container>
14 - )
15 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/templates/Footer.tsx deleted
-27
@@ -1,27 +0,0 @@
1 -import React from 'react'
2 -import { Col, Row } from 'react-bootstrap'
3 -import { Link } from 'react-router-dom'
4 -
5 -const year = (new Date()).getFullYear();
6 -
7 -export default function Footer() {
8 - return (
9 - <footer className="mt-5 text-white-50">
10 - <Row>
11 - <Col xs={6}>
12 - <p className='float-start'>© {year}</p>
13 - </Col>
14 - <Col xs={6}>
15 - <ul className="list-inline float-end">
16 - <li className='list-inline-item'>
17 - <Link to='/about' aria-current="page">About</Link>
18 - </li>
19 - <li className='list-inline-item'>
20 - <Link to='/contact' aria-current="page">Contact</Link>
21 - </li>
22 - </ul>
23 - </Col>
24 - </Row>
25 - </footer>
26 - )
27 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/templates/Header.tsx deleted
-53
@@ -1,53 +0,0 @@
1 -import React from 'react'
2 -import { Component } from 'react'
3 -import { Link, Location } from 'react-router-dom'
4 -import 'dexie-observable'
5 -import { usernameOrId } from '../../models/user'
6 -import { Badge, Row } from 'react-bootstrap'
7 -import { UserContext } from '../../lib/CurrentUserContext'
8 -
9 -interface HeaderProps {
10 - location: Location
11 -}
12 -
13 -interface HeaderState { }
14 -
15 -export default class Header extends Component<HeaderProps, HeaderState> {
16 -
17 - constructor(props: HeaderProps) {
18 - super(props)
19 - this.state = {}
20 - }
21 -
22 - _isActive(path: string): string {
23 - if (this.props.location.pathname === path)
24 - return ' active'
25 - return ''
26 - }
27 -
28 - render(): React.ReactNode {
29 - return (
30 - <UserContext.Consumer>
31 - {({ user }) => (
32 - <header className="mb-5">
33 - <Row className="border border-light">
34 - <h3 className="float-md-start mb-0">
35 - <Link to='/' className={`text-white nav-link${this._isActive('/about')}`} aria-current="page">
36 - <span className=''>SplitFire AI </span>
37 - <Badge bg="warning" text="dark" className='fst-italic fw-light'>beta</Badge>
38 - </Link>
39 - </h3>
40 - <nav className="nav nav-masthead justify-content-center float-md-end mb-3">
41 - <Link to='/' className={`nav-link${this._isActive('/')}`} aria-current="page">Home</Link>
42 - {
43 - user?.user ? <Link to={`/${usernameOrId(user?.user)}`} className={`nav-link`} aria-current="page">@{usernameOrId(user.user)}</Link>
44 - : <Link to='/login' className={`nav-link${this._isActive('/login')}`} aria-current="page">Login</Link>
45 - }
46 - </nav>
47 - </Row>
48 - </header>
49 - )}
50 - </UserContext.Consumer>
51 - )
52 - }
53 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/templates/LoadingView.tsx deleted
-12
@@ -1,12 +0,0 @@
1 -import { Col, Container, Spinner } from "react-bootstrap";
2 -
3 -export function LoadingView() {
4 -
5 - return <Container>
6 - <Col className="align-self-center" xs={12}>
7 - <Spinner animation="grow" role="status" variant="danger">
8 - <span className="visually-hidden">Loading...</span>
9 - </Spinner>
10 - </Col>
11 - </Container>
12 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/components/templates/Main.tsx deleted
-22
@@ -1,22 +0,0 @@
1 -import { Outlet, useLocation } from 'react-router-dom'
2 -import Header from './Header'
3 -import Footer from './Footer'
4 -
5 -export default function Main() {
6 -
7 - const location = useLocation()
8 -
9 - return (
10 - <div className="main-container d-flex text-center text-white bg-dark">
11 - <div className="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
12 - <main className="px-3 h-100">
13 - <Header location={location} />
14 - <div className="flex-wrapper">
15 - <Outlet />
16 - <Footer />
17 - </div>
18 - </main>
19 - </div>
20 - </div>
21 - )
22 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/index.css deleted
-13
@@ -1,13 +0,0 @@
1 -body {
2 - margin: 0;
3 - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 - sans-serif;
6 - -webkit-font-smoothing: antialiased;
7 - -moz-osx-font-smoothing: grayscale;
8 -}
9 -
10 -code {
11 - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 - monospace;
13 -}
frontend/splitfire-desktop/app/_src/index.tsx deleted
-23
@@ -1,23 +0,0 @@
1 -import { render } from 'react-dom';
2 -import './index.css';
3 -import * as Sentry from "@sentry/react";
4 -import { BrowserTracing } from "@sentry/tracing";
5 -import App from './App';
6 -import reportWebVitals from './reportWebVitals';
7 -
8 -Sentry.init({
9 - dsn: "https://35835e5db0a1497daa344e7cbff13641@o933906.ingest.sentry.io/6685071",
10 - integrations: [new BrowserTracing()],
11 -
12 - // Set tracesSampleRate to 1.0 to capture 100%
13 - // of transactions for performance monitoring.
14 - // We recommend adjusting this value in production
15 - tracesSampleRate: process.env.NODE_ENV === "production" ? 1.0 : 0,
16 -});
17 -
18 -render(<App />, document.getElementById('root'));
19 -
20 -// If you want to start measuring performance in your app, pass a function
21 -// to log results (for example: reportWebVitals(console.log))
22 -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
23 -reportWebVitals();
frontend/splitfire-desktop/app/_src/lib/axios.ts deleted
-12
@@ -1,12 +0,0 @@
1 -import axios from "axios";
2 -
3 -let host = process.env.REACT_APP_SPLITFIRE_API_HOST;
4 -
5 -const client = axios.create({
6 - baseURL: `https://${host}/api/v1/`,
7 - headers: {
8 - "Content-Type": "application/json",
9 - },
10 -});
11 -
12 -export default client;
frontend/splitfire-desktop/app/_src/lib/isTauri.ts deleted
-11
@@ -1,11 +0,0 @@
1 -declare const window: Window & { __TAURI__: any }
2 -
3 -const isTauri = () => {
4 - return Boolean(
5 - typeof window !== 'undefined' &&
6 - window !== undefined &&
7 - window.__TAURI__ !== undefined
8 - )
9 -}
10 -
11 -export default isTauri
\ No newline at end of file
frontend/splitfire-desktop/app/_src/lib/requestAudioFiles.ts deleted
-5
@@ -1,5 +0,0 @@
1 -import axios from "./axios"
2 -
3 -export default function requestAudioFiles(audioFileId: string): Promise<any> {
4 - return axios.get(`/splitfire/${audioFileId}`)
5 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/lib/requestSplitService.ts deleted
-18
@@ -1,18 +0,0 @@
1 -import { AudioFile } from "../components/player/models/AudioFile"
2 -import axios from "./axios"
3 -
4 -export default function requestSplitService(songProviderId: number, accessToken: string): Promise<any> {
5 - return axios.post(`/split`, { provider_id: songProviderId },
6 - {
7 - headers: {
8 - 'Content-Type': 'application/json',
9 - 'Authorization': accessToken,
10 - }
11 - })
12 -}
13 -
14 -export interface SplitResponse {
15 - code: number
16 - audio_file: AudioFile
17 - message: string
18 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/logo.svg deleted
-1
@@ -1 +0,0 @@
1 -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
\ No newline at end of file
frontend/splitfire-desktop/app/_src/reportWebVitals.js deleted
-13
@@ -1,13 +0,0 @@
1 -const reportWebVitals = onPerfEntry => {
2 - if (onPerfEntry && onPerfEntry instanceof Function) {
3 - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 - getCLS(onPerfEntry);
5 - getFID(onPerfEntry);
6 - getFCP(onPerfEntry);
7 - getLCP(onPerfEntry);
8 - getTTFB(onPerfEntry);
9 - });
10 - }
11 -};
12 -
13 -export default reportWebVitals;
frontend/splitfire-desktop/app/_src/setupTests.js deleted
-5
@@ -1,5 +0,0 @@
1 -// jest-dom adds custom jest matchers for asserting on DOM nodes.
2 -// allows you to do things like:
3 -// expect(element).toHaveTextContent(/react/i)
4 -// learn more: https://github.com/testing-library/jest-dom
5 -import '@testing-library/jest-dom';
frontend/splitfire-desktop/app/_src/translations/de.json deleted
-19
@@ -1,19 +0,0 @@
1 -{
2 - "header.home": "Heim",
3 - "header.about": "Etwa",
4 - "header.cv": "CV",
5 - "header.contact": "Kontakt",
6 - "header.fun": "Lustige Projekte",
7 - "home.title": "Hallo, mein Name ist {name}.",
8 - "home.iam": "Ich bin ",
9 - "about.title": "Etwa",
10 - "about.skills": "Fähigkeiten",
11 - "cv.title": "CV",
12 - "contact.title": "Kontakt",
13 - "player.instruction": "Klicken Sie auf das Instrumentensymbol, um es aus dem Song zu entfernen.",
14 - "home.loading": "SplitFire AI wird geladen...",
15 - "player.loadingYoutube": "Warten auf YouTube...",
16 - "footer.toTop": "Zurück nach oben",
17 - "splitfire.whatIs": "Ich bin eine Software für künstliche Intelligenz, die Ihre Lieblingsmusik in ihre einzelnen Komponenten aufteilt.",
18 - "player.loading": "🤖 Ich mache mein Geschäft..."
19 -}
frontend/splitfire-desktop/app/_src/translations/en.json deleted
-20
@@ -1,20 +0,0 @@
1 -{
2 - "header.home": "Home",
3 - "header.about": "About",
4 - "header.cv": "CV",
5 - "header.contact": "Contact",
6 - "header.fun": "Fun projects",
7 - "home.title": "Hello, my name is {name}.",
8 - "home.iam": "I'm ",
9 - "about.title": "About",
10 - "about.skills": "Skills",
11 - "cv.title": "CV",
12 - "contact.title": "Contact",
13 - "player.instruction": "Click the instrument icon to remove it from the song.",
14 - "home.loading": "Loading SplitFire AI...",
15 - "player.loadingYoutube": "Generating video...",
16 - "footer.toTop": "Back to top",
17 - "splitfire.whatIs": "I'm an artificial intelligence software who will split your favorite music to its separate components.",
18 - "player.loading": "🤖 I'm doing my job...",
19 - "song.loading": "🤖 Searching for song..."
20 -}
\ No newline at end of file
frontend/splitfire-desktop/app/_src/translations/es.json deleted
-19
@@ -1,19 +0,0 @@
1 -{
2 - "header.home": "Casa",
3 - "header.about": "Sobre",
4 - "header.cv": "CV",
5 - "header.contact": "Contacto",
6 - "header.fun": "Proyectos divertidos",
7 - "home.title": "Hola, mi nombre es {name}.",
8 - "home.iam": "Estoy ",
9 - "about.title": "Acerca de",
10 - "about.skills": "Habilidades",
11 - "cv.title": "CV",
12 - "contact.title": "Contacto",
13 - "player.instruction": "Haz clic en el ícono del instrumento para eliminarlo de la canción.",
14 - "home.loading": "Cargando SplitFire IA...",
15 - "player.loadingYoutube": "Esperando YouTube...",
16 - "footer.toTop": "Volver al principio",
17 - "splitfire.whatIs": "Soy un software de inteligencia artificial que dividirá tu música favorita en sus componentes separados",
18 - "player.loading": "🤖 Estoy haciendo mi trabajo..."
19 -}
frontend/splitfire-desktop/app/_src/translations/fr.json deleted
-19
@@ -1,19 +0,0 @@
1 -{
2 - "header.home": "Accueil",
3 - "header.about": "Sur",
4 - "header.cv": "CV",
5 - "header.contact": "Contact",
6 - "header.fun": "Projets amusants",
7 - "home.title": "Bonjour, je m'appelle {name}.",
8 - "home.iam": "Je suis ",
9 - "about.title": "Sur",
10 - "about.skills": "Compétences",
11 - "cv.title": "CV",
12 - "contact.title": "Contact",
13 - "player.instruction": "Cliquez sur l'icône de l'instrument pour le supprimer du morceau.",
14 - "home.loading": "Chargement de SplitFire AI...",
15 - "player.loadingYoutube": "En attente de YouTube...",
16 - "footer.toTop": "Retour en haut",
17 - "splitfire.whatIs": "Je suis un logiciel d'intelligence artificielle qui divisera votre musique préférée en ses composants séparés.",
18 - "player.loading": "🤖 Je fais mes affaires..."
19 -}
frontend/splitfire-desktop/app/_src/translations/id.json deleted
-19
@@ -1,19 +0,0 @@
1 - {
2 - "header.home": "Beranda",
3 - "header.about": "Tentang",
4 - "header.cv": "CV",
5 - "header.contact": "Kontak",
6 - "header.fun": "Iseng-iseng berhadiah",
7 - "home.title": "Halo, nama saya {name}.",
8 - "home.iam": "Saya ",
9 - "about.title": "Tentang",
10 - "about.skills": "Keterampilan",
11 - "cv.title": "CV",
12 - "contact.title": "Kontak",
13 - "player.instruction": "Klik ikon instrumen untuk menghapusnya dari lagu.",
14 - "home.loading": "Memuat SplitFire AI...",
15 - "player.loadingYoutube": "Menunggu YouTube...",
16 - "footer.toTop": "Kembali ke atas",
17 - "splitfire.whatIs": "Saya adalah perangkat lunak kecerdasan buatan yang akan membagi musik favorit Anda ke komponen terpisah.",
18 - "player.loading": "🤖 Saya sedang melakukan pekerjaan saya..."
19 - }
frontend/splitfire-desktop/app/_src/translations/ja.json deleted
-19
@@ -1,19 +0,0 @@
1 -{
2 - "header.home": "ホーム",
3 - "header.about": "約",
4 - "header.cv": "CV",
5 - "header.contact": "連絡先",
6 - "header.fun": "楽しいプロジェクト",
7 - "home.title": "こんにちは,私の名前は{name}です。",
8 - "home.iam": "私は",
9 - "about.title": "約",
10 - "about.skills": "スキル",
11 - "cv.title": "CV",
12 - "contact.title": "連絡先",
13 - "player.instruction": "楽器のアイコンをクリックして,曲から削除します。",
14 - "home.loading": "SplitFire AIを読み込んでいます...",
15 - "player.loadingYoutube": "YouTubeを待っています...",
16 - "footer.toTop": "トップに戻る",
17 - "splitfire.whatIs": "私は,お気に入りの音楽を個別のコンポーネントに分割する人工知能ソフトウェアです。",
18 - "player.loading": "🤖私は自分の仕事をしています..."
19 -}
frontend/splitfire-desktop/app/_src/translations/sv.json deleted
-19
@@ -1,19 +0,0 @@
1 - {
2 - "header.home": "Hem",
3 - "header.about": "Om",
4 - "header.cv": "CV",
5 - "header.contact": "Kontakt",
6 - "header.fun": "Roliga projekt",
7 - "home.title": "Tjena, jag heter {name}.",
8 - "home.iam": "Jag ",
9 - "about.title": "Om",
10 - "about.skills": "Kompetens",
11 - "cv.title": "CV",
12 - "contact.title": "Kontakt",
13 - "player.instruction": "Klicka på instrumentikonen för att ta bort den från låten.",
14 - "home.loading": "Laddar SplitFire AI...",
15 - "player.loadingYoutube": "Väntar på YouTube...",
16 - "footer.toTop": "Tillbaka till toppen",
17 - "splitfire.whatIs": "Jag är en programvara för artificiell intelligens som delar upp din favoritmusik till dess separata komponenter.",
18 - "player.loading": "🤖 Jag gör min job..."
19 - }
\ No newline at end of file
frontend/splitfire-desktop/app/_src/translations/zh.json deleted
-19
@@ -1,19 +0,0 @@
1 -{
2 - "header.home": "家",
3 - "header.about": "关于",
4 - "header.cv": "CV",
5 - "header.contact": "接触",
6 - "header.fun": "有趣的项目",
7 - "home.title": "你好我的名字是 {name}.",
8 - "home.iam": "我是",
9 - "about.title": "关于",
10 - "about.skills": "技能",
11 - "cv.title": "CV",
12 - "contact.title": "接触",
13 - "player.instruction": "点击乐器图标将其从歌曲中移除。",
14 - "home.loading": "正在加载 SplitFire AI...",
15 - "player.loadingYoutube": "正在等待 YouTube...",
16 - "footer.toTop": "回到顶部",
17 - "splitfire.whatIs": "我是一个人工智能软件,可以将你最喜欢的音乐拆分成不同的组件.",
18 - "player.loading": "🤖 我在做我的事......"
19 -}
frontend/splitfire-desktop/app/_ui/global-nav.tsx
+2 -2
@@ -7,9 +7,9 @@ import { MenuAlt2Icon, XIcon } from '@heroicons/react/solid';
7 import clsx from 'clsx';
8 import { useContext, useState } from 'react';
9 import Image from 'next/image';
10 -import { UserContext } from '../_src/lib/CurrentUserContext';
10 +import { UserContext } from '../../lib/CurrentUserContext';
11 import { useLogger } from '@/lib/logger';
12 -import { PARAMS_USER_ID } from '../profile/page';
12 +import { PARAMS_USER_ID } from '@/app/_lib/params';
13
14 export function GlobalNav() {
15
frontend/splitfire-desktop/app/_ui/skeleton-card.tsx
+1 -1
@@ -1,7 +1,7 @@
1 "use client";
2
3 import clsx from "clsx";
4 -import { SongProvider } from "../_src/models/SongResponse";
4 +import { SongProvider } from "../../models/SongResponse";
5 import Image from "next/image";
6 import { useRouter } from "next/navigation";
7
frontend/splitfire-desktop/app/debug/page.tsx
+1 -1
@@ -5,7 +5,7 @@ import { invoke } from "@tauri-apps/api/tauri";
5 import {
6 TAURI_GET_ENVIRONMENT,
7 TAURI_SET_ENVIRONMENT,
8 -} from "../_src/lib/tauriHandler";
8 +} from "../../lib/tauriHandler";
9 import { Button } from "../_ui/components/button";
10 import { useEffect, useState } from "react";
11 import { LoadingView } from "../_src/components/templates/LoadingView";
frontend/splitfire-desktop/app/layout.tsx
+2 -2
@@ -4,8 +4,8 @@ import { AddressBar } from "./_ui/address-bar";
4 import { GlobalNav } from "./_ui/global-nav";
5 import "./globals.css";
6 import { useEffect, useState } from "react";
7 -import { CurrentUser, CurrentUserType, db } from "./_src/lib/db";
8 -import { UserContext } from "./_src/lib/CurrentUserContext";
7 +import { CurrentUser, CurrentUserType, db } from "../lib/db";
8 +import { UserContext } from "../lib/CurrentUserContext";
9 import { useLogger } from "../lib/logger";
10
11 enum State {
frontend/splitfire-desktop/app/login/page.tsx
+3 -3
@@ -2,11 +2,11 @@
2
3 import * as Form from "@radix-ui/react-form";
4 import { invoke } from "@tauri-apps/api/tauri";
5 -import { TAURI_ACCOUNT_LOGIN } from "../_src/lib/tauriHandler";
5 +import { TAURI_ACCOUNT_LOGIN } from "../../lib/tauriHandler";
6 import { Button } from "../_ui/components/button";
7 import { useState } from "react";
8 -import { UserContext } from "../_src/lib/CurrentUserContext";
9 -import { CurrentUser, CurrentUserType, db } from "../_src/lib/db";
8 +import { UserContext } from "../../lib/CurrentUserContext";
9 +import { CurrentUser, CurrentUserType, db } from "../../lib/db";
10 import { AccountLoginResponse } from "@/models/account";
11 import { Mode } from "../_src/components/player/models/Mode";
12 import { LoadingView } from "../_src/components/templates/LoadingView";
frontend/splitfire-desktop/app/logout/page.tsx
+3 -3
@@ -1,11 +1,11 @@
1 "use client";
2
3 import { useContext, useState } from "react";
4 -import { UserContext } from "../_src/lib/CurrentUserContext";
4 +import { UserContext } from "../../lib/CurrentUserContext";
5 import { useLogger } from "../../lib/logger";
6 -import { CurrentUser, db } from "../_src/lib/db";
6 +import { CurrentUser, db } from "../../lib/db";
7 import { invoke } from "@tauri-apps/api/tauri";
8 -import { TAURI_ACCOUNT_LOGOUT } from "../_src/lib/tauriHandler";
8 +import { TAURI_ACCOUNT_LOGOUT } from "../../lib/tauriHandler";
9 import { useRouter } from "next/navigation";
10 import { Button } from "../_ui/components/button";
11
frontend/splitfire-desktop/app/page.tsx
+2 -2
@@ -3,9 +3,9 @@
3 import { useEffect, useState } from "react";
4 import { SkeletonCard, SongProviderCard, SongProviderPath } from "./_ui/skeleton-card";
5 import { invoke } from "@tauri-apps/api";
6 -import { TAURI_CONTENT_CAROUSEL } from "./_src/lib/tauriHandler";
6 +import { TAURI_CONTENT_CAROUSEL } from "../lib/tauriHandler";
7 import { ContentCarouselResponse } from "@/models/content";
8 -import { SongProvider } from "./_src/models/SongResponse";
8 +import { SongProvider } from "../models/SongResponse";
9
10 export default function Page() {
11
frontend/splitfire-desktop/app/play/_components/audio-info-middle.tsx
+1 -1
@@ -1,4 +1,4 @@
1 -import { useCountDown } from "@/app/_src/lib/useCountDown";
1 +import { useCountDown } from "@/lib/useCountDown";
2 import { useLogger } from "@/lib/logger";
3 import { useEffect } from "react";
4
frontend/splitfire-desktop/app/play/_components/player.tsx
+15 -3
@@ -1,7 +1,5 @@
1 "use client";
2
3 -import { ModeDemucs } from "@/app/_src/components/player/models/Mode";
4 -import { PlayerState, PlayerVolume } from "@/app/_src/components/player/Player";
3 import {
4 TAURI_PLAYER_PAUSED,
5 TAURI_PLAYER_PLAY,
@@ -11,13 +9,27 @@ import {
9 TAURI_PLAYER_RESUMED,
10 TAURI_PLAYER_SET_VOLUME,
11 TAURI_PLAYER_STOP,
14 -} from "@/app/_src/lib/tauriHandler";
12 +} from "@/lib/tauriHandler";
13 import { invoke } from "@tauri-apps/api";
14 import { useState } from "react";
15 import { ControlButtons } from "./control-button";
16 import { AudioInfo } from "./audio-info";
17 import { AudioInfoMiddle } from "./audio-info-middle";
18 import { useLogger } from "@/lib/logger";
19 +import { ModeDemucs } from "@/models/mode";
20 +
21 +enum PlayerState {
22 + STOPPED,
23 + PLAYING,
24 + PAUSED,
25 + RECORDING
26 +}
27 +
28 +export interface PlayerVolume {
29 + mode: ModeDemucs,
30 + volume: string
31 +}
32 +
33
34 export default function Player({
35 audioId,
frontend/splitfire-desktop/app/play/page.tsx
+2 -2
@@ -2,14 +2,14 @@
2
3 import { invoke } from "@tauri-apps/api";
4 import { useContext, useEffect, useState } from "react";
5 -import { TAURI_PLAYER_PREPARE } from "../_src/lib/tauriHandler";
5 +import { TAURI_PLAYER_PREPARE } from "../../lib/tauriHandler";
6 import { useRouter, useSearchParams } from "next/navigation";
7 import { useLogger } from "../../lib/logger";
8 import { IconSpinner } from "../_ui/components/icons";
9 import { PlayerPrepareResponse } from "@/models/content";
10 import { TauriResponse } from "@/models/shared";
11 import Player from "./_components/player";
12 -import { UserContext } from "../_src/lib/CurrentUserContext";
12 +import { UserContext } from "../../lib/CurrentUserContext";
13
14 enum State {
15 LOADING,
frontend/splitfire-desktop/app/profile/_components/index.tsx new
+3
@@ -0,0 +1,3 @@
1 +
2 +
3 +export default function Index() {}
\ No newline at end of file
frontend/splitfire-desktop/app/profile/page.tsx
+9 -7
@@ -1,19 +1,19 @@
1 "use client";
2
3 -import { useEffect, useState } from "react";
4 -import { useLogger } from "../../lib/logger";
5 -import User, { usernameOrId } from "../_src/models/user";
3 +import { useContext, useEffect, useState } from "react";
4 +import { useLogger } from "@/lib/logger";
5 +import User, { usernameOrId } from "@/models/user";
6 import { useSearchParams } from "next/navigation";
7 import Image from "next/image";
8 import { invoke } from "@tauri-apps/api";
9 -import { TAURI_ACCOUNT_PROFILE } from "../_src/lib/tauriHandler";
9 +import { TAURI_ACCOUNT_PROFILE } from "@/lib/tauriHandler";
10 import { AccountProfileResponse } from "@/models/account";
11 import { TauriResponse } from "@/models/shared";
12 import { LocationMarkerIcon } from "@heroicons/react/solid";
13 import { GearIcon } from "@radix-ui/react-icons";
14 import Link from "next/link";
15 -
16 -export const PARAMS_USER_ID = "userId";
15 +import { PARAMS_USER_ID } from "@/app/_lib/params";
16 +import { UserContext } from "@/lib/CurrentUserContext";
17
18 enum State {
19 LOADING,
@@ -21,13 +21,15 @@ enum State {
21 ERROR,
22 }
23
24 -export default function Page({ loggedInUser }: { loggedInUser: User | null }) {
24 +export default function Page() {
25 const log = useLogger("Profile/Page");
26 const searchParams = useSearchParams();
27 const userId = searchParams.get(PARAMS_USER_ID);
28 const [isOwnProfile, setIsOwnProfile] = useState(false);
29 const [userDisplayed, setUserDisplayed] = useState<User | null>(null);
30 const [state, setState] = useState(State.LOADING);
31 + const userContext = useContext(UserContext);
32 + const loggedInUser = userContext.user?.user;
33
34 useEffect(() => {
35 log.debug("Profile page loaded.");
frontend/splitfire-desktop/app/ready-to-play/page.tsx
+2 -2
@@ -3,9 +3,9 @@
3 import { ContentCarouselResponse } from "@/models/content";
4 import { invoke } from "@tauri-apps/api";
5 import { useEffect, useState } from "react";
6 -import { TAURI_CONTENT_READY_TO_PLAY } from "../_src/lib/tauriHandler";
6 +import { TAURI_CONTENT_READY_TO_PLAY } from "../../lib/tauriHandler";
7 import { SkeletonCard, SongProviderCard, SongProviderPath } from "../_ui/skeleton-card";
8 -import { SongProvider } from "../_src/models/SongResponse";
8 +import { SongProvider } from "../../models/SongResponse";
9
10 export default function Page() {
11 const [songProviders, setSongProviders] = useState<SongProvider[]>([]);
frontend/splitfire-desktop/app/register/page.tsx
+1 -1
@@ -3,7 +3,7 @@
3 import { invoke } from "@tauri-apps/api";
4 import { useState } from "react";
5 import * as Form from "@radix-ui/react-form";
6 -import { TAURI_ACCOUNT_REGISTER } from "../_src/lib/tauriHandler";
6 +import { TAURI_ACCOUNT_REGISTER } from "../../lib/tauriHandler";
7 import { Button } from "../_ui/components/button";
8 import { useLogger } from "../../lib/logger";
9 import { AccountRegisterResponse } from "@/models/account";
frontend/splitfire-desktop/app/split/_components/button-split.tsx
+4 -24
@@ -1,12 +1,5 @@
1 -import { HTTPStatusCode } from "@/app/_src/components/pages/esef/SplitFireView";
2 -import {
3 - AudioFile,
4 - Status,
5 -} from "@/app/_src/components/player/models/AudioFile";
6 -import { UserContext } from "@/app/_src/lib/CurrentUserContext";
7 -import requestSplitService, {
8 - SplitResponse,
9 -} from "@/app/_src/lib/requestSplitService";
1 +import { UserContext } from "@/lib/CurrentUserContext";
2 +import { AudioFile, Status } from "@/models/audio-file";
3 import { CountdownTimerIcon, LapTimerIcon, RocketIcon } from "@radix-ui/react-icons";
4 import { useRouter } from "next/navigation";
5 import { useContext, useState } from "react";
@@ -40,21 +33,8 @@ export default function ButtonGenerateBackingTracks({
33 }
34
35 setState(State.LOADING);
43 - requestSplitService(songProviderId, user.accessToken)
44 - .then((res) => {
45 - console.log(res);
46 - const response: SplitResponse = res.data;
47 - if (response.code === HTTPStatusCode.OK) {
48 - audioFile = response.audio_file;
49 - setState(State.LOADED);
50 - } else {
51 - setState(State.ERROR);
52 - }
53 - })
54 - .catch((error) => {
55 - console.log(error);
56 - setState(State.ERROR);
57 - });
36 + // Split request logic here.
37 +
38 };
39
40 // Default to production value.
frontend/splitfire-desktop/app/split/_components/song-votes.tsx
+2 -2
@@ -1,7 +1,7 @@
1 "use client";
2
3 -import { SongProvider } from "@/app/_src/models/SongResponse";
4 -import { SongProviderVote } from "@/app/_src/models/SongVotesDetailResponse";
3 +import { SongProvider } from "@/models/SongResponse";
4 +import { SongProviderVote } from "@/models/SongVotesDetailResponse";
5 import VotesView from "./votes-view";
6 import SongVotesImage from "./song-votes-image";
7
frontend/splitfire-desktop/app/split/_components/video.tsx
+2 -2
@@ -1,9 +1,9 @@
1 "use client";
2
3 -import YouTube, { Options } from "react-youtube";
3 +import YouTube from "react-youtube";
4
5 export function Video({ providerId }: { providerId: string }) {
6 - const opts: Options = {
6 + const opts = {
7 width: "100%",
8 height: "100%",
9 playerVars: {
frontend/splitfire-desktop/app/split/_components/voters-view.tsx
+4 -3
@@ -1,8 +1,9 @@
1 -import { VoteType } from "@/app/_src/components/pages/song/components/UpDownVotes";
2 -import { SongProviderVote } from "@/app/_src/models/SongVotesDetailResponse";
3 -import { PARAMS_USER_ID } from "@/app/profile/page";
1 +
2 +import { SongProviderVote } from "@/models/SongVotesDetailResponse";
3 +import { PARAMS_USER_ID } from '@/app/_lib/params';
4 import Image from "next/image";
5 import Link from "next/link";
6 +import { VoteType } from "./votes-view-main";
7
8 export function VoterGravatarsViews(props: {
9 voters: SongProviderVote[];
frontend/splitfire-desktop/app/split/_components/votes-view-main.tsx
+4 -4
@@ -2,13 +2,13 @@ import { BsArrowUpCircleFill, BsArrowDownCircleFill } from "react-icons/bs";
2 import { VoterGravatarsViews } from "./voters-view";
3 import { SongProviderResponse } from "@/models/content";
4 import { CSSProperties, useContext, useState } from "react";
5 -import { TAURI_CONTENT_SONG_BRIDGE_VOTE } from "@/app/_src/lib/tauriHandler";
6 -import { SongProviderVote } from "@/app/_src/models/SongVotesDetailResponse";
5 +import { TAURI_CONTENT_SONG_BRIDGE_VOTE } from "@/lib/tauriHandler";
6 +import { SongProviderVote } from "@/models/SongVotesDetailResponse";
7 import { TauriResponse } from "@/models/shared";
8 import { invoke } from "@tauri-apps/api";
9 -import { UserContext } from "@/app/_src/lib/CurrentUserContext";
9 +import { UserContext } from "@//lib/CurrentUserContext";
10 import { useRouter } from "next/navigation";
11 -import { CurrentUser } from "@/app/_src/lib/db";
11 +import { CurrentUser } from "@/lib/db";
12 import { Spinner } from "react-bootstrap";
13 import { useLogger } from "@/lib/logger";
14
frontend/splitfire-desktop/app/split/_components/votes-view.tsx
+2 -2
@@ -3,8 +3,8 @@
3 import {
4 AudioFile,
5 Status,
6 -} from "@/app/_src/components/player/models/AudioFile";
7 -import { SongProviderVote } from "@/app/_src/models/SongVotesDetailResponse";
6 +} from "@/models/audio-file";
7 +import { SongProviderVote } from "@/models/SongVotesDetailResponse";
8 import { useRouter } from "next/navigation";
9 import { useState } from "react";
10 import ButtonGenerateBackingTracks from "./button-split";
frontend/splitfire-desktop/app/split/page.tsx
+3 -3
@@ -4,10 +4,10 @@ import { useSearchParams } from "next/navigation";
4 import { useLogger } from "../../lib/logger";
5 import { useEffect, useState } from "react";
6 import { invoke } from "@tauri-apps/api";
7 -import { TAURI_CONTENT_SONG_BRIDGE_DETAIL } from "../_src/lib/tauriHandler";
8 -import { SongProvider } from "../_src/models/SongResponse";
7 +import { TAURI_CONTENT_SONG_BRIDGE_DETAIL } from "../../lib/tauriHandler";
8 +import { SongProvider } from "../../models/SongResponse";
9 import { SongBridgeResponse } from "../_src/components/pages/esef/SplitFireView";
10 -import { SongProviderVote } from "../_src/models/SongVotesDetailResponse";
10 +import { SongProviderVote } from "../../models/SongVotesDetailResponse";
11 import SongVotes from "./_components/song-votes";
12
13 export default function Page() {
frontend/splitfire-desktop/app/top-voted/page.tsx
+2 -2
@@ -3,8 +3,8 @@
3 import { ContentCarouselResponse } from "@/models/content";
4 import { invoke } from "@tauri-apps/api";
5 import { useState, useEffect } from "react";
6 -import { TAURI_CONTENT_TOP_VOTED } from "../_src/lib/tauriHandler";
7 -import { SongProvider } from "../_src/models/SongResponse";
6 +import { TAURI_CONTENT_TOP_VOTED } from "../../lib/tauriHandler";
7 +import { SongProvider } from "../../models/SongResponse";
8 import { SkeletonCard, SongProviderCard, SongProviderPath } from "../_ui/skeleton-card";
9 import { useLogger } from "../../lib/logger";
10
frontend/splitfire-desktop/lib/CurrentUserContext.ts renamed
frontend/splitfire-desktop/lib/db.ts renamed
+5 -5
@@ -1,11 +1,11 @@
1 import Dexie, { Table } from "dexie";
2 -import { Mode, ModeDemucs } from "../components/player/models/Mode";
2 +import { Mode, ModeDemucs } from "../app/_src/components/player/models/Mode";
3 import User from "../models/user";
4 -import { AudioFile } from "../components/player/models/AudioFile";
5 -import axios from "./axios";
6 -import { Result } from "../components/player/models/Result";
4 +import { AudioFile } from "../app/_src/components/player/models/AudioFile";
5 +import axios from "../app/_src/lib/axios";
6 +import { Result } from "../app/_src/components/player/models/Result";
7 import { Logger } from "tslog";
8 -import isTauri from "./isTauri";
8 +import isTauri from "../app/_src/lib/isTauri";
9
10 // Legacy Spleeter result file
11 export interface AudioFiles {
frontend/splitfire-desktop/lib/tauriHandler.ts renamed
frontend/splitfire-desktop/lib/useCountDown.ts renamed
frontend/splitfire-desktop/models/SongResponse.tsx renamed
+1 -1
@@ -1,4 +1,4 @@
1 -import { AudioFile } from "../components/player/models/AudioFile"
1 +import { AudioFile } from "./audio-file"
2
3 export interface SongResponse {
4 code: number,
frontend/splitfire-desktop/models/SongVotesDetailResponse.tsx renamed
+6 -1
@@ -1,6 +1,11 @@
1 -import { VoteType } from "../components/pages/song/components/UpDownVotes"
1 import { SongProvider } from "./SongResponse"
2
3 +
4 +export enum VoteType {
5 + UP = 'up',
6 + DOWN = 'down'
7 + }
8 +
9 export default interface SongVotesDetailResponse {
10 code: string
11 message: string
frontend/splitfire-desktop/models/account.ts
+1 -1
@@ -1,4 +1,4 @@
1 -import User from "@/app/_src/models/user";
1 +import User from "@/models/user";
2 import { TauriResponse } from "./shared";
3
4 export interface AccountLoginResponse {
frontend/splitfire-desktop/models/audio-file.tsx renamed
+1 -1
@@ -1,4 +1,4 @@
1 -import { Result } from "./Result";
1 +import { Result } from "./result";
2
3 export enum Status {
4 DOWNLOADING = "downloading", // Requested to process file.
frontend/splitfire-desktop/models/content.ts
+2 -2
@@ -1,6 +1,6 @@
1 -import { SongProvider } from "@/app/_src/models/SongResponse";
1 +import { SongProvider } from "@/models/SongResponse";
2 import { TauriResponse } from "./shared";
3 -import { SongProviderVote } from "@/app/_src/models/SongVotesDetailResponse";
3 +import { SongProviderVote } from "@/models/SongVotesDetailResponse";
4
5 // Need to be renamed into more generic name
6 export interface ContentCarouselResponse {
frontend/splitfire-desktop/models/lyricSynches.tsx renamed
frontend/splitfire-desktop/models/mode.tsx renamed
frontend/splitfire-desktop/models/result.tsx renamed
frontend/splitfire-desktop/models/split.ts new
+7
@@ -0,0 +1,7 @@
1 +import { AudioFile } from "./audio-file"
2 +
3 +export interface SplitResponse {
4 + code: number
5 + audio_file: AudioFile
6 + message: string
7 +}
\ No newline at end of file
frontend/splitfire-desktop/models/user.tsx renamed
frontend/splitfire-desktop/src-tauri/src/command/constants.rs
+1
@@ -28,6 +28,7 @@ pub const PATH_SEARCH: &str = "api/v1//search";
28 pub const PATH_TOP_VOTES: &str = "api/v1/top-votes";
29 pub const PATH_SONG_BRIDGE_DETAIL: &str = "api/v1/song-bridge/{providerId}/detail";
30 pub const PATH_SONG_BRIDGE_VOTE: &str = "api/v1/song-bridge/{providerId}/vote";
31 +pub const PATH_SONG_BRIDGE_SPLIT: &str = "api/v1/song-bridge/{providerId}/split";
32
33 pub fn base_url_builder() -> URLBuilder {
34 let mut url_builder = URLBuilder::new();
frontend/splitfire-desktop/src-tauri/src/models/content.rs
+17 -1
@@ -6,7 +6,7 @@ use super::player::{AudioFile, TauriResponse};
6 pub struct ContentCarouselResponse {
7 pub status: TauriResponse,
8 pub message: String,
9 - pub audio_files: Vec<SongProvider>,
9 + pub audio_files: Vec<SongProvider>, // TODO: Change to song_providers
10 }
11
12 #[derive(Serialize, Deserialize)]
@@ -77,3 +77,19 @@ pub enum VoteType {
77 #[serde(rename = "down")]
78 DOWN
79 }
80 +
81 +#[derive(Serialize)]
82 +#[derive(Debug)]
83 +pub struct ContentSplitResponse {
84 + pub status: TauriResponse,
85 + pub message: String,
86 + pub audio_file: Option<AudioFile>,
87 +}
88 +
89 +#[derive(Serialize, Deserialize)]
90 +#[derive(Debug)]
91 +pub struct SplitResponse {
92 + pub code: i32,
93 + pub message: String,
94 + pub audio_file: Option<AudioFile>,
95 +}
\ No newline at end of file
frontend/splitfire-desktop/src-tauri/src/rest/content.rs
+43 -3
@@ -1,12 +1,12 @@
1 use crate::{
2 command::constants::{
3 base_url_builder, PATH_CAROUSEL, PATH_READY_TO_PLAY, PATH_SONG_BRIDGE_DETAIL,
4 - PATH_SONG_BRIDGE_VOTE, PATH_TOP_VOTES,
4 + PATH_SONG_BRIDGE_SPLIT, PATH_SONG_BRIDGE_VOTE, PATH_TOP_VOTES,
5 },
6 models::{
7 content::{
8 CarouselResponse, ContentCarouselResponse, ContentSongProviderResponse,
9 - SongProviderResponse, VoteType,
9 + ContentSplitResponse, SongProviderResponse, SplitResponse, VoteType,
10 },
11 player::TauriResponse,
12 },
@@ -185,7 +185,8 @@ pub async fn content_song_bridge_vote(
185 access_token: String,
186 ) -> ContentSongProviderResponse {
187 debug!("Song provider id: {:?}", song_provider_id);
188 - let url = content_url_builder(PATH_SONG_BRIDGE_VOTE).replace("{providerId}", &song_provider_id.to_string());
188 + let url = content_url_builder(PATH_SONG_BRIDGE_VOTE)
189 + .replace("{providerId}", &song_provider_id.to_string());
190 let body = json!({
191 "vote_type": vote_type,
192 "provider_id": song_provider_id
@@ -238,6 +239,45 @@ pub async fn content_song_bridge_vote(
239 }
240 }
241
242 +#[tauri::command]
243 +pub async fn content_song_bridge_split(song_provider_id: i32) -> ContentSplitResponse {
244 + let url = content_url_builder(PATH_SONG_BRIDGE_SPLIT)
245 + .replace("{providerId}", &song_provider_id.to_string());
246 + let response: Result<reqwest::Response, reqwest::Error> = Client::new().get(url).send().await;
247 +
248 + let response = match response {
249 + Ok(response) => response,
250 + Err(e) => {
251 + error!("Failed to get response: {:?}", e);
252 + return ContentSplitResponse {
253 + status: TauriResponse::Error,
254 + message: e.to_string(),
255 + audio_file: None,
256 + };
257 + }
258 + };
259 +
260 + let res: SplitResponse = match response.json().await {
261 + Ok(json) => json,
262 + Err(e) => {
263 + error!("Failed to parse response: {:?}", e);
264 + return ContentSplitResponse {
265 + status: TauriResponse::Error,
266 + message: e.to_string(),
267 + audio_file: None,
268 + };
269 + }
270 + };
271 +
272 + debug!("Song bridge split: {:?}", res);
273 +
274 + ContentSplitResponse {
275 + status: TauriResponse::Success,
276 + message: res.message,
277 + audio_file: res.audio_file,
278 + }
279 +}
280 +
281 fn content_url_builder(path: &str) -> String {
282 let mut url_builder = base_url_builder();
283 url_builder.add_route(path);