Skip to content

Commit 21750b1

Browse files
feat: enhance download functionality and improve UI components
1 parent e16e668 commit 21750b1

File tree

11 files changed

+134
-111
lines changed

11 files changed

+134
-111
lines changed

src/assets/i18n/en.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"artist": "Artist",
2727
"addedTime": "Added Time",
2828
"origFileName": "Original File Name",
29-
"status": "Status"
29+
"status": "Status",
30+
"tag": "Applying Metadata"
3031
},
3132
"trackList": {
3233
"downloadSelected": "Download Selected",
@@ -41,7 +42,8 @@
4142
"geoRestrict": "Geo",
4243
"premium": "Premium",
4344
"source": "1 Source | {count} Sources",
44-
"noGenre": "No Genre"
45+
"noGenre": "No Genre",
46+
"freeDL": "Free DLs"
4547
},
4648
"playlists": {
4749
"playlist": "Playlist",

src/assets/i18n/zh-CN.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"artist": "艺术家",
2727
"addedTime": "添加时间",
2828
"origFileName": "原文件名",
29-
"status": "状态"
29+
"status": "状态",
30+
"tag": "添加音频元数据"
3031
},
3132
"trackList": {
3233
"downloadSelected": "下载选中",
@@ -41,7 +42,8 @@
4142
"geoRestrict": "地区限制",
4243
"premium": "会员专属",
4344
"source": "{count} 轨",
44-
"noGenre": "无流派"
45+
"noGenre": "无流派",
46+
"freeDL": "免费下载"
4547
},
4648
"playlists": {
4749
"playlist": "歌单",

src/assets/i18n/zh-TW.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"geoRestrict": "地區限制",
4242
"premium": "會員專屬",
4343
"source": "{count} 軌",
44-
"noGenre": "無流派"
44+
"noGenre": "無流派",
45+
"freeDL": "免費下載"
4546
},
4647
"playlists": {
4748
"playlist": "歌單",
@@ -59,7 +60,8 @@
5960
"deleteLocalFiles": "刪除本地檔案",
6061
"keepLocalFiles": "保留本地檔案",
6162
"cancel": "取消",
62-
"close": "關閉"
63+
"close": "關閉",
64+
"tag": "添加元數據"
6365
},
6466
"common": {
6567
"loading": "載入中...",

src/components/PlaylistList.vue

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
@click="open(item)"
1717
:title="item.playlist?.title ?? item.system_playlist!.title"
1818
class="bg-base-200 rounded-box flex flex-col gap-1 overflow-hidden outline transition-all hover:-translate-y-1 hover:cursor-pointer hover:opacity-70">
19-
<div class="bg-base-300 relative w-full aspect-square">
19+
<div class="bg-base-300 relative aspect-square w-full">
2020
<img
2121
v-if="getImageUrl(item).value"
2222
:src="getImageUrl(item).value"
2323
alt="cover"
2424
class="h-full w-full object-cover" />
25-
<div v-else class="skeleton rounded-none h-full w-full absolute inset-0"></div>
25+
<div v-else class="skeleton absolute inset-0 h-full w-full rounded-none"></div>
2626
</div>
2727
<div class="flex flex-col p-3">
2828
<div class="text-base-content truncate font-bold">
@@ -52,12 +52,9 @@
5252
<script setup lang="ts">
5353
import { computed } from "vue"
5454
import { replaceImageUrl } from "../utils/utils"
55-
import { toast } from "vue-sonner"
56-
import { PlaylistLike, Track } from "@/utils/types"
57-
import { i18n } from "@/systems/i18n"
55+
import { PlaylistLike } from "@/utils/types"
5856
import TracklistModal from "./modals/TracklistModal.vue"
5957
import { useModal } from "vue-final-modal"
60-
import { fetchPlaylistUpdates, getPlaylist, savePlaylist } from "@/systems/cache"
6158
6259
const props = defineProps<{
6360
items: PlaylistLike[]
@@ -66,36 +63,17 @@ const props = defineProps<{
6663
6764
// TODO: 可视化加载
6865
async function open(likeResp: PlaylistLike) {
69-
let playlistId = likeResp.playlist ? likeResp.playlist.id : likeResp.system_playlist.id
70-
71-
try {
72-
let currentPlaylist = await getPlaylist(playlistId)
73-
if (!currentPlaylist) {
74-
currentPlaylist = await fetchPlaylistUpdates(likeResp)
75-
}
76-
77-
const { open, close } = useModal({
78-
component: TracklistModal,
79-
attrs: {
80-
tracks: currentPlaylist!.tracks as Track[],
81-
currentResponse: likeResp,
82-
shouldAutoUpdate: !currentPlaylist,
83-
onClose() {
84-
close()
85-
},
66+
const { open, close } = useModal({
67+
component: TracklistModal,
68+
attrs: {
69+
currentResp: likeResp,
70+
onClose() {
71+
close()
8672
},
87-
})
88-
89-
open()
73+
},
74+
})
9075
91-
savePlaylist(currentPlaylist)
92-
} catch (err: any) {
93-
console.error("PlaylistList open error:", err)
94-
toast.error(i18n.global.t("cloudie.toasts.playlistOpenFailed"), {
95-
description: err.message,
96-
})
97-
return
98-
}
76+
open()
9977
}
10078
10179
function getImageUrl(item: PlaylistLike) {
@@ -109,7 +87,7 @@ function getImageUrl(item: PlaylistLike) {
10987
11088
if (!artworkUrl) return ""
11189
112-
return replaceImageUrl(artworkUrl, 200)
90+
return replaceImageUrl(artworkUrl)
11391
})
11492
}
11593
</script>

src/components/TrackList.vue

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,15 @@
118118
})
119119
}}
120120
</div>
121-
<!-- 未知:MONETIZE-->
122121
</div>
123122
</td>
124123

