Skip to content
Merged
3 changes: 2 additions & 1 deletion src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand All @@ -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']
}
}
15 changes: 8 additions & 7 deletions src/components/AlbumList.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
<script lang="ts" setup>
import router from '@/router';
import type { Album } from '@/types/tapeify/models';
import type { InfiniteScrollSide, InfiniteScrollStatus } from 'vuetify/lib/components/VInfiniteScroll/VInfiniteScroll.mjs';
import { VInfiniteScroll } from 'vuetify/components';
import { useAlbumsStore } from '@/stores/album';
import { useLayoutStore } from '@/stores/layout';

const albumsStore = useAlbumsStore()
const layoutStore = useLayoutStore()

const props = defineProps<{
albums: Album[]
Expand All @@ -11,12 +15,9 @@ const props = defineProps<{

const infiniteScrollRef = useTemplateRef<InstanceType<typeof VInfiniteScroll>>('albumsScroll')

function SelectItem(id: string) {
router.push({
name: '/cassette/[spotify_id]',
params: { spotify_id: id },
query: { type: 'album' }
})
async function SelectItem(id: string) {
await albumsStore.FetchAlbumTracks(id)
layoutStore.calculateLayout()
}

function reset() {
Expand Down
104 changes: 91 additions & 13 deletions src/components/CassetteActionsBar.vue
Original file line number Diff line number Diff line change
@@ -1,31 +1,109 @@
<script lang="ts" setup>
import { useAnchorsStore } from '@/stores/anchor';
import { useCassettesStore } from '@/stores/cassette';
import { useLayoutStore } from '@/stores/layout';
import { useProjectStore } from '@/stores/project';
import { useTracksStore } from '@/stores/tracks';

const layoutStore = useLayoutStore()
const projectStore = useProjectStore()
const cassetteStore = useCassettesStore()
const trackStore = useTracksStore()
const anchorStore = useAnchorsStore()

const selectedSortType = computed({
get: () => layoutStore.selectedSortType,
set: (val: string) => layoutStore.setSortType(val)
})

function addCassette() {
cassetteStore.addCassette()
layoutStore.calculateLayout()
}

function removeSource(sourceId: string) {
projectStore.removeSource(sourceId)
const removedTracks = trackStore.RemoveTracksBySource(sourceId)
removedTracks.forEach(trackId => {
anchorStore.removeAnchor(trackId)
})
layoutStore.calculateLayout()
}

const sources = computed(() => {
return Object.entries(projectStore.sources).map(([id, source]) => ({
id,
name: source.name
}))
})

const menuIcon = computed(() => projectStore.drawerOpen ? "mdi-menu-close" : "mdi-menu-open")
const menuBadgeContent = computed(() => trackStore.unavailableTrackIds.length > 0 ? trackStore.unavailableTrackIds.length : undefined)
</script>

<template>
<v-app-bar class="included pa-1">
<template v-slot:prepend>
<v-select v-model="selectedSortType" :items="layoutStore.getAvailableSorters()" item-value="type"
density="compact" label="Sorting Algorithm" item-title="name" hide-details min-width="200" variant="outlined">
<template v-slot:item="{ props: itemProps, item }">
<v-list-item v-bind="itemProps" :subtitle="item.raw.description" :title="item.raw.name" />
</template>
</v-select>
</template>

<v-app-bar class="included pa-1" flat color="transparent">
<template v-slot:append>
<v-btn block variant="outlined">
Upload Cassette
<upload-cassette-dialog />
<v-btn @click="projectStore.drawerOpen = !projectStore.drawerOpen" stacked>
<v-badge v-if="menuBadgeContent" color="secondary" :content="menuBadgeContent" location="top left" :offset-x="-5">
<v-icon :icon="menuIcon" />
</v-badge>
<v-icon v-else :icon="menuIcon" />
</v-btn>

</template>

<v-spacer />
<v-card>
<template v-slot:actions>

<!-- Add items -->
<add-source-dialog />

<!-- Remove items -->
<v-select :items="sources" item-value="id" item-title="name" label="Sources" chips multiple density="compact"
variant="outlined" hide-details v-model="projectStore.selectedSources"
@update:modelValue="layoutStore.calculateLayout" :disabled="!projectStore.hasSources">
<template v-slot:item="{ props: itemProps, item }">
<v-list-item v-bind="itemProps" :title="item.raw.name">
<template v-slot:prepend="{ isSelected, select }">
<v-list-item-action start>
<v-checkbox-btn :model-value="isSelected" @update:model-value="select"></v-checkbox-btn>
</v-list-item-action>
</template>
<template v-slot:append>
<v-btn icon="mdi-playlist-remove" size="small" variant="text" @click.stop="removeSource(item.raw.id)" />
</template>
</v-list-item>
</template>
</v-select>

<v-divider vertical />

<v-btn icon="mdi-cassette" size="small" variant="text" @click="addCassette"
:disabled="!projectStore.hasSources" />

<v-divider vertical />

<!-- Sorting select -->
<v-select v-model="selectedSortType" :items="layoutStore.getAvailableSorters()" item-value="type"
density="compact" label="Sorting Algorithm" item-title="name" hide-details min-width="200" variant="outlined"
:disabled="!projectStore.hasSources">
<template v-slot:item="{ props: itemProps, item }">
<v-list-item v-bind="itemProps" :subtitle="item.raw.description" :title="item.raw.name" />
</template>
</v-select>

<v-divider vertical />


<v-btn icon="mdi-import" size="small" variant="text" />
<v-btn icon="mdi-export" size="small" variant="text" :disabled="!projectStore.hasSources" />

<v-divider vertical />
<upload-cassette-dialog />
</template>
</v-card>
<v-spacer />
</v-app-bar>
</template>
40 changes: 4 additions & 36 deletions src/components/CassetteInfoDrawer.vue
Original file line number Diff line number Diff line change
@@ -1,43 +1,11 @@
<script setup lang="ts">
import { useCassettesStore } from '@/stores/cassette';
import { useTracksStore } from '@/stores/tracks';
import { formatDuration } from '@/utils/duration/durationHelper';

const cassetteStore = useCassettesStore()
const tracksStore = useTracksStore()
<script lang="ts" setup>
import { useProjectStore } from '@/stores/project';

const projectStore = useProjectStore()
</script>

<template>
<v-navigation-drawer location="left" class="pa-4" color="primary" permanent>
<v-img :src="cassetteStore.metadata.image_url?.toString()" aspect-ratio="1" cover class="rounded-lg" />
<a :href="cassetteStore.metadata.original_item_url?.toString()" class="text-h5 clickable" target="_blank"
v-text="cassetteStore.metadata.item_name"></a>
<br />
<a :href="cassetteStore.metadata.owner_url?.toString()" class="text-subtitle-1 clickable" target="_blank"
v-text="cassetteStore.metadata.owner_display_name"></a>
<v-divider class="my-4" />
<div class="text-body-1"><strong>Cassettes:</strong> {{ cassetteStore.cassettes.length }}</div>
<div class="text-body-1"><strong>Tracks:</strong> {{ tracksStore.availableTracks.length }}</div>
<div class="text-body-1"><strong>Total Duration:</strong> {{ formatDuration(tracksStore.availableTracksTotalDuration) }}</div>
<v-divider class="my-4" />
<v-navigation-drawer floating v-model="projectStore.drawerOpen" location="right">
<unavailable-tracks-list />
</v-navigation-drawer>
</template>

<style scoped>
.clickable {
text-decoration: none;
color: inherit;
}

.clickable:visited,
.clickable:hover,
.clickable:active {
color: inherit;
}

.clickable:hover {
text-decoration: underline;
}
</style>
15 changes: 8 additions & 7 deletions src/components/PlaylistList.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
<script lang="ts" setup>
import router from '@/router';
import type { Playlist } from '@/types/tapeify/models';
import type { InfiniteScrollSide, InfiniteScrollStatus } from 'vuetify/lib/components/VInfiniteScroll/VInfiniteScroll.mjs';
import { VInfiniteScroll } from 'vuetify/components';
import { usePlaylistsStore } from '@/stores/playlists';
import { useLayoutStore } from '@/stores/layout';

const playlistStore = usePlaylistsStore()
const layoutStore = useLayoutStore()

const props = defineProps<{
playlists: Playlist[]
Expand All @@ -11,12 +15,9 @@ const props = defineProps<{

const infiniteScrollRef = useTemplateRef<InstanceType<typeof VInfiniteScroll>>('playlistScroll')

function SelectItem(id: string) {
router.push({
name: '/cassette/[spotify_id]',
params: { spotify_id: id },
query: { type: 'playlist' }
})
async function SelectItem(id: string) {
await playlistStore.FetchPlaylistTracks(id)
layoutStore.calculateLayout()
}

function reset() {
Expand Down
38 changes: 0 additions & 38 deletions src/components/SearchAlbumsTab.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script lang="ts" setup>
import router from '@/router'
import { useAlbumsStore } from '@/stores/album';
import type { AlbumSearchResult } from '@/types/tapeify/models';
import type { InfiniteScrollSide, InfiniteScrollStatus } from 'vuetify/lib/components/VInfiniteScroll/VInfiniteScroll.mjs';
Expand All @@ -15,40 +14,21 @@ const albums = ref<AlbumSearchResult>({
next: false,
previous: false
})
const TAB_NAME = 'search_albums'
const DEBOUNCE_DELAY_MS = 200

let query = ref('')
watch(query, (newQuery) => {
if (newQuery.length === 0) {
albums.value.albums = []
updateUrl()
return
}
loading.value = true
searchAlbums(newQuery, limit.value, offset.value)
loading.value = false
})

onMounted(async () => {
const url = new URL(location.href)

const queryParam = url.searchParams.get('query')
const tabParam = url.searchParams.get('tab')

if (queryParam !== null && tabParam === TAB_NAME) {
query.value = queryParam
albums.value = await albumsStore.searchAlbums(
query.value,
limit.value,
offset.value
)
}
})

const searchAlbums = debounce(async (query: string, limit: number = 10, offset: number = 0) => {
{
updateUrl()
if (query === '') {
albums.value.albums = []
} else {
Expand All @@ -67,7 +47,6 @@ async function LoadMoreAlbums({ side, done }: { side: InfiniteScrollSide; done:
albums.value.albums = []
return
}
updateUrl()
offset.value += limit.value
done('loading')
await albumsStore.searchAlbums(
Expand All @@ -88,23 +67,6 @@ function ClearSearchBar() {
albums.value.albums = []
query.value = ''
offset.value = 0
updateUrl()
}

function updateUrl() {
if (query.value === '') {
router.push({
name: '/search',
});
} else {
router.push({
name: '/search',
query: {
query: query.value,
tab: TAB_NAME
}
});
}
}

</script>
Expand Down
Loading