chore: refactor vue components

This commit is contained in:
2026-09-02 15:21:43 +02:00
parent 8a3dd7a583
commit b903eb416f
30 changed files with 209 additions and 52 deletions
@@ -0,0 +1,49 @@
<script setup lang="ts">
import {
queue,
queueIdx
} from '@/ts/player/state';
import CurrentSong from '../player/CurrentSong.vue';
import PlayerControls from '../player/PlayerControls.vue';
import QueueViewer from '../player/QueueViewer.vue';
</script>
<template>
<div class="player">
<div
class="panel"
>
<div style="margin-bottom: 20px; width: 100%;">
<CurrentSong
v-model="queue[queueIdx]"
/>
</div>
<PlayerControls />
</div>
<div class="panel">
<QueueViewer />
</div>
</div>
</template>
<style lang="scss" scoped>
.player {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
.panel {
width: 45%;
margin-left: 2.5%;
margin-right: 2.5%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
</style>
+49
View File
@@ -0,0 +1,49 @@
<script setup lang="ts">
import PlayerComponent from './PlayerComponent.vue';
import SmallPlayerComponent from './SmallPlayerComponent.vue';
import {
ref
} from 'vue';
const fullPlayer = ref( true );
const close = () => {
fullPlayer.value = false;
};
</script>
<template>
<div class="player-wrapper">
<SmallPlayerComponent v-model="fullPlayer" />
<div :class="['player-container', fullPlayer ? undefined : 'hidden']">
<i class="fa-solid fa-xmark" @click="close"></i>
<PlayerComponent />
</div>
</div>
</template>
<style lang="scss" scoped>
.player-container {
width: 100vw;
height: 100vh;
position: fixed;
bottom: 0;
left: 0;
background-color: var(--secondary-color);
transition: bottom 1s ease;
transition-delay: 0.25s;
&.hidden {
transition-delay: 0s;
bottom: -100vh;
}
>.fa-xmark {
position: absolute;
top: 10px;
right: 10px;
font-size: 1.5rem;
cursor: pointer;
}
}
</style>
@@ -1,5 +1,5 @@
<template> <template>
<div> <div>
Hello World <h1>Playlists</h1>
</div> </div>
</template> </template>
@@ -0,0 +1,35 @@
<script setup lang="ts">
const closed = defineModel<boolean>( {
'required': true
} );
const open = () => {
closed.value = true;
};
</script>
<template>
<div :class="['small-player', closed ? 'hidden' : undefined]" @click="open">
Player
</div>
</template>
<style lang="scss" scoped>
.small-player {
background-color: green;
width: 80vw;
height: 7vh;
position: fixed;
left: 10vw;
bottom: 15px;
border-radius: 4vh;
overflow: hidden;
transition: bottom 0.5s ease;
transition-delay: 0.75s;
&.hidden {
bottom: -8vh;
transition-delay: 0s;
}
}
</style>
@@ -5,12 +5,12 @@
computed, computed,
ref ref
} from 'vue'; } from 'vue';
import AddSong from './AddSong.vue'; import AddSong from '../popups/AddSong.vue';
import type { import type {
Song Song
} from '@/ts/dtype/playlist'; } from '@/ts/dtype/playlist';
import SongEditor from './SongEditor.vue'; import SongEditor from '../popups/SongEditor.vue';
import SortableList from './SortableList.vue'; import SortableList from '../SortableList.vue';
import { import {
beautifyTime beautifyTime
} from '@/ts/util/time'; } from '@/ts/util/time';
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import DiskLoader from '@/composables/DiskLoader.vue'; import DiskLoader from '@/composables/DiskLoader.vue';
import ImportTypePicker from '@/composables/ImportTypePicker.vue'; import ImportTypePicker from '@/composables/ImportTypePicker.vue';
import PopupElement from '@/components/PopupElement.vue'; import PopupElement from './PopupElement.vue';
import player from '@/ts/player'; import player from '@/ts/player';
import { import {
sources sources
+1 -1
View File
@@ -11,7 +11,7 @@
onMounted, onMounted,
useTemplateRef useTemplateRef
} from 'vue'; } from 'vue';
import PopupElement from '@/components/PopupElement.vue'; import PopupElement from '@/components/popups/PopupElement.vue';
import player from '@/ts/player'; import player from '@/ts/player';
import { import {
queue queue
+1 -1
View File
@@ -8,7 +8,7 @@
ref, ref,
useTemplateRef useTemplateRef
} from 'vue'; } from 'vue';
import PopupElement from '@/components/PopupElement.vue'; import PopupElement from '@/components/popups/PopupElement.vue';
const fileinput = useTemplateRef( 'fileinput' ); const fileinput = useTemplateRef( 'fileinput' );
const progress = ref( -1 ); const progress = ref( -1 );
+1 -1
View File
@@ -4,7 +4,7 @@
importers, importers,
isShowingImportTypePicker isShowingImportTypePicker
} from './importTypePicker'; } from './importTypePicker';
import PopupElement from '@/components/PopupElement.vue'; import PopupElement from '@/components/popups/PopupElement.vue';
import SearchView from './SearchView.vue'; import SearchView from './SearchView.vue';
import { import {
openDiskLoaderInterface openDiskLoaderInterface
+1 -1
View File
@@ -5,7 +5,7 @@
results, results,
searchOpts searchOpts
} from './searchManager'; } from './searchManager';
import PopupElement from '@/components/PopupElement.vue'; import PopupElement from '@/components/popups/PopupElement.vue';
import { import {
ref ref
} from 'vue'; } from 'vue';
+1 -1
View File
@@ -1,6 +1,6 @@
// NOTE: For re-associating files with details here, can use dropdown in UI // NOTE: For re-associating files with details here, can use dropdown in UI
export type Playlist = Song[]; export type PlaylistSongs = Song[];
export interface UrlToFileMapping { export interface UrlToFileMapping {
[key: string]: string [key: string]: string
+7
View File
@@ -4,6 +4,7 @@ import {
queue, queue,
queueIdx, queueIdx,
rawQueue, rawQueue,
repeat,
shuffle, shuffle,
sources sources
} from '../state'; } from '../state';
@@ -27,6 +28,12 @@ export const clearQueue = () => {
duration.value = -1; duration.value = -1;
playbackPercentage.value = 1; playbackPercentage.value = 1;
isPlaying.value = false; isPlaying.value = false;
repeat.value = 'off';
shuffle.value = false;
for ( const song of queue.value ) {
sources[ song.source ]?.songUnload( song );
}
}; };
/** /**
+2 -2
View File
@@ -7,13 +7,13 @@ import type {
AssociationResult AssociationResult
} from '../plugins/interface'; } from '../plugins/interface';
import type { import type {
Playlist PlaylistSongs
} from '@/ts/dtype/playlist'; } from '@/ts/dtype/playlist';
import { import {
openAssociationManager openAssociationManager
} from '@/composables/associationManager'; } from '@/composables/associationManager';
export const load = ( playlist: Playlist ) => { export const load = ( playlist: PlaylistSongs ) => {
queue.value = playlist; queue.value = playlist;
rawQueue.value = playlist; rawQueue.value = playlist;
+5
View File
@@ -118,4 +118,9 @@ export interface PlayerSourcePlugin {
* You may use the provided interface elements (search bar and popups) to e.g. ask if user wants to use a playlist, album, etc * You may use the provided interface elements (search bar and popups) to e.g. ask if user wants to use a playlist, album, etc
*/ */
'addSongsFromThisSource': ( cb: ( songs: Song[] ) => void ) => void; 'addSongsFromThisSource': ( cb: ( songs: Song[] ) => void ) => void;
/**
* Fully unload a song. This is called on clear of a playlist
*/
'songUnload': ( song: Song ) => Promise<void>;
} }
+4 -2
View File
@@ -7,7 +7,8 @@ import {
updateIdentifiers updateIdentifiers
} from './association'; } from './association';
import { import {
load load,
unload
} from './loader'; } from './loader';
import { import {
ref ref
@@ -38,6 +39,7 @@ export const useLocalPlayer: PlayerSourcePluginInitializer = async (): Promise<P
'association': associate, 'association': associate,
'updateIdentifiers': updateIdentifiers, 'updateIdentifiers': updateIdentifiers,
'mime': 'audio/aac,audio/mpeg,audio/wav,audio/mp4,audio/ogg' 'mime': 'audio/aac,audio/mpeg,audio/wav,audio/mp4,audio/ogg'
} },
'songUnload': unload
}; };
}; };
@@ -75,3 +75,7 @@ export const load = ( cb: ( songs: Song[] ) => void ) => {
} }
}; };
}; };
export const unload = async ( song: Song ) => {
URL.revokeObjectURL( song.identifier );
};
+2 -4
View File
@@ -2,9 +2,6 @@ import type {
PlayerSourcePlugin, PlayerSourcePlugin,
PlayerSourcePluginInitializer PlayerSourcePluginInitializer
} from '../interface'; } from '../interface';
import type {
Song
} from '@/ts/dtype/playlist';
import { import {
addFromAppleMusic addFromAppleMusic
} from './search'; } from './search';
@@ -57,7 +54,8 @@ export const useMusicKit: PlayerSourcePluginInitializer = ( storefront: string =
'addSongsFromThisSource': addFromAppleMusic, 'addSongsFromThisSource': addFromAppleMusic,
'loading': { 'loading': {
'requiresLocalFiles': false 'requiresLocalFiles': false
} },
'songUnload': async () => {}
}; };
} else { } else {
throw new Error( 'ERR_AUTH' ); throw new Error( 'ERR_AUTH' );
+3 -3
View File
@@ -6,7 +6,7 @@ import type {
PlayerSourcePlugin PlayerSourcePlugin
} from './plugins/interface'; } from './plugins/interface';
import type { import type {
Playlist PlaylistSongs
} from '../dtype/playlist'; } from '../dtype/playlist';
import type { import type {
RepeatMode RepeatMode
@@ -24,11 +24,11 @@ export const sources: {
export const currentSource = ref( '' ); export const currentSource = ref( '' );
export const rawQueue: Ref<Playlist> = ref( [] ); export const rawQueue: Ref<PlaylistSongs> = ref( [] );
export const queueIdx = ref( 0 ); export const queueIdx = ref( 0 );
export const queue: Ref<Playlist> = ref( [] ); export const queue: Ref<PlaylistSongs> = ref( [] );
export const isPlaying = ref( false ); export const isPlaying = ref( false );
+14
View File
@@ -0,0 +1,14 @@
import type {
PlaylistSongs
} from '../dtype/playlist';
interface UserPlaylistFile {
'version': '1',
'userid': string,
'playlists': Playlist[]
}
interface Playlist {
'name': string;
'songs': PlaylistSongs;
}
View File
+10
View File
@@ -0,0 +1,10 @@
import {
queue,
rawQueue,
shuffle
} from '../player/state';
const savePlaylist = () => {
rawQueue.value = queue.value;
shuffle.value = false;
};
+10
View File
@@ -0,0 +1,10 @@
import {
type Ref,
ref
} from 'vue';
import type {
PlaylistSongs
} from '../dtype/playlist';
const currentPlaylist = ref( '' );
const playlists: Ref<PlaylistSongs> = ref( [] );
+5 -31
View File
@@ -1,28 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { import PlayerWrapper from '@/components/main/PlayerWrapper.vue';
queue, import PlaylistsComponent from '@/components/main/PlaylistsComponent.vue';
queueIdx
} from '@/ts/player/state';
import CurrentSong from '@/components/CurrentSong.vue';
import PlayerControls from '@/components/PlayerControls.vue';
import QueueViewer from '@/components/QueueViewer.vue';
</script> </script>
<template> <template>
<div class="main-app"> <div class="main-app">
<div <PlaylistsComponent />
class="panel" <PlayerWrapper />
>
<div style="margin-bottom: 20px; width: 100%;">
<CurrentSong
v-model="queue[queueIdx]"
/>
</div>
<PlayerControls />
</div>
<div class="panel">
<QueueViewer />
</div>
</div> </div>
</template> </template>
@@ -34,16 +19,5 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: row; flex-direction: row;
.panel {
width: 45%;
margin-left: 2.5%;
margin-right: 2.5%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
} }
</style> </style>