125124
<td>
126-
<div class="flex">
127-
<button class="btn btn-ghost btn-sm" @click="download(item)">
125+
<div class="flex justify-center">
126+
<div
127+
v-if="getDownloadTask(item).value?.downloadingState"
128+
class="loading loading-spinner loading-md"></div>
129+
<button v-else class="btn btn-ghost btn-sm" @click="download(item)">
128130
<i-mdi-download />
129131
</button>
130132
<a class="btn btn-ghost btn-sm" :href="item.permalink_url" target="_blank">
@@ -154,18 +156,37 @@
154156

155157
<script setup lang="ts">
156158
import { ref, computed, onMounted, useTemplateRef } from "vue"
157-
import { formatMillis, getArtist, getCoverUrl } from "../utils/utils"
158-
import { addDownloadTask } from "../systems/download/download"
159+
import { formatMillis, getArtist, getCoverUrl } from "@/utils/utils"
160+
import { addDownloadTask, downloadTasks } from "@/systems/download/download"
159161
import { LikedPlaylist, PlaylistLike, Track } from "@/utils/types"
160162
import { useInfiniteScroll } from "@vueuse/core"
161163
162-
// 音乐显示
163-
164164
const selectedIds = ref<number[]>([])
165165
const freeFilter = ref(false)
166166
const searchQuery = ref("")
167167
const selectedGenres = ref<(string | null)[]>([]) // TODO: 获取tag_list
168-
const scrollContainer = useTemplateRef<HTMLDivElement>("scrollContainer")
168+
const scrollContainer = useTemplateRef<HTMLDivElement>("scrollContainer") // TODO: fix virtual scroll
169+
170+
const props = defineProps<{
171+
tracks: Track[]
172+
playlistResponse?: PlaylistLike
173+
scrollCallbacks?: { canLoadMore: () => boolean; onTrigger: () => void }
174+
}>()
175+
176+
onMounted(() => {
177+
if (props.scrollCallbacks) {
178+
useInfiniteScroll(scrollContainer.value, props.scrollCallbacks.onTrigger, {
179+
distance: 10,
180+
canLoadMore: props.scrollCallbacks?.canLoadMore,
181+
})
182+
}
183+
})
184+
185+
function getDownloadTask(item: Track) {
186+
return computed(() => {
187+
return downloadTasks.value.find((t) => t.task.trackId === item.id)
188+
})
189+
}
169190
170191
const filteredItems = computed(() => {
171192
let items = props.tracks
@@ -276,21 +297,6 @@ function isPossibleFreeDownload(track: Track) {
276297
277298
return isFreeDownload
278299
}
279-
280-
const props = defineProps<{
281-
tracks: Track[]
282-
playlistResponse?: PlaylistLike
283-
scrollCallbacks?: { canLoadMore: () => boolean; onTrigger: () => void }
284-
}>()
285-
286-
onMounted(() => {
287-
if (props.scrollCallbacks) {
288-
useInfiniteScroll(scrollContainer.value, props.scrollCallbacks.onTrigger, {
289-
distance: 10,
290-
canLoadMore: props.scrollCallbacks?.canLoadMore,
291-
})
292-
}
293-
})
294300
</script>
295301

296302
<style scoped>

src/components/modals/TracklistModal.vue

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,23 @@
33
class="flex items-center justify-center"
44
overlay-transition="vfm-fade"
55
content-transition="vfm-fade"
6-
content-class="max-w-screen modal-box opacity-100">
7-
<div v-if="currentResponse" class="text-xl">
8-
{{ currentResponse.playlist?.title || currentResponse.system_playlist?.title }}
6+
content-class="max-w-screen modal-box opacity-100 ">
7+
<div v-if="currentResp" class="text-xl">
8+
{{ currentResp.playlist?.title || currentResp.system_playlist?.title }}
99
</div>
1010
<div>
11-
<TrackList :tracks="tracks" :playlist-response="currentResponse" />
11+
<TrackList :tracks="tracks" :playlist-response="currentResp">
12+
<template #bottom>
13+
<template v-if="loading">
14+
<div class="loading loading-spinner loading-lg"></div>
15+
<span class="ml-2">{{ $t("cloudie.common.loading") }}</span>
16+
</template>
17+
18+
<template v-else>
19+
<span class="ml-2">{{ $t("cloudie.common.noMore") }}</span>
20+
</template>
21+
</template>
22+
</TrackList>
1223
</div>
1324
<div class="modal-action">
1425
<button class="btn" @click="emit('close')">{{ $t("cloudie.toasts.close") }}</button>
@@ -22,30 +33,55 @@ import TrackList from "../TrackList.vue"
2233
import { Playlist, PlaylistLike, SystemPlaylist, Track } from "@/utils/types"
2334
import { onMounted, ref } from "vue"
2435
import { fetchPlaylistUpdates } from "@/systems/cache"
36+
import { getPlaylist } from "@/systems/cache"
37+
import { toast } from "vue-sonner"
38+
import { i18n } from "@/systems/i18n"
39+
import { savePlaylist } from "@/systems/cache"
2540
2641
const props = defineProps<{
27-
tracks: Track[]
28-
currentResponse: PlaylistLike
29-
shouldAutoUpdate: boolean
42+
currentResp: PlaylistLike
3043
}>()
3144
32-
const playlistRef = ref(props.currentResponse)
33-
34-
onMounted(() => {
35-
if (props.shouldAutoUpdate) {
36-
// Reactively update playtlist meta
37-
fetchPlaylistUpdates(
38-
props.currentResponse,
39-
(props.currentResponse.playlist ?? props.currentResponse.system_playlist).tracks!.map(
40-
(t) => t.id,
41-
),
42-
).then((playlist) => {
43-
if (playlistRef.value.system_playlist) {
44-
playlistRef.value.system_playlist = playlist as SystemPlaylist
45-
} else {
46-
playlistRef.value.playlist = playlist as Playlist
47-
}
45+
const playlistRef = ref(props.currentResp)
46+
const loading = ref(true)
47+
const tracks = ref<Track[]>([])
48+
49+
onMounted(async () => {
50+
let playlistId = props.currentResp.playlist
51+
? props.currentResp.playlist.id
52+
: props.currentResp.system_playlist.id
53+
54+
try {
55+
let currentPlaylist = await getPlaylist(playlistId)
56+
const newCreatedPlaylist = !currentPlaylist
57+
if (newCreatedPlaylist) {
58+
currentPlaylist = await fetchPlaylistUpdates(props.currentResp)
59+
}
60+
61+
tracks.value = currentPlaylist!.tracks as Track[]
62+
loading.value = false
63+
64+
await savePlaylist(currentPlaylist)
65+
66+
if (!newCreatedPlaylist) {
67+
// Reactively update playtlist meta
68+
fetchPlaylistUpdates(
69+
props.currentResp,
70+
(props.currentResp.playlist ?? props.currentResp.system_playlist).tracks!.map((t) => t.id), // FIXME: Undefinded???
71+
).then((playlist) => {
72+
if (playlistRef.value.system_playlist) {
73+
playlistRef.value.system_playlist = playlist as SystemPlaylist
74+
} else {
75+
playlistRef.value.playlist = playlist as Playlist
76+
}
77+
})
78+
}
79+
} catch (err: any) {
80+
console.error("PlaylistList open error:", err)
81+
toast.error(i18n.global.t("cloudie.toasts.playlistOpenFailed"), {
82+
description: err.message,
4883
})
84+
return
4985
}
5086
})
5187

src/systems/download/download.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import { ref } from "vue"
66
import { config } from "@/systems/config"
7-
import { getArtist, getCoverUrl } from "@/utils/utils"
7+
import { getArtist, getCoverUrl, replaceImageUrl } from "@/utils/utils"
88
import { db } from "@/systems/db"
99
import * as schema from "@/systems/db/schema"
1010
import { desc, DrizzleQueryError, eq, inArray } from "drizzle-orm"
@@ -75,7 +75,7 @@ export class DownloadTask {
7575

7676
class DownloadStat {
7777
progress: number = 0 // from 0 to 1
78-
name: "pending" | "getinfo" | "downloading" = "pending"
78+
name: "pending" | "getinfo" | "downloading" | "tag" = "pending"
7979
}
8080

8181
class DownloadDetail {
@@ -219,27 +219,28 @@ async function runTask(task: DownloadTask) {
219219
task.downloadingState!.progress = progress
220220
})
221221

222-
task.setPath(response.path)
223-
task.setOrigFileName(response.origFileName)
222+
await task.setPath(response.path)
223+
await task.setOrigFileName(response.origFileName)
224224

225225
try {
226+
task.downloadingState.name = "tag"
226227
await invoke("add_tags", {
227228
filePath: task.task.path,
228229
title: task.details.title,
229230
album: task.details.playlistName,
230231
artist: task.details.artist,
231-
coverUrl: config.value.addCover ? task.details.coverUrl : undefined,
232+
coverUrl: config.value.addCover ? replaceImageUrl(task.details.coverUrl, "1080x1080") : undefined,
232233
})
233234
} catch (error) {
234235
console.error(`Error adding tags to ${task.task.path},Ignoring tags...`, error)
235236
}
236237

237-
task.setStatus("completed")
238+
await task.setStatus("completed")
238239
task.downloadingState = undefined
239240
console.debug(task.task.status, task)
240241
} catch (err) {
241242
console.error("Download failed: ", err)
242-
task.setStatus("failed")
243+
await task.setStatus("failed")
243244
task.failedReason = err as string
244245
task.downloadingState = undefined
245246
}

0 commit comments

Comments
 (0)