+
Searching...
diff --git a/web/src/scss/components/queue.scss b/web/src/scss/components/queue.scss
index f49ab3b..99e2e20 100644
--- a/web/src/scss/components/queue.scss
+++ b/web/src/scss/components/queue.scss
@@ -6,6 +6,7 @@
height: 10rem;
width: 10rem;
position: relative;
+ margin-right: 20px;
.song-cover,
.fa-solid {
@@ -25,6 +26,7 @@
left: 0;
width: 100%;
height: 100%;
+ cursor: pointer;
&.hover {
display: none;
@@ -37,6 +39,39 @@
}
}
}
+
+ >.song-details {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+
+ >* {
+ margin: 5px;
+ }
+
+ >h3 {
+ font-size: 1.8rem;
+ }
+ }
+
+ >.song-actions {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ align-items: center;
+ margin-left: auto;
+ margin-right: 10px;
+
+ >* {
+ margin: 5px;
+ }
+
+ >.fa-solid {
+ font-size: 1.25rem;
+ cursor: pointer;
+ }
+ }
}
.playing-symbols {
diff --git a/web/src/scss/components/search.scss b/web/src/scss/components/search.scss
index ab19504..300bc73 100644
--- a/web/src/scss/components/search.scss
+++ b/web/src/scss/components/search.scss
@@ -3,10 +3,42 @@
height: 60vh;
overflow-x: hidden;
overflow-y: scroll;
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+
+ &.placeholder {
+ justify-content: center;
+ }
>div {
+ display: flex;
+ align-items: center;
+ margin-bottom: 5px;
+ width: 75%;
+ cursor: pointer;
+
+ >div {
+ display: flex;
+ justify-content: flex-start;
+ align-items: flex-start;
+ flex-direction: column;
+ height: 100%;
+
+ >* {
+ margin: 5px;
+ }
+ }
+
>img {
- width: 10%;
+ height: 7rem;
+ width: 7rem;
+ margin-right: 20px;
+ }
+
+ >.fa-solid {
+ margin-left: auto;
+ font-size: 2rem;
}
}
}
diff --git a/web/src/scss/components/sortablelist.scss b/web/src/scss/components/sortablelist.scss
index 3d4047d..2af4a32 100644
--- a/web/src/scss/components/sortablelist.scss
+++ b/web/src/scss/components/sortablelist.scss
@@ -18,6 +18,7 @@
flex-direction: row;
align-items: center;
justify-content: center;
+ margin-bottom: 5px;
&.moving {
position: fixed;
diff --git a/web/src/ts/player/index.ts b/web/src/ts/player/index.ts
index fe82458..aa6d9ec 100644
--- a/web/src/ts/player/index.ts
+++ b/web/src/ts/player/index.ts
@@ -1,6 +1,7 @@
import {
addSongList,
clearQueue,
+ removeSong,
shuffleList
} from './playlists/add';
import {
@@ -27,12 +28,12 @@ import {
} from './playlists';
const next = () => {
- playIndex( queueIdx.value + 1 );
+ playIndex( ( queueIdx.value + 1 ) % queue.value.length );
};
const prev = () => {
// TODO: Back to beginning if more than some seconds have passed?
- playIndex( queueIdx.value - 1 );
+ playIndex( ( queueIdx.value - 1 + queue.value.length ) % queue.value.length );
};
const play = () => {
@@ -117,6 +118,10 @@ const getSources = (): string[] => {
return Object.keys( sources );
};
+/**
+ * Add songs to the playlist from given source
+ * @param source - The ID of the source to add from
+ */
const addSongFromSource = async ( source: string ) => {
sources[source]!.addSongsFromThisSource( addSongList );
};
@@ -135,6 +140,7 @@ export default {
next,
prev,
clearQueue,
+ removeSong,
queue,
queueIdx,
duration,
diff --git a/web/src/ts/player/playlists/add.ts b/web/src/ts/player/playlists/add.ts
index d6a4fee..f7e70a1 100644
--- a/web/src/ts/player/playlists/add.ts
+++ b/web/src/ts/player/playlists/add.ts
@@ -35,7 +35,6 @@ export const removeSong = ( idx: number ) => {
const removed = queue.value.splice( idx, 1 )[0];
- // TODO: Check that this works
for ( let i = 0; i < rawQueue.value.length; i++ ) {
if ( rawQueue.value[ i ] === removed ) {
rawQueue.value.splice( i, 1 );
diff --git a/web/src/ts/player/plugins/interface.d.ts b/web/src/ts/player/plugins/interface.d.ts
index dd3fa88..a49ca3f 100644
--- a/web/src/ts/player/plugins/interface.d.ts
+++ b/web/src/ts/player/plugins/interface.d.ts
@@ -90,6 +90,11 @@ export interface PlayerSourcePlugin {
*/
'getDuration': () => number;
+ /**
+ * Check if source is available for playback
+ */
+ 'available': () => boolean;
+
/**
* Called when user adds another song to the playlist via this source.
* You may use the provided interface elements (search bar and popups) to e.g. ask if user wants to use a playlist, album, etc
diff --git a/web/src/ts/player/plugins/local/index.ts b/web/src/ts/player/plugins/local/index.ts
index 69ea1f3..637061f 100644
--- a/web/src/ts/player/plugins/local/index.ts
+++ b/web/src/ts/player/plugins/local/index.ts
@@ -12,6 +12,7 @@ export const useLocalPlayer: PlayerSourcePluginInitializer = async (): Promise
true,
'id': 'local',
'name': 'Local Disk',
'play': player.play,
diff --git a/web/src/ts/player/plugins/musickit/index.ts b/web/src/ts/player/plugins/musickit/index.ts
index a89b9d1..650ae8a 100644
--- a/web/src/ts/player/plugins/musickit/index.ts
+++ b/web/src/ts/player/plugins/musickit/index.ts
@@ -43,6 +43,7 @@ export const useMusicKit: PlayerSourcePluginInitializer = ( storefront: string =
};
return {
+ 'available': controls.getLoggedIn,
'play': controls.play,
'pause': controls.pause,
'stop': controls.stop,
diff --git a/web/src/ts/player/plugins/musickit/playback.ts b/web/src/ts/player/plugins/musickit/playback.ts
index 261b4b4..3e16961 100644
--- a/web/src/ts/player/plugins/musickit/playback.ts
+++ b/web/src/ts/player/plugins/musickit/playback.ts
@@ -34,6 +34,10 @@ export const musicKitPlayback = ( musickitInstance: MusicKitInstance ) => {
return musickitInstance.currentPlaybackDuration;
};
+ const getLoggedIn = (): boolean => {
+ return musickitInstance.isAuthorized;
+ };
+
return {
play,
pause,
@@ -41,6 +45,7 @@ export const musicKitPlayback = ( musickitInstance: MusicKitInstance ) => {
playSong,
stop,
getPlaybackPos,
- getDuration
+ getDuration,
+ getLoggedIn
};
};
diff --git a/web/src/ts/player/plugins/musickit/search/index.ts b/web/src/ts/player/plugins/musickit/search/index.ts
index 00eec1d..b78d309 100644
--- a/web/src/ts/player/plugins/musickit/search/index.ts
+++ b/web/src/ts/player/plugins/musickit/search/index.ts
@@ -7,12 +7,16 @@ import type {
import {
openImportTypePicker
} from '@/composables/importTypePicker';
+import {
+ searchPlaylists
+} from './playlists';
import {
searchSongs
} from './songs';
export const addFromAppleMusic = async ( instance: MusicKitInstance, cb: ( songs: Song[] ) => void ): Promise => {
const songs = await searchSongs( instance, cb );
+ const playlists = await searchPlaylists( instance, cb );
openImportTypePicker( [
{
@@ -24,8 +28,8 @@ export const addFromAppleMusic = async ( instance: MusicKitInstance, cb: ( songs
{
'name': 'Playlists',
'type': 'cloud',
- 'addSelected': songs.addSelected,
- 'search': songs.search
+ 'addSelected': playlists.addSelected,
+ 'search': playlists.search
}
] );
};
diff --git a/web/src/ts/player/plugins/musickit/search/playlists.ts b/web/src/ts/player/plugins/musickit/search/playlists.ts
index aa74732..6fe0dc3 100644
--- a/web/src/ts/player/plugins/musickit/search/playlists.ts
+++ b/web/src/ts/player/plugins/musickit/search/playlists.ts
@@ -1,17 +1,24 @@
-import type {
- Song
-} from '@/ts/dtype/playlist';
import type {
MusicKitInstance
} from 'musickitjs-v3-types/MusicKitInstance';
+import type {
+ Song
+} from '@/ts/dtype/playlist';
-export const searchPlaylists = async ( instance: MusicKitInstance ) => {
+export const searchPlaylists = async ( instance: MusicKitInstance, cb: ( songs: Song[] ) => void ) => {
// TODO: Get all playlists
- const search = async () => {};
-
- const addSelected = async ( idx: number ): Promise => {
- // TODO: Load the playlist content and return songs
+ const search = async ( term: string ): Promise => {
return [];
};
+
+ const addSelected = async ( idx: number ) => {
+ // TODO: Implement
+ cb( [] );
+ };
+
+ return {
+ search,
+ addSelected
+ };
};
diff --git a/web/src/views/AppView.vue b/web/src/views/AppView.vue
index 1a2805b..fc3cc9c 100644
--- a/web/src/views/AppView.vue
+++ b/web/src/views/AppView.vue
@@ -1,10 +1,33 @@
-