diff --git a/src/components.d.ts b/src/components.d.ts index b0e8fcb..ff0cf3b 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -11,6 +11,7 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { + AddSourceDialog: typeof import('./components/dialogs/AddSourceDialog.vue')['default'] AlbumList: typeof import('./components/AlbumList.vue')['default'] AppFooter: typeof import('./components/AppFooter.vue')['default'] Cassette: typeof import('./components/cassette/Cassette.vue')['default'] @@ -25,7 +26,7 @@ declare module 'vue' { SearchPlaylistsTab: typeof import('./components/SearchPlaylistsTab.vue')['default'] UnavailableCassetteItem: typeof import('./components/UnavailableCassetteItem.vue')['default'] UnavailableTracksList: typeof import('./components/UnavailableTracksList.vue')['default'] - UploadCassetteDialog: typeof import('./components/UploadCassetteDialog.vue')['default'] + UploadCassetteDialog: typeof import('./components/dialogs/UploadCassetteDialog.vue')['default'] UserPlaylistsTab: typeof import('./components/UserPlaylistsTab.vue')['default'] } } diff --git a/src/components/AlbumList.vue b/src/components/AlbumList.vue index d39445b..4bde27c 100644 --- a/src/components/AlbumList.vue +++ b/src/components/AlbumList.vue @@ -1,8 +1,12 @@ diff --git a/src/components/CassetteInfoDrawer.vue b/src/components/CassetteInfoDrawer.vue index de7267e..29b4b71 100644 --- a/src/components/CassetteInfoDrawer.vue +++ b/src/components/CassetteInfoDrawer.vue @@ -1,43 +1,11 @@ - - - diff --git a/src/components/PlaylistList.vue b/src/components/PlaylistList.vue index 5c1e198..e022884 100644 --- a/src/components/PlaylistList.vue +++ b/src/components/PlaylistList.vue @@ -1,8 +1,12 @@ diff --git a/src/components/SearchPlaylistsTab.vue b/src/components/SearchPlaylistsTab.vue index a8841ad..03670a9 100644 --- a/src/components/SearchPlaylistsTab.vue +++ b/src/components/SearchPlaylistsTab.vue @@ -1,5 +1,4 @@ diff --git a/src/components/UnavailableTracksList.vue b/src/components/UnavailableTracksList.vue index bce92c3..ee4b4c2 100644 --- a/src/components/UnavailableTracksList.vue +++ b/src/components/UnavailableTracksList.vue @@ -1,9 +1,11 @@ diff --git a/src/parsers/episodeDtoParser.ts b/src/parsers/episodeDtoParser.ts index 43274ed..94833de 100644 --- a/src/parsers/episodeDtoParser.ts +++ b/src/parsers/episodeDtoParser.ts @@ -3,7 +3,7 @@ import type { Track } from "@/types/tapeify/models"; import { GetSmallestImage } from "@/utils/images/imageUtils"; import { v4 as uuidv4 } from 'uuid'; -export function ParsePlaylistEpisodeDTO(episodeDTO: EpisodeDTO): Track { +export function ParsePlaylistEpisodeDTO(episodeDTO: EpisodeDTO, playlistId: string): Track { return { name: episodeDTO.name, spotifyId: episodeDTO.id, @@ -12,6 +12,7 @@ export function ParsePlaylistEpisodeDTO(episodeDTO: EpisodeDTO): Track { explicit: episodeDTO.explicit, durationMs: episodeDTO.duration_ms, artists: episodeDTO.album.artists.map(a => a.type), - image: GetSmallestImage(episodeDTO.album.images) + image: GetSmallestImage(episodeDTO.album.images), + source: playlistId } } \ No newline at end of file diff --git a/src/parsers/trackDtoParser.ts b/src/parsers/trackDtoParser.ts index 63ec546..ef2b4eb 100644 --- a/src/parsers/trackDtoParser.ts +++ b/src/parsers/trackDtoParser.ts @@ -3,7 +3,7 @@ import type { Track } from "@/types/tapeify/models"; import { GetSmallestImage } from "@/utils/images/imageUtils"; import { v4 as uuidv4 } from 'uuid'; -export function ParsePlaylistTrackDTO(track: PlaylistTrackDTO): Track { +export function ParsePlaylistTrackDTO(track: PlaylistTrackDTO, playlistId: string): Track { return { name: track.name, spotifyId: track.id, @@ -12,11 +12,12 @@ export function ParsePlaylistTrackDTO(track: PlaylistTrackDTO): Track { explicit: track.explicit, durationMs: track.duration_ms, artists: track.artists.map(a => a.name), - image: GetSmallestImage(track.album.images) + image: GetSmallestImage(track.album.images), + source: playlistId } } -export function ParseAlbumTrackDTO(track: TrackDTO, albumImage: URL | undefined): Track { +export function ParseAlbumTrackDTO(track: TrackDTO, albumImage: URL | undefined, albumId: string): Track { return { name: track.name, spotifyId: track.id, @@ -25,6 +26,7 @@ export function ParseAlbumTrackDTO(track: TrackDTO, albumImage: URL | undefined) explicit: track.explicit, durationMs: track.duration_ms, artists: track.artists.map(a => a.name), - image: albumImage + image: albumImage, + source: albumId } } diff --git a/src/stores/album.ts b/src/stores/album.ts index 2f7414c..5aa4dbb 100644 --- a/src/stores/album.ts +++ b/src/stores/album.ts @@ -8,12 +8,14 @@ import { apiClient } from '@/api/clients' import { useProfileStore } from './profile' import type { AlbumDTO } from '@/types/spotify/dto' import { ParseAlbumDTO } from '@/parsers/albumDtoParser' +import { useProjectStore } from './project' export const useAlbumsStore = defineStore('albums', { actions: { async FetchAlbumTracks(albumId: string) { const tracksStore = useTracksStore() const cassetteStore = useCassettesStore() + const projectStore = useProjectStore() const response = await apiClient.get('/albums/' + albumId) const album = response.data @@ -34,21 +36,20 @@ export const useAlbumsStore = defineStore('albums', { }) for (const item of tracksResponse.data.items) { - tracksStore.AddTrack(ParseAlbumTrackDTO(item, imageUrl)) + tracksStore.AddTrack(ParseAlbumTrackDTO(item, imageUrl, albumId)) } offset += limit } - cassetteStore.updateName('default', album.name) - cassetteStore.updateMetadata({ + projectStore.addSource({ + name: album.name, + type: 'album', owner_display_name: album.artists[0].name, - owner_url: album.artists[0].external_urls.spotify, - description: '', - image_url: new URL(album.images[0].url), original_item_url: album.external_urls.spotify, - item_name: album.name, - }) + owner_url: album.artists[0].external_urls.spotify, + description: "" + }, album.id) }, async searchAlbums(query: string, limit: number = 10, offset: number = 0) { const profileStore = useProfileStore() diff --git a/src/stores/cassette.ts b/src/stores/cassette.ts index 819c06c..b98bf86 100644 --- a/src/stores/cassette.ts +++ b/src/stores/cassette.ts @@ -2,7 +2,6 @@ import { defineStore } from 'pinia' import type { Cassette, CassetteAlert, - CassetteMetadata, TapeSideLayout, } from '@/types/tapeify/models' import { v4 as uuidv4 } from 'uuid' @@ -14,9 +13,8 @@ import { useLayoutStore } from './layout' export const useCassettesStore = defineStore('cassettes', { state: () => ({ possibleLengthsMin: [30, 45, 60, 90, 120], - metadata: {} as CassetteMetadata, cassettes: [ - { id: 'default', name: 'My First Cassette', capacityMs: 90 * 60000, sidesCount: 2 }, + { id: 'default', name: 'Cassette', capacityMs: 90 * 60000, sidesCount: 2 }, ] as Cassette[], alerts: {} as Record, }), @@ -34,7 +32,7 @@ export const useCassettesStore = defineStore('cassettes', { addCassette() { this.cassettes.push({ id: uuidv4(), - name: `${this.metadata.item_name} ${this.cassettes.length + 1}`, + name: `Cassette ${this.cassettes.length + 1}`, capacityMs: 90 * 60000, sidesCount: 2 }) @@ -68,10 +66,6 @@ export const useCassettesStore = defineStore('cassettes', { cassette.sidesCount = newSidesCount } }, - - updateMetadata(newMetadata: CassetteMetadata) { - this.metadata = newMetadata - }, initAlerts() { const layoutStore = useLayoutStore() diff --git a/src/stores/layout.ts b/src/stores/layout.ts index 0911818..8386b0d 100644 --- a/src/stores/layout.ts +++ b/src/stores/layout.ts @@ -5,6 +5,7 @@ import { useCassettesStore } from "./cassette"; import { useTracksStore } from "./tracks"; import { useAnchorsStore } from "./anchor"; import { TapeSide } from "@/sorting/tapeSideLayout"; +import { useProjectStore } from "./project"; export const useLayoutStore = defineStore('layout', { state: () => ({ @@ -63,6 +64,7 @@ export const useLayoutStore = defineStore('layout', { const cassetteStore = useCassettesStore() const trackStore = useTracksStore() const anchorsStore = useAnchorsStore() + const projectStore = useProjectStore() this.orderedTracks = [] this.trackLocations = {} @@ -79,10 +81,11 @@ export const useLayoutStore = defineStore('layout', { const trackSorter = trackSorterRegistry.create(this.selectedSortType, sides) - const tracks = trackStore.availableTracks + const availableTracks = trackStore.availableTracks + const tracksInSelectedOrigins = availableTracks.filter(track => projectStore.selectedSources.includes(track.source)) - const anchored_tracks = trackSorter.prepackAnchoredTracks(tracks, anchorsStore.anchors) - const unanchored_tracks = tracks.filter(t => !anchored_tracks.includes(t)) + const anchored_tracks = trackSorter.prepackAnchoredTracks(tracksInSelectedOrigins, anchorsStore.anchors) + const unanchored_tracks = tracksInSelectedOrigins.filter(t => !anchored_tracks.includes(t)) trackSorter.sortTracks(sides, unanchored_tracks) this._calculate_cassette_layout(sides) diff --git a/src/stores/playlists.ts b/src/stores/playlists.ts index 9d07013..6a75def 100644 --- a/src/stores/playlists.ts +++ b/src/stores/playlists.ts @@ -4,12 +4,11 @@ import type { CreatePlaylistResponse, GetPlaylistsResponse, GetPlaylistTracksRes import type { EpisodeDTO, PlaylistDTO, PlaylistTrackDTO } from '@/types/spotify/dto' import { ParsePlaylistTrackDTO } from '@/parsers/trackDtoParser' import { ParsePlaylistEpisodeDTO } from '@/parsers/episodeDtoParser' -import { useCassettesStore } from './cassette' import { apiClient } from '@/api/clients' import { ParsePlaylistDTO } from '@/parsers/playlistDtoParser' import type { Playlist, PlaylistSearchResult, Track } from '@/types/tapeify/models' import { useProfileStore } from './profile' -import { GetSmallestImage } from '@/utils/images/imageUtils' +import { useProjectStore } from './project' export const usePlaylistsStore = defineStore('playlists', { actions: { @@ -54,8 +53,8 @@ export const usePlaylistsStore = defineStore('playlists', { }, async FetchPlaylistTracks(playlistId: string) { - const cassetteStore = useCassettesStore() const tracksStore = useTracksStore() + const projectStore = useProjectStore() const playlistResponse = await apiClient.get('/playlists/' + playlistId) const playlist = playlistResponse.data @@ -77,25 +76,24 @@ export const usePlaylistsStore = defineStore('playlists', { for (const item of tracks.items) { const track = item.track if (track.type === 'track') { - tracksStore.AddTrack(ParsePlaylistTrackDTO(track as PlaylistTrackDTO)) + tracksStore.AddTrack(ParsePlaylistTrackDTO(track as PlaylistTrackDTO, playlistId)) } if (track.type === 'episode') { - tracksStore.AddTrack(ParsePlaylistEpisodeDTO(track as EpisodeDTO)) + tracksStore.AddTrack(ParsePlaylistEpisodeDTO(track as EpisodeDTO, playlistId)) } } offset += limit } - cassetteStore.updateName('default', playlist.name) - cassetteStore.updateMetadata({ + projectStore.addSource({ + name: playlist.name, + type: 'playlist', owner_display_name: playlist.owner.display_name, + original_item_url: playlist.external_urls.spotify, owner_url: playlist.owner.external_urls.spotify, description: playlist.description, - image_url: new URL(playlist.images[0].url), - original_item_url: playlist.external_urls.spotify, - item_name: playlist.name, - }) + }, playlist.id) }, async UploadNewPlaylist(name: string, description: string, isPublic: boolean): Promise { const profileStore = useProfileStore() diff --git a/src/stores/project.ts b/src/stores/project.ts new file mode 100644 index 0000000..234a2b1 --- /dev/null +++ b/src/stores/project.ts @@ -0,0 +1,25 @@ +import type { Source } from "@/types/tapeify/models"; +import { defineStore } from "pinia"; + +export const useProjectStore = defineStore('project', { + state: () => ({ + sources: {} as Record, + selectedSources: [] as string[], + drawerOpen: false + }), + getters: { + hasSources: (state) => Object.keys(state.sources).length > 0, + }, + actions: { + addSource(source: Source, id: string) { + this.sources[id] = source; + if (!this.selectedSources.includes(id)) { + this.selectedSources.push(id); + } + }, + removeSource(id: string) { + delete this.sources[id]; + this.selectedSources = this.selectedSources.filter(sourceId => sourceId !== id); + } + } +}) \ No newline at end of file diff --git a/src/stores/tracks.ts b/src/stores/tracks.ts index 0c0943f..274b9d5 100644 --- a/src/stores/tracks.ts +++ b/src/stores/tracks.ts @@ -42,6 +42,15 @@ export const useTracksStore = defineStore('tracks', { }, ClearSelectedTracks() { this.selectedTracks = [] + }, + RemoveTracksBySource(sourceId: string): string[] { + const tracksToRemove = this._masterTrackList.filter(track => track.source === sourceId) + const trackIdsToRemove = tracksToRemove.map(t => t.id) + + this._masterTrackList = this._masterTrackList.filter(track => track.source !== sourceId) + this.unavailableTrackIds = this.unavailableTrackIds.filter(id => !trackIdsToRemove.includes(id)) + this.selectedTracks = this.selectedTracks.filter(id => !trackIdsToRemove.includes(id)) + return trackIdsToRemove } } }) diff --git a/src/typed-router.d.ts b/src/typed-router.d.ts index 43541d4..8d179c7 100644 --- a/src/typed-router.d.ts +++ b/src/typed-router.d.ts @@ -37,16 +37,9 @@ declare module 'vue-router/auto-routes' { Record, | never >, - '/cassette/[spotify_id]': RouteRecordInfo< - '/cassette/[spotify_id]', - '/cassette/:spotify_id', - { spotify_id: ParamValue }, - { spotify_id: ParamValue }, - | never - >, - '/search': RouteRecordInfo< - '/search', - '/search', + '/project': RouteRecordInfo< + '/project', + '/project', Record, Record, | never @@ -76,15 +69,9 @@ declare module 'vue-router/auto-routes' { views: | never } - 'src/pages/cassette/[spotify_id].vue': { - routes: - | '/cassette/[spotify_id]' - views: - | never - } - 'src/pages/search.vue': { + 'src/pages/project.vue': { routes: - | '/search' + | '/project' views: | never } diff --git a/src/types/tapeify/models.ts b/src/types/tapeify/models.ts index 24049e3..def990b 100644 --- a/src/types/tapeify/models.ts +++ b/src/types/tapeify/models.ts @@ -58,6 +58,7 @@ export interface Track { explicit: boolean durationMs: number artists: string[] + source: string } export interface TrackLocation { @@ -77,15 +78,6 @@ export interface CassetteLayout { sides: Array } -export interface CassetteMetadata { - owner_display_name: string - item_name: string - original_item_url: string - owner_url: string - description: string - image_url?: URL -} - export interface CassetteAlert { message: string priority: number @@ -119,3 +111,12 @@ export type AlertRule = { payload?: TPayload ) => CassetteAlert['action'] } + +export interface Source { + type: 'playlist' | 'album', + name: string + owner_display_name: string + original_item_url: string + owner_url: string + description: string +}