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);