@ozdao/martyrs 0.2.565 → 0.2.567
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{main-B9o1iBAZ.js → main-BFvlam0J.js} +9 -6
- package/dist/martyrs/dist/main-BFvlam0J.js +943 -0
- package/dist/martyrs/dist/main-BFvlam0J.js.map +1 -0
- package/dist/martyrs/dist/web-CH5wzMHy.js +55 -0
- package/dist/martyrs/dist/web-CH5wzMHy.js.map +1 -0
- package/dist/martyrs/src/components/BottomSheet/BottomSheet.vue.js +96 -0
- package/dist/martyrs/src/components/BottomSheet/BottomSheet.vue.js.map +1 -0
- package/dist/martyrs/src/components/Feed/Carousel.vue.js +2 -2
- package/dist/martyrs/src/components/Feed/Carousel.vue.js.map +1 -1
- package/dist/martyrs/src/components/Feed/Feed.vue.js +6 -3
- package/dist/martyrs/src/components/Feed/Feed.vue.js.map +1 -1
- package/dist/martyrs/src/components/Menu/{Menu.vue2.js → Menu.vue.js} +2 -2
- package/dist/martyrs/src/components/Menu/Menu.vue.js.map +1 -0
- package/dist/martyrs/src/modules/auth/views/components/pages/Profile.vue.js +1 -1
- package/dist/martyrs/src/modules/community/components/sections/HotPosts.vue.js.map +1 -1
- package/dist/martyrs/src/modules/core/views/components/layouts/App.vue.js +1 -1
- package/dist/martyrs/src/modules/core/views/components/layouts/Client.vue.js +1 -0
- package/dist/martyrs/src/modules/core/views/components/layouts/Client.vue.js.map +1 -1
- package/dist/martyrs/src/modules/core/views/components/sections/Walkthrough.vue.js +1 -1
- package/dist/martyrs/src/modules/events/components/elements/ButtonCheck.vue.js +1 -1
- package/dist/martyrs/src/modules/events/components/sections/EventsHot.vue.js +17 -1
- package/dist/martyrs/src/modules/events/components/sections/EventsHot.vue.js.map +1 -1
- package/dist/martyrs/src/modules/events/events.client.js +15 -12
- package/dist/martyrs/src/modules/events/events.client.js.map +1 -1
- package/dist/martyrs/src/modules/music/components/blocks/ActionButtons.vue.js +95 -0
- package/dist/martyrs/src/modules/music/components/blocks/ActionButtons.vue.js.map +1 -0
- package/dist/martyrs/src/modules/music/components/cards/AlbumCard.vue.js +5 -2
- package/dist/martyrs/src/modules/music/components/cards/AlbumCard.vue.js.map +1 -1
- package/dist/martyrs/src/modules/music/components/cards/ArtistCardSmall.vue.js +24 -24
- package/dist/martyrs/src/modules/music/components/cards/ArtistCardSmall.vue.js.map +1 -1
- package/dist/martyrs/src/modules/music/components/cards/TrackListCard.vue.js +3 -3
- package/dist/martyrs/src/modules/music/components/cards/TrackListCard.vue.js.map +1 -1
- package/dist/martyrs/src/modules/music/components/layouts/MusicBottomPlayer.vue.js +31 -6
- package/dist/martyrs/src/modules/music/components/layouts/MusicBottomPlayer.vue.js.map +1 -1
- package/dist/martyrs/src/modules/music/components/pages/Album.vue.js +120 -205
- package/dist/martyrs/src/modules/music/components/pages/Album.vue.js.map +1 -1
- package/dist/martyrs/src/modules/music/components/pages/MusicHome.vue.js +9 -13
- package/dist/martyrs/src/modules/music/components/pages/MusicHome.vue.js.map +1 -1
- package/dist/martyrs/src/modules/music/components/pages/Playlist.vue.js +166 -245
- package/dist/martyrs/src/modules/music/components/pages/Playlist.vue.js.map +1 -1
- package/dist/martyrs/src/modules/music/components/pages/Track.vue.js +135 -220
- package/dist/martyrs/src/modules/music/components/pages/Track.vue.js.map +1 -1
- package/dist/martyrs/src/modules/music/components/player/FullscreenPlayer.vue.js +171 -0
- package/dist/martyrs/src/modules/music/components/player/FullscreenPlayer.vue.js.map +1 -0
- package/dist/martyrs/src/modules/music/components/player/MusicPlayer.vue.js +31 -153
- package/dist/martyrs/src/modules/music/components/player/MusicPlayer.vue.js.map +1 -1
- package/dist/martyrs/src/modules/music/components/player/PlayerControls.vue.js +96 -0
- package/dist/martyrs/src/modules/music/components/player/PlayerControls.vue.js.map +1 -0
- package/dist/martyrs/src/modules/music/components/player/VolumeControl.vue.js +55 -27
- package/dist/martyrs/src/modules/music/components/player/VolumeControl.vue.js.map +1 -1
- package/dist/martyrs/src/modules/music/components/player/tonar.png.js +5 -0
- package/dist/martyrs/src/modules/music/components/player/tonar.png.js.map +1 -0
- package/dist/martyrs/src/modules/music/store/albums.js +8 -2
- package/dist/martyrs/src/modules/music/store/albums.js.map +1 -1
- package/dist/martyrs/src/modules/music/store/player.js +83 -65
- package/dist/martyrs/src/modules/music/store/player.js.map +1 -1
- package/dist/martyrs/src/modules/music/store/tracks.js +4 -13
- package/dist/martyrs/src/modules/music/store/tracks.js.map +1 -1
- package/dist/martyrs/src/modules/notifications/notifications.client.js +2 -2
- package/dist/martyrs/src/modules/organizations/components/pages/Organization.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationBackoffice.vue.js +1 -1
- package/dist/martyrs.css +1 -1
- package/dist/martyrs.es.js +1 -1
- package/dist/music.server.js +33 -6
- package/dist/node_modules/.pnpm/{@capacitor-mlkit_barcode-scanning@7.1.0_@capacitor_core@7.0.1 → @capacitor-mlkit_barcode-scanning@7.3.0_@capacitor_core@7.4.4}/node_modules/@capacitor-mlkit/barcode-scanning/dist/esm/definitions.js +1 -0
- package/dist/node_modules/.pnpm/@capacitor-mlkit_barcode-scanning@7.3.0_@capacitor_core@7.4.4/node_modules/@capacitor-mlkit/barcode-scanning/dist/esm/definitions.js.map +1 -0
- package/dist/node_modules/.pnpm/{@capacitor-mlkit_barcode-scanning@7.1.0_@capacitor_core@7.0.1 → @capacitor-mlkit_barcode-scanning@7.3.0_@capacitor_core@7.4.4}/node_modules/@capacitor-mlkit/barcode-scanning/dist/esm/index.js +1 -1
- package/dist/node_modules/.pnpm/{@capacitor-mlkit_barcode-scanning@7.1.0_@capacitor_core@7.0.1 → @capacitor-mlkit_barcode-scanning@7.3.0_@capacitor_core@7.4.4}/node_modules/@capacitor-mlkit/barcode-scanning/dist/esm/index.js.map +1 -1
- package/dist/node_modules/.pnpm/{@capacitor-mlkit_barcode-scanning@7.1.0_@capacitor_core@7.0.1 → @capacitor-mlkit_barcode-scanning@7.3.0_@capacitor_core@7.4.4}/node_modules/@capacitor-mlkit/barcode-scanning/dist/esm/web.js +16 -1
- package/dist/node_modules/.pnpm/@capacitor-mlkit_barcode-scanning@7.3.0_@capacitor_core@7.4.4/node_modules/@capacitor-mlkit/barcode-scanning/dist/esm/web.js.map +1 -0
- package/dist/node_modules/.pnpm/{@capacitor_core@7.0.1 → @capacitor_core@7.4.4}/node_modules/@capacitor/core/dist/index.js +2 -1
- package/dist/node_modules/.pnpm/@capacitor_core@7.4.4/node_modules/@capacitor/core/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/{@capacitor_device@7.0.0_@capacitor_core@7.0.1 → @capacitor_device@7.0.1_@capacitor_core@7.4.4}/node_modules/@capacitor/device/dist/esm/index.js +1 -1
- package/dist/node_modules/.pnpm/{@capacitor_device@7.0.0_@capacitor_core@7.0.1 → @capacitor_device@7.0.1_@capacitor_core@7.4.4}/node_modules/@capacitor/device/dist/esm/index.js.map +1 -1
- package/dist/node_modules/.pnpm/{@capacitor_device@7.0.0_@capacitor_core@7.0.1 → @capacitor_device@7.0.1_@capacitor_core@7.4.4}/node_modules/@capacitor/device/dist/esm/web.js +1 -1
- package/dist/node_modules/.pnpm/{@capacitor_device@7.0.0_@capacitor_core@7.0.1 → @capacitor_device@7.0.1_@capacitor_core@7.4.4}/node_modules/@capacitor/device/dist/esm/web.js.map +1 -1
- package/dist/node_modules/.pnpm/{@capacitor_keyboard@7.0.0_@capacitor_core@7.0.1 → @capacitor_keyboard@7.0.1_@capacitor_core@7.4.4}/node_modules/@capacitor/keyboard/dist/esm/definitions.js.map +1 -1
- package/dist/node_modules/.pnpm/{@capacitor_keyboard@7.0.0_@capacitor_core@7.0.1 → @capacitor_keyboard@7.0.1_@capacitor_core@7.4.4}/node_modules/@capacitor/keyboard/dist/esm/index.js +1 -1
- package/dist/node_modules/.pnpm/{@capacitor_keyboard@7.0.0_@capacitor_core@7.0.1 → @capacitor_keyboard@7.0.1_@capacitor_core@7.4.4}/node_modules/@capacitor/keyboard/dist/esm/index.js.map +1 -1
- package/dist/node_modules/.pnpm/{@capacitor_push-notifications@7.0.0_@capacitor_core@7.0.1 → @capacitor_push-notifications@7.0.3_@capacitor_core@7.4.4}/node_modules/@capacitor/push-notifications/dist/esm/index.js +1 -1
- package/dist/node_modules/.pnpm/{@capacitor_push-notifications@7.0.0_@capacitor_core@7.0.1 → @capacitor_push-notifications@7.0.3_@capacitor_core@7.4.4}/node_modules/@capacitor/push-notifications/dist/esm/index.js.map +1 -1
- package/dist/node_modules/.pnpm/{capacitor-plugin-app-tracking-transparency@2.0.5_@capacitor_core@7.0.1 → capacitor-plugin-app-tracking-transparency@2.0.5_@capacitor_core@7.4.4}/node_modules/capacitor-plugin-app-tracking-transparency/dist/esm/index.js +1 -1
- package/dist/node_modules/.pnpm/{capacitor-plugin-app-tracking-transparency@2.0.5_@capacitor_core@7.0.1 → capacitor-plugin-app-tracking-transparency@2.0.5_@capacitor_core@7.4.4}/node_modules/capacitor-plugin-app-tracking-transparency/dist/esm/index.js.map +1 -1
- package/dist/node_modules/.pnpm/{capacitor-plugin-app-tracking-transparency@2.0.5_@capacitor_core@7.0.1 → capacitor-plugin-app-tracking-transparency@2.0.5_@capacitor_core@7.4.4}/node_modules/capacitor-plugin-app-tracking-transparency/dist/esm/web.js +1 -1
- package/dist/node_modules/.pnpm/{capacitor-plugin-app-tracking-transparency@2.0.5_@capacitor_core@7.0.1 → capacitor-plugin-app-tracking-transparency@2.0.5_@capacitor_core@7.4.4}/node_modules/capacitor-plugin-app-tracking-transparency/dist/esm/web.js.map +1 -1
- package/dist/style.css +221 -145
- package/dist/{web-BF3ijvEr.js → web-CH5wzMHy.js} +1 -1
- package/package.json +1 -1
- package/src/components/BottomSheet/BottomSheet.vue +4 -4
- package/src/components/Feed/Carousel.vue +1 -1
- package/src/components/Feed/Feed.vue +3 -3
- package/src/modules/LAYOUT.MD +767 -0
- package/src/modules/community/components/sections/HotPosts.vue +8 -4
- package/src/modules/core/views/components/layouts/Client.vue +1 -1
- package/src/modules/events/components/sections/EventsHot.vue +21 -4
- package/src/modules/events/events.client.js +3 -0
- package/src/modules/music/components/blocks/ActionButtons.vue +74 -0
- package/src/modules/music/components/cards/AlbumCard.vue +1 -1
- package/src/modules/music/components/cards/ArtistCardSmall.vue +8 -6
- package/src/modules/music/components/cards/TrackListCard.vue +6 -6
- package/src/modules/music/components/layouts/MusicBottomPlayer.vue +94 -4
- package/src/modules/music/components/pages/Album.vue +55 -67
- package/src/modules/music/components/pages/MusicHome.vue +4 -6
- package/src/modules/music/components/pages/Playlist.vue +61 -70
- package/src/modules/music/components/pages/Track.vue +54 -71
- package/src/modules/music/components/player/FullscreenPlayer.vue +248 -0
- package/src/modules/music/components/player/MusicPlayer.vue +21 -216
- package/src/modules/music/components/player/PlayerControls.vue +112 -0
- package/src/modules/music/components/player/Visualizer.vue +151 -0
- package/src/modules/music/components/player/VolumeControl.vue +75 -23
- package/src/modules/music/components/player/tonar.png +0 -0
- package/src/modules/music/routes/albums.routes.js +13 -12
- package/src/modules/music/routes/tracks.routes.js +39 -0
- package/src/modules/music/store/albums.js +10 -2
- package/src/modules/music/store/player.js +101 -89
- package/src/modules/music/store/tracks.js +5 -21
- package/src/styles/config.scss +6 -6
- package/dist/martyrs/src/components/Menu/Menu.vue2.js.map +0 -1
- package/dist/node_modules/.pnpm/@capacitor-mlkit_barcode-scanning@7.1.0_@capacitor_core@7.0.1/node_modules/@capacitor-mlkit/barcode-scanning/dist/esm/definitions.js.map +0 -1
- package/dist/node_modules/.pnpm/@capacitor-mlkit_barcode-scanning@7.1.0_@capacitor_core@7.0.1/node_modules/@capacitor-mlkit/barcode-scanning/dist/esm/web.js.map +0 -1
- package/dist/node_modules/.pnpm/@capacitor_core@7.0.1/node_modules/@capacitor/core/dist/index.js.map +0 -1
- /package/dist/node_modules/.pnpm/{@capacitor_keyboard@7.0.0_@capacitor_core@7.0.1 → @capacitor_keyboard@7.0.1_@capacitor_core@7.4.4}/node_modules/@capacitor/keyboard/dist/esm/definitions.js +0 -0
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { ref, computed, onMounted, resolveComponent, createElementBlock, openBlock, createCommentVNode, createVNode, createElementVNode, toDisplayString,
|
|
1
|
+
import { ref, computed, onMounted, resolveComponent, createElementBlock, openBlock, createCommentVNode, createVNode, createElementVNode, toDisplayString, Fragment, renderList, createBlock, withCtx, createTextVNode } from "vue";
|
|
2
2
|
import { useRoute, useRouter } from "vue-router";
|
|
3
|
-
import _sfc_main$1 from "../../../../components/Button/Button.vue2.js";
|
|
4
3
|
import Loader from "../../../../components/Loader/Loader.vue.js";
|
|
4
|
+
/* empty css */
|
|
5
5
|
import Media from "../../../../components/Media/Media.vue.js";
|
|
6
|
-
|
|
7
|
-
import _sfc_main$
|
|
8
|
-
import _sfc_main$
|
|
9
|
-
import _sfc_main$
|
|
10
|
-
import _sfc_main$
|
|
11
|
-
import _sfc_main$
|
|
12
|
-
import _sfc_main$7 from "../../../icons/entities/IconCalendar.vue.js";
|
|
6
|
+
/* empty css */
|
|
7
|
+
import _sfc_main$8 from "../../../../components/Feed/Feed.vue.js";
|
|
8
|
+
import _sfc_main$1 from "../../../icons/navigation/IconPlay.vue.js";
|
|
9
|
+
import _sfc_main$3 from "../../../icons/navigation/IconLike.vue.js";
|
|
10
|
+
import _sfc_main$2 from "../../../icons/navigation/IconShuffle.vue.js";
|
|
11
|
+
import _sfc_main$5 from "../../../icons/entities/IconCalendar.vue.js";
|
|
13
12
|
import IconTime from "../../../icons/entities/IconTime.vue.js";
|
|
14
|
-
import _sfc_main$
|
|
15
|
-
import _sfc_main$
|
|
16
|
-
import _sfc_main$
|
|
13
|
+
import _sfc_main$7 from "../../../icons/actions/IconShow.vue.js";
|
|
14
|
+
import _sfc_main$6 from "../../../icons/entities/IconMusic.vue.js";
|
|
15
|
+
import _sfc_main$4 from "../blocks/ActionButtons.vue.js";
|
|
16
|
+
import _sfc_main$9 from "../cards/TrackListCard.vue.js";
|
|
17
17
|
import AlbumCard from "../cards/AlbumCard.vue.js";
|
|
18
18
|
import ArtistCardSmall from "../cards/ArtistCardSmall.vue.js";
|
|
19
|
-
import { state, actions } from "../../store/albums.js";
|
|
20
|
-
import { actions
|
|
19
|
+
import { state, actions as actions$1 } from "../../store/albums.js";
|
|
20
|
+
import { actions } from "../../store/player.js";
|
|
21
21
|
import { state as state$1 } from "../../../auth/views/store/auth.js";
|
|
22
22
|
const _hoisted_1 = { class: "album-page pd-small" };
|
|
23
23
|
const _hoisted_2 = {
|
|
@@ -30,7 +30,7 @@ const _hoisted_3 = {
|
|
|
30
30
|
};
|
|
31
31
|
const _hoisted_4 = {
|
|
32
32
|
key: 2,
|
|
33
|
-
class: "album-content cols-2
|
|
33
|
+
class: "album-content cols-2 mobile:cols-1 gap-big"
|
|
34
34
|
};
|
|
35
35
|
const _hoisted_5 = { class: "pos-sticky pos-t-0 mobile:pos-relative album-cover-section" };
|
|
36
36
|
const _hoisted_6 = { class: "stats-grid grid cols-2 gap-small" };
|
|
@@ -46,42 +46,40 @@ const _hoisted_14 = {
|
|
|
46
46
|
class: "bg-light t-medium pd-thin radius-thin uppercase t-small t-uppercase"
|
|
47
47
|
};
|
|
48
48
|
const _hoisted_15 = { class: "h1 mn-b-medium" };
|
|
49
|
-
const _hoisted_16 = { class: "
|
|
50
|
-
const _hoisted_17 = {
|
|
51
|
-
const _hoisted_18 = { class: "artists-section mn-b-medium" };
|
|
52
|
-
const _hoisted_19 = {
|
|
49
|
+
const _hoisted_16 = { class: "artists-section mn-b-medium" };
|
|
50
|
+
const _hoisted_17 = {
|
|
53
51
|
key: 0,
|
|
54
52
|
class: "t-medium mn-b-small"
|
|
55
53
|
};
|
|
56
|
-
const
|
|
57
|
-
const
|
|
54
|
+
const _hoisted_18 = { class: "flex flex-column gap-small" };
|
|
55
|
+
const _hoisted_19 = { class: "metadata-grid grid cols-2 gap-small mn-b-medium" };
|
|
56
|
+
const _hoisted_20 = { class: "metadata-card bg-light pd-medium radius-medium flex items-center gap-regular" };
|
|
57
|
+
const _hoisted_21 = { class: "t-medium" };
|
|
58
58
|
const _hoisted_22 = { class: "metadata-card bg-light pd-medium radius-medium flex items-center gap-regular" };
|
|
59
59
|
const _hoisted_23 = { class: "t-medium" };
|
|
60
|
-
const _hoisted_24 = {
|
|
61
|
-
const _hoisted_25 = { class: "t-medium" };
|
|
62
|
-
const _hoisted_26 = {
|
|
60
|
+
const _hoisted_24 = {
|
|
63
61
|
key: 0,
|
|
64
62
|
class: "metadata-card bg-light pd-medium radius-medium flex items-center gap-regular"
|
|
65
63
|
};
|
|
64
|
+
const _hoisted_25 = { class: "t-medium" };
|
|
65
|
+
const _hoisted_26 = { class: "metadata-card bg-light pd-medium radius-medium flex items-center gap-regular" };
|
|
66
66
|
const _hoisted_27 = { class: "t-medium" };
|
|
67
|
-
const _hoisted_28 = {
|
|
68
|
-
const _hoisted_29 = { class: "t-medium" };
|
|
69
|
-
const _hoisted_30 = {
|
|
67
|
+
const _hoisted_28 = {
|
|
70
68
|
key: 0,
|
|
71
69
|
class: "tags-section mn-b-medium"
|
|
72
70
|
};
|
|
73
|
-
const
|
|
74
|
-
const
|
|
71
|
+
const _hoisted_29 = { class: "flex gap-thin flex-wrap" };
|
|
72
|
+
const _hoisted_30 = {
|
|
75
73
|
key: 3,
|
|
76
74
|
class: "tracks-section mn-t-big"
|
|
77
75
|
};
|
|
78
|
-
const
|
|
79
|
-
const
|
|
76
|
+
const _hoisted_31 = { class: "bg-light radius-medium o-hidden" };
|
|
77
|
+
const _hoisted_32 = {
|
|
80
78
|
key: 4,
|
|
81
79
|
class: "more-albums-section mn-t-big"
|
|
82
80
|
};
|
|
83
|
-
const
|
|
84
|
-
const
|
|
81
|
+
const _hoisted_33 = { class: "flex justify-between items-center mn-b-medium" };
|
|
82
|
+
const _hoisted_34 = { class: "flex flex-nowrap gap-small o-x-scroll overscroll-behavior-x-contain scroll-behavior-smooth scroll-snap-type-x-mandatory scroll-hide" };
|
|
85
83
|
const _sfc_main = {
|
|
86
84
|
__name: "Album",
|
|
87
85
|
emits: ["page-loading", "page-loaded"],
|
|
@@ -91,7 +89,6 @@ const _sfc_main = {
|
|
|
91
89
|
const emits = __emit;
|
|
92
90
|
const hasLoaded = ref(false);
|
|
93
91
|
const isFavorite = ref(false);
|
|
94
|
-
const showDropdown = ref(false);
|
|
95
92
|
const followedArtists = ref([]);
|
|
96
93
|
const moreAlbums = ref([]);
|
|
97
94
|
state.currentAlbum = null;
|
|
@@ -106,6 +103,48 @@ const _sfc_main = {
|
|
|
106
103
|
const totalSeconds = albumTracks.value.reduce((sum, track) => sum + (track.duration || 0), 0);
|
|
107
104
|
return formatDuration(totalSeconds);
|
|
108
105
|
});
|
|
106
|
+
const actionButtons = computed(() => {
|
|
107
|
+
const buttons = [
|
|
108
|
+
{
|
|
109
|
+
type: "button",
|
|
110
|
+
class: "t-white bg-black",
|
|
111
|
+
icon: _sfc_main$1,
|
|
112
|
+
iconFill: "rgb(var(--white))",
|
|
113
|
+
text: "Play All",
|
|
114
|
+
action: playAlbum
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: "button",
|
|
118
|
+
class: "bg-light",
|
|
119
|
+
icon: _sfc_main$2,
|
|
120
|
+
text: "Shuffle",
|
|
121
|
+
action: shufflePlay
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: "button",
|
|
125
|
+
class: "bg-light",
|
|
126
|
+
icon: _sfc_main$3,
|
|
127
|
+
iconFill: isFavorite.value ? "rgb(var(--main))" : "rgb(var(--black))",
|
|
128
|
+
text: isFavorite.value ? "Liked" : "Like",
|
|
129
|
+
action: toggleFavorite
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: "dropdown",
|
|
133
|
+
items: [
|
|
134
|
+
{ text: "Add to Queue", action: addToQueue },
|
|
135
|
+
{ text: "Copy Link", action: copyLink },
|
|
136
|
+
{ text: "Add to Playlist", action: addToPlaylist }
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
];
|
|
140
|
+
if (isOwner.value) {
|
|
141
|
+
const items = buttons[3].items;
|
|
142
|
+
items.push({ separator: true });
|
|
143
|
+
items.push({ text: "Edit Album", action: editAlbum });
|
|
144
|
+
items.push({ text: "Delete Album", action: deleteAlbum, color: "danger" });
|
|
145
|
+
}
|
|
146
|
+
return buttons;
|
|
147
|
+
});
|
|
109
148
|
const formatDate = (dateString) => {
|
|
110
149
|
if (!dateString) return "Unknown";
|
|
111
150
|
return new Date(dateString).toLocaleDateString("en-US", {
|
|
@@ -135,13 +174,13 @@ const _sfc_main = {
|
|
|
135
174
|
};
|
|
136
175
|
const playAlbum = () => {
|
|
137
176
|
if (albumTracks.value && albumTracks.value.length > 0) {
|
|
138
|
-
actions
|
|
177
|
+
actions.setQueue(albumTracks.value);
|
|
139
178
|
}
|
|
140
179
|
};
|
|
141
180
|
const shufflePlay = () => {
|
|
142
181
|
if (albumTracks.value && albumTracks.value.length > 0) {
|
|
143
182
|
const shuffled = [...albumTracks.value].sort(() => Math.random() - 0.5);
|
|
144
|
-
actions
|
|
183
|
+
actions.setQueue(shuffled);
|
|
145
184
|
}
|
|
146
185
|
};
|
|
147
186
|
const toggleFavorite = () => {
|
|
@@ -158,18 +197,20 @@ const _sfc_main = {
|
|
|
158
197
|
const addToQueue = () => {
|
|
159
198
|
if (albumTracks.value.length > 0) {
|
|
160
199
|
albumTracks.value.forEach((track) => {
|
|
161
|
-
actions
|
|
200
|
+
actions.addToQueue(track);
|
|
162
201
|
});
|
|
163
|
-
showDropdown.value = false;
|
|
164
202
|
}
|
|
165
203
|
};
|
|
204
|
+
const addToPlaylist = () => {
|
|
205
|
+
console.log("Add album to playlist");
|
|
206
|
+
};
|
|
166
207
|
const editAlbum = () => {
|
|
167
208
|
router.push({ name: "album-edit", params: { url: album.value.url } });
|
|
168
209
|
};
|
|
169
210
|
const deleteAlbum = async () => {
|
|
170
211
|
if (confirm("Are you sure you want to delete this album?")) {
|
|
171
212
|
try {
|
|
172
|
-
await actions.deleteAlbum(album.value._id);
|
|
213
|
+
await actions$1.deleteAlbum(album.value._id);
|
|
173
214
|
router.push({ name: "music-library" });
|
|
174
215
|
} catch (error) {
|
|
175
216
|
console.error("Failed to delete album:", error);
|
|
@@ -178,14 +219,13 @@ const _sfc_main = {
|
|
|
178
219
|
};
|
|
179
220
|
const copyLink = () => {
|
|
180
221
|
navigator.clipboard.writeText(window.location.href);
|
|
181
|
-
showDropdown.value = false;
|
|
182
222
|
};
|
|
183
223
|
const fetchAlbumData = async () => {
|
|
184
224
|
try {
|
|
185
|
-
await actions.fetchAlbumByUrl(route.params.url);
|
|
225
|
+
await actions$1.fetchAlbumByUrl(route.params.url);
|
|
186
226
|
if (album.value?.artists?.length) {
|
|
187
227
|
const artistIds = album.value.artists.map((a) => a._id);
|
|
188
|
-
const albums = await actions.fetchAlbums({
|
|
228
|
+
const albums = await actions$1.fetchAlbums({
|
|
189
229
|
artist: { $in: artistIds },
|
|
190
230
|
status: "published",
|
|
191
231
|
isPublic: true,
|
|
@@ -209,7 +249,7 @@ const _sfc_main = {
|
|
|
209
249
|
_ctx.isLoading ? (openBlock(), createElementBlock("div", _hoisted_2, [
|
|
210
250
|
createVNode(Loader)
|
|
211
251
|
])) : createCommentVNode("", true),
|
|
212
|
-
hasLoaded.value && !album.value ? (openBlock(), createElementBlock("div", _hoisted_3, _cache[
|
|
252
|
+
hasLoaded.value && !album.value ? (openBlock(), createElementBlock("div", _hoisted_3, _cache[0] || (_cache[0] = [
|
|
213
253
|
createElementVNode("h2", { class: "" }, "Album not found", -1),
|
|
214
254
|
createElementVNode("p", { class: "t-transp t-medium" }, "The album you're looking for doesn't exist or has been removed.", -1)
|
|
215
255
|
]))) : createCommentVNode("", true),
|
|
@@ -218,16 +258,16 @@ const _sfc_main = {
|
|
|
218
258
|
createVNode(Media, {
|
|
219
259
|
url: album.value.coverArt || "/logo/logo-placeholder.jpg",
|
|
220
260
|
alt: album.value.title,
|
|
221
|
-
class: "aspect-1x1 w-100
|
|
261
|
+
class: "aspect-1x1 w-100 mn-b-small radius-medium o-hidden"
|
|
222
262
|
}, null, 8, ["url", "alt"]),
|
|
223
263
|
createElementVNode("div", _hoisted_6, [
|
|
224
264
|
createElementVNode("div", _hoisted_7, [
|
|
225
265
|
createElementVNode("div", _hoisted_8, toDisplayString(album.value.totalTracks || 0), 1),
|
|
226
|
-
_cache[
|
|
266
|
+
_cache[1] || (_cache[1] = createElementVNode("div", { class: "t-small t-transp t-uppercase" }, "Tracks", -1))
|
|
227
267
|
]),
|
|
228
268
|
createElementVNode("div", _hoisted_9, [
|
|
229
269
|
createElementVNode("div", _hoisted_10, toDisplayString(formatNumber(album.value.views)), 1),
|
|
230
|
-
_cache[
|
|
270
|
+
_cache[2] || (_cache[2] = createElementVNode("div", { class: "t-small t-transp t-uppercase" }, "Views", -1))
|
|
231
271
|
])
|
|
232
272
|
])
|
|
233
273
|
]),
|
|
@@ -237,135 +277,10 @@ const _sfc_main = {
|
|
|
237
277
|
album.value.status === "published" ? (openBlock(), createElementBlock("span", _hoisted_14, " Published ")) : createCommentVNode("", true)
|
|
238
278
|
]),
|
|
239
279
|
createElementVNode("h1", _hoisted_15, toDisplayString(album.value.title), 1),
|
|
280
|
+
createVNode(_sfc_main$4, { buttons: actionButtons.value }, null, 8, ["buttons"]),
|
|
240
281
|
createElementVNode("div", _hoisted_16, [
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
color: "primary",
|
|
244
|
-
size: "medium",
|
|
245
|
-
class: "flex-1 t-white bg-black radius-thin flex-center gap-thin"
|
|
246
|
-
}, {
|
|
247
|
-
default: withCtx(() => [
|
|
248
|
-
createVNode(_sfc_main$2, {
|
|
249
|
-
fill: "rgb(var(--white))",
|
|
250
|
-
class: "i-medium"
|
|
251
|
-
}),
|
|
252
|
-
_cache[4] || (_cache[4] = createTextVNode(" Play All "))
|
|
253
|
-
]),
|
|
254
|
-
_: 1
|
|
255
|
-
}),
|
|
256
|
-
createVNode(_sfc_main$1, {
|
|
257
|
-
onClick: shufflePlay,
|
|
258
|
-
color: "primary",
|
|
259
|
-
size: "medium",
|
|
260
|
-
class: "flex-1 bg-light radius-thin flex-center gap-thin"
|
|
261
|
-
}, {
|
|
262
|
-
default: withCtx(() => [
|
|
263
|
-
createVNode(_sfc_main$3, { class: "i-medium" }),
|
|
264
|
-
_cache[5] || (_cache[5] = createTextVNode(" Shuffle "))
|
|
265
|
-
]),
|
|
266
|
-
_: 1
|
|
267
|
-
}),
|
|
268
|
-
createVNode(_sfc_main$1, {
|
|
269
|
-
onClick: toggleFavorite,
|
|
270
|
-
color: "primary",
|
|
271
|
-
size: "medium",
|
|
272
|
-
class: "flex-1 bg-light radius-thin flex-center gap-thin"
|
|
273
|
-
}, {
|
|
274
|
-
default: withCtx(() => [
|
|
275
|
-
createVNode(_sfc_main$4, {
|
|
276
|
-
class: "i-medium",
|
|
277
|
-
fill: isFavorite.value ? "rgb(var(--main)" : "rgb(var(--black)"
|
|
278
|
-
}, null, 8, ["fill"]),
|
|
279
|
-
createTextVNode(" " + toDisplayString(isFavorite.value ? "Liked" : "Like"), 1)
|
|
280
|
-
]),
|
|
281
|
-
_: 1
|
|
282
|
-
}),
|
|
283
|
-
createVNode(_sfc_main$5, {
|
|
284
|
-
label: { component: _sfc_main$6, class: "bg-light radius-thin pd-thin i-big" },
|
|
285
|
-
modelValue: showDropdown.value,
|
|
286
|
-
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => showDropdown.value = $event),
|
|
287
|
-
class: "relative"
|
|
288
|
-
}, {
|
|
289
|
-
trigger: withCtx(() => [
|
|
290
|
-
createVNode(_sfc_main$1, {
|
|
291
|
-
color: "transp",
|
|
292
|
-
size: "medium",
|
|
293
|
-
class: "w-3r h-3r radius-full"
|
|
294
|
-
}, {
|
|
295
|
-
default: withCtx(() => [
|
|
296
|
-
createVNode(_sfc_main$6, { class: "w-1-25r h-1-25r" })
|
|
297
|
-
]),
|
|
298
|
-
_: 1
|
|
299
|
-
})
|
|
300
|
-
]),
|
|
301
|
-
default: withCtx(() => [
|
|
302
|
-
createElementVNode("div", _hoisted_17, [
|
|
303
|
-
createVNode(_sfc_main$1, {
|
|
304
|
-
onClick: addToQueue,
|
|
305
|
-
color: "transp",
|
|
306
|
-
size: "small",
|
|
307
|
-
class: "w-100 justify-start"
|
|
308
|
-
}, {
|
|
309
|
-
default: withCtx(() => _cache[6] || (_cache[6] = [
|
|
310
|
-
createTextVNode(" Add to Queue ")
|
|
311
|
-
])),
|
|
312
|
-
_: 1
|
|
313
|
-
}),
|
|
314
|
-
createVNode(_sfc_main$1, {
|
|
315
|
-
onClick: copyLink,
|
|
316
|
-
color: "transp",
|
|
317
|
-
size: "small",
|
|
318
|
-
class: "w-100 justify-start"
|
|
319
|
-
}, {
|
|
320
|
-
default: withCtx(() => _cache[7] || (_cache[7] = [
|
|
321
|
-
createTextVNode(" Copy Link ")
|
|
322
|
-
])),
|
|
323
|
-
_: 1
|
|
324
|
-
}),
|
|
325
|
-
createVNode(_sfc_main$1, {
|
|
326
|
-
onClick: _ctx.addToPlaylist,
|
|
327
|
-
color: "transp",
|
|
328
|
-
size: "small",
|
|
329
|
-
class: "w-100 justify-start"
|
|
330
|
-
}, {
|
|
331
|
-
default: withCtx(() => _cache[8] || (_cache[8] = [
|
|
332
|
-
createTextVNode(" Add to Playlist ")
|
|
333
|
-
])),
|
|
334
|
-
_: 1
|
|
335
|
-
}, 8, ["onClick"]),
|
|
336
|
-
isOwner.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
|
|
337
|
-
_cache[11] || (_cache[11] = createElementVNode("hr", { class: "mn-v-thin border-dark-transp-10" }, null, -1)),
|
|
338
|
-
createVNode(_sfc_main$1, {
|
|
339
|
-
onClick: editAlbum,
|
|
340
|
-
color: "transp",
|
|
341
|
-
size: "small",
|
|
342
|
-
class: "w-100 justify-start"
|
|
343
|
-
}, {
|
|
344
|
-
default: withCtx(() => _cache[9] || (_cache[9] = [
|
|
345
|
-
createTextVNode(" Edit Album ")
|
|
346
|
-
])),
|
|
347
|
-
_: 1
|
|
348
|
-
}),
|
|
349
|
-
createVNode(_sfc_main$1, {
|
|
350
|
-
onClick: deleteAlbum,
|
|
351
|
-
color: "danger",
|
|
352
|
-
size: "small",
|
|
353
|
-
class: "w-100 justify-start"
|
|
354
|
-
}, {
|
|
355
|
-
default: withCtx(() => _cache[10] || (_cache[10] = [
|
|
356
|
-
createTextVNode(" Delete Album ")
|
|
357
|
-
])),
|
|
358
|
-
_: 1
|
|
359
|
-
})
|
|
360
|
-
], 64)) : createCommentVNode("", true)
|
|
361
|
-
])
|
|
362
|
-
]),
|
|
363
|
-
_: 1
|
|
364
|
-
}, 8, ["label", "modelValue"])
|
|
365
|
-
]),
|
|
366
|
-
createElementVNode("div", _hoisted_18, [
|
|
367
|
-
album.value.artists ? (openBlock(), createElementBlock("h3", _hoisted_19, "Artists")) : createCommentVNode("", true),
|
|
368
|
-
createElementVNode("div", _hoisted_20, [
|
|
282
|
+
album.value.artists ? (openBlock(), createElementBlock("h3", _hoisted_17, "Artists")) : createCommentVNode("", true),
|
|
283
|
+
createElementVNode("div", _hoisted_18, [
|
|
369
284
|
(openBlock(true), createElementBlock(Fragment, null, renderList(album.value.artists, (artist) => {
|
|
370
285
|
return openBlock(), createBlock(ArtistCardSmall, {
|
|
371
286
|
key: artist._id,
|
|
@@ -377,40 +292,40 @@ const _sfc_main = {
|
|
|
377
292
|
}), 128))
|
|
378
293
|
])
|
|
379
294
|
]),
|
|
380
|
-
_cache[
|
|
381
|
-
createElementVNode("div",
|
|
382
|
-
createElementVNode("div",
|
|
383
|
-
createVNode(_sfc_main$
|
|
295
|
+
_cache[8] || (_cache[8] = createElementVNode("h3", { class: "t-medium mn-b-small" }, "Metadata", -1)),
|
|
296
|
+
createElementVNode("div", _hoisted_19, [
|
|
297
|
+
createElementVNode("div", _hoisted_20, [
|
|
298
|
+
createVNode(_sfc_main$5, { class: "i-medium t-primary" }),
|
|
384
299
|
createElementVNode("div", null, [
|
|
385
|
-
_cache[
|
|
386
|
-
createElementVNode("div",
|
|
300
|
+
_cache[3] || (_cache[3] = createElementVNode("div", { class: "t-small t-transp t-uppercase" }, "Released", -1)),
|
|
301
|
+
createElementVNode("div", _hoisted_21, toDisplayString(formatDate(album.value.releaseDate)), 1)
|
|
387
302
|
])
|
|
388
303
|
]),
|
|
389
|
-
createElementVNode("div",
|
|
304
|
+
createElementVNode("div", _hoisted_22, [
|
|
390
305
|
createVNode(IconTime, { class: "i-medium t-primary" }),
|
|
391
306
|
createElementVNode("div", null, [
|
|
392
|
-
_cache[
|
|
393
|
-
createElementVNode("div",
|
|
307
|
+
_cache[4] || (_cache[4] = createElementVNode("div", { class: "t-small t-transp t-uppercase" }, "Duration", -1)),
|
|
308
|
+
createElementVNode("div", _hoisted_23, toDisplayString(totalDuration.value), 1)
|
|
394
309
|
])
|
|
395
310
|
]),
|
|
396
|
-
album.value.label ? (openBlock(), createElementBlock("div",
|
|
397
|
-
createVNode(_sfc_main$
|
|
311
|
+
album.value.label ? (openBlock(), createElementBlock("div", _hoisted_24, [
|
|
312
|
+
createVNode(_sfc_main$6, { class: "i-medium t-primary" }),
|
|
398
313
|
createElementVNode("div", null, [
|
|
399
|
-
_cache[
|
|
400
|
-
createElementVNode("div",
|
|
314
|
+
_cache[5] || (_cache[5] = createElementVNode("div", { class: "t-small t-transp t-uppercase" }, "Label", -1)),
|
|
315
|
+
createElementVNode("div", _hoisted_25, toDisplayString(album.value.label), 1)
|
|
401
316
|
])
|
|
402
317
|
])) : createCommentVNode("", true),
|
|
403
|
-
createElementVNode("div",
|
|
404
|
-
createVNode(_sfc_main$
|
|
318
|
+
createElementVNode("div", _hoisted_26, [
|
|
319
|
+
createVNode(_sfc_main$7, { class: "i-medium t-primary" }),
|
|
405
320
|
createElementVNode("div", null, [
|
|
406
|
-
_cache[
|
|
407
|
-
createElementVNode("div",
|
|
321
|
+
_cache[6] || (_cache[6] = createElementVNode("div", { class: "t-small t-transp t-uppercase" }, "Visibility", -1)),
|
|
322
|
+
createElementVNode("div", _hoisted_27, toDisplayString(album.value.isPublic ? "Public" : "Private"), 1)
|
|
408
323
|
])
|
|
409
324
|
])
|
|
410
325
|
]),
|
|
411
|
-
album.value.genres && album.value.genres.length || album.value.tags && album.value.tags.length ? (openBlock(), createElementBlock("div",
|
|
412
|
-
_cache[
|
|
413
|
-
createElementVNode("div",
|
|
326
|
+
album.value.genres && album.value.genres.length || album.value.tags && album.value.tags.length ? (openBlock(), createElementBlock("div", _hoisted_28, [
|
|
327
|
+
_cache[7] || (_cache[7] = createElementVNode("h3", { class: "t-medium mn-b-small" }, "Genres & Tags", -1)),
|
|
328
|
+
createElementVNode("div", _hoisted_29, [
|
|
414
329
|
(openBlock(true), createElementBlock(Fragment, null, renderList(album.value.genres, (genre) => {
|
|
415
330
|
return openBlock(), createElementBlock("span", {
|
|
416
331
|
key: genre,
|
|
@@ -427,9 +342,9 @@ const _sfc_main = {
|
|
|
427
342
|
])) : createCommentVNode("", true)
|
|
428
343
|
])
|
|
429
344
|
])) : createCommentVNode("", true),
|
|
430
|
-
!_ctx.isLoading && album.value && albumTracks.value.length ? (openBlock(), createElementBlock("section",
|
|
431
|
-
_cache[
|
|
432
|
-
createVNode(_sfc_main$
|
|
345
|
+
!_ctx.isLoading && album.value && albumTracks.value.length ? (openBlock(), createElementBlock("section", _hoisted_30, [
|
|
346
|
+
_cache[9] || (_cache[9] = createElementVNode("h2", { class: "h2 mn-b-medium" }, "Tracklist", -1)),
|
|
347
|
+
createVNode(_sfc_main$8, {
|
|
433
348
|
store: {
|
|
434
349
|
read: () => _ctx.Promise.resolve(albumTracks.value),
|
|
435
350
|
state: { isLoading: false }
|
|
@@ -445,9 +360,9 @@ const _sfc_main = {
|
|
|
445
360
|
}
|
|
446
361
|
}, {
|
|
447
362
|
default: withCtx(({ items }) => [
|
|
448
|
-
createElementVNode("div",
|
|
363
|
+
createElementVNode("div", _hoisted_31, [
|
|
449
364
|
(openBlock(true), createElementBlock(Fragment, null, renderList(items, (track, index) => {
|
|
450
|
-
return openBlock(), createBlock(_sfc_main$
|
|
365
|
+
return openBlock(), createBlock(_sfc_main$9, {
|
|
451
366
|
key: track._id,
|
|
452
367
|
track,
|
|
453
368
|
index: index + 1,
|
|
@@ -460,21 +375,21 @@ const _sfc_main = {
|
|
|
460
375
|
_: 1
|
|
461
376
|
}, 8, ["store", "items"])
|
|
462
377
|
])) : createCommentVNode("", true),
|
|
463
|
-
!_ctx.isLoading && album.value && moreAlbums.value.length ? (openBlock(), createElementBlock("section",
|
|
464
|
-
createElementVNode("div",
|
|
465
|
-
_cache[
|
|
378
|
+
!_ctx.isLoading && album.value && moreAlbums.value.length ? (openBlock(), createElementBlock("section", _hoisted_32, [
|
|
379
|
+
createElementVNode("div", _hoisted_33, [
|
|
380
|
+
_cache[11] || (_cache[11] = createElementVNode("h2", { class: "h2" }, "More Albums", -1)),
|
|
466
381
|
album.value.artists && album.value.artists[0] ? (openBlock(), createBlock(_component_router_link, {
|
|
467
382
|
key: 0,
|
|
468
383
|
to: { name: "artist", params: { url: album.value.artists[0].url } },
|
|
469
384
|
class: "t-primary hover-opacity"
|
|
470
385
|
}, {
|
|
471
|
-
default: withCtx(() => _cache[
|
|
386
|
+
default: withCtx(() => _cache[10] || (_cache[10] = [
|
|
472
387
|
createTextVNode(" See all ")
|
|
473
388
|
])),
|
|
474
389
|
_: 1
|
|
475
390
|
}, 8, ["to"])) : createCommentVNode("", true)
|
|
476
391
|
]),
|
|
477
|
-
createElementVNode("div",
|
|
392
|
+
createElementVNode("div", _hoisted_34, [
|
|
478
393
|
(openBlock(true), createElementBlock(Fragment, null, renderList(moreAlbums.value, (relatedAlbum) => {
|
|
479
394
|
return openBlock(), createElementBlock("li", {
|
|
480
395
|
key: album.value._id,
|
|
@@ -482,7 +397,7 @@ const _sfc_main = {
|
|
|
482
397
|
}, [
|
|
483
398
|
createVNode(AlbumCard, {
|
|
484
399
|
album: relatedAlbum,
|
|
485
|
-
class: "w-
|
|
400
|
+
class: "w-15r transition-cubic-in-out"
|
|
486
401
|
}, null, 8, ["album"])
|
|
487
402
|
]);
|
|
488
403
|
}), 128))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Album.vue.js","sources":["../../../../../../../src/modules/music/components/pages/Album.vue"],"sourcesContent":["<!-- components/pages/Album.vue -->\n<template>\n <div class=\"album-page pd-small\">\n <!-- Loading -->\n <div v-if=\"isLoading\" class=\"w-100 h-25r flex-center flex\">\n <Loader />\n </div>\n \n <!-- Not Found -->\n <div v-if=\"hasLoaded && !album\" class=\"t-center pd-big\">\n <h2 class=\"\">Album not found</h2>\n <p class=\"t-transp t-medium\">The album you're looking for doesn't exist or has been removed.</p>\n </div>\n \n <!-- Album Content -->\n <div v-if=\"album\" class=\"album-content cols-2-fit-content mobile:cols-1 gap-big\">\n <!-- Left Column - Cover & Stats -->\n <div class=\"pos-sticky pos-t-0 mobile:pos-relative album-cover-section\">\n <!-- Cover -->\n <Media \n :url=\"album.coverArt || '/logo/logo-placeholder.jpg'\"\n :alt=\"album.title\"\n class=\"aspect-1x1 w-100 w-max-30r mn-b-small radius-medium o-hidden\"\n />\n <!-- Quick Stats -->\n <div class=\"stats-grid grid cols-2 gap-small\">\n <div class=\"stat-card bg-light pd-medium radius-medium t-center\">\n <div class=\" mn-b-thin\">{{ album.totalTracks || 0 }}</div>\n <div class=\"t-small t-transp t-uppercase\">Tracks</div>\n </div>\n <div class=\"stat-card bg-light pd-medium radius-medium t-center\">\n <div class=\" mn-b-thin\">{{ formatNumber(album.views) }}</div>\n <div class=\"t-small t-transp t-uppercase\">Views</div>\n </div>\n </div>\n </div>\n\n <!-- Right Column - Album Details -->\n <div class=\"album-details-section\">\n <!-- Album Type Badge -->\n <div class=\"flex items-center gap-small mn-b-small\">\n <span class=\"bg-light t-medium pd-thin radius-thin uppercase t-small t-uppercase\">\n {{ album.type }}\n </span>\n <span v-if=\"album.status === 'published'\" class=\"bg-light t-medium pd-thin radius-thin uppercase t-small t-uppercase\">\n Published\n </span>\n </div>\n\n <!-- Album Title -->\n <h1 class=\"h1 mn-b-medium\">{{ album.title }}</h1>\n\n <!-- Action Buttons -->\n <div class=\"flex gap-small mn-b-medium\">\n <Button\n @click=\"playAlbum\"\n color=\"primary\"\n size=\"medium\"\n class=\"flex-1 t-white bg-black radius-thin flex-center gap-thin\"\n >\n <IconPlay fill=\"rgb(var(--white))\" class=\"i-medium\" />\n Play All\n </Button>\n\n <Button\n @click=\"shufflePlay\"\n color=\"primary\"\n size=\"medium\"\n class=\"flex-1 bg-light radius-thin flex-center gap-thin\"\n >\n <IconShuffle class=\"i-medium\" />\n Shuffle\n </Button>\n\n <Button\n @click=\"toggleFavorite\"\n color=\"primary\"\n size=\"medium\"\n class=\"flex-1 bg-light radius-thin flex-center gap-thin\"\n >\n <IconLike class=\"i-medium\" :fill=\"isFavorite ? 'rgb(var(--main)':'rgb(var(--black)'\" />\n {{isFavorite ? 'Liked' : 'Like'}}\n </Button>\n\n <Dropdown :label=\"{component: IconEllipsis, class: 'bg-light radius-thin pd-thin i-big' }\" v-model=\"showDropdown\" class=\"relative\">\n <template #trigger>\n <Button color=\"transp\" size=\"medium\" class=\"w-3r h-3r radius-full\">\n <IconEllipsis class=\"w-1-25r h-1-25r\" />\n </Button>\n </template>\n <template #default>\n <div class=\"dropdown-menu bg-white pd-small radius-medium shadow-big mn-t-thin\">\n <Button @click=\"addToQueue\" color=\"transp\" size=\"small\" class=\"w-100 justify-start\">\n Add to Queue\n </Button>\n <Button @click=\"copyLink\" color=\"transp\" size=\"small\" class=\"w-100 justify-start\">\n Copy Link\n </Button>\n <Button @click=\"addToPlaylist\" color=\"transp\" size=\"small\" class=\"w-100 justify-start\">\n Add to Playlist\n </Button>\n <template v-if=\"isOwner\">\n <hr class=\"mn-v-thin border-dark-transp-10\" />\n <Button @click=\"editAlbum\" color=\"transp\" size=\"small\" class=\"w-100 justify-start\">\n Edit Album\n </Button>\n <Button @click=\"deleteAlbum\" color=\"danger\" size=\"small\" class=\"w-100 justify-start\">\n Delete Album\n </Button>\n </template>\n </div>\n </template>\n </Dropdown>\n </div>\n\n <!-- Artists Cards -->\n <div class=\"artists-section mn-b-medium\">\n <h3 class=\"t-medium mn-b-small\" v-if=\"album.artists\">Artists</h3>\n <div class=\"flex flex-column gap-small\">\n <ArtistCardSmall \n v-for=\"artist in album.artists\" \n :key=\"artist._id\"\n :artist=\"artist\"\n :is-following=\"followedArtists.includes(artist._id)\"\n :show-follow-button=\"!isOwner\"\n @toggle-follow=\"toggleFollowArtist\"\n />\n </div>\n </div>\n\n <!-- Metadata Cards -->\n <h3 class=\"t-medium mn-b-small\">Metadata</h3>\n <div class=\"metadata-grid grid cols-2 gap-small mn-b-medium\">\n <!-- Release Date -->\n <div class=\"metadata-card bg-light pd-medium radius-medium flex items-center gap-regular\">\n <IconCalendar class=\"i-medium t-primary\" />\n <div>\n <div class=\"t-small t-transp t-uppercase\">Released</div>\n <div class=\"t-medium \">{{ formatDate(album.releaseDate) }}</div>\n </div>\n </div>\n\n <!-- Total Duration -->\n <div class=\"metadata-card bg-light pd-medium radius-medium flex items-center gap-regular\">\n <IconClock class=\"i-medium t-primary\" />\n <div>\n <div class=\"t-small t-transp t-uppercase\">Duration</div>\n <div class=\"t-medium \">{{ totalDuration }}</div>\n </div>\n </div>\n\n <!-- Label -->\n <div v-if=\"album.label\" class=\"metadata-card bg-light pd-medium radius-medium flex items-center gap-regular\">\n <IconDisc class=\"i-medium t-primary\" />\n <div>\n <div class=\"t-small t-transp t-uppercase\">Label</div>\n <div class=\"t-medium \">{{ album.label }}</div>\n </div>\n </div>\n\n <!-- Visibility -->\n <div class=\"metadata-card bg-light pd-medium radius-medium flex items-center gap-regular\">\n <IconEye class=\"i-medium t-primary\" />\n <div>\n <div class=\"t-small t-transp t-uppercase\">Visibility</div>\n <div class=\"t-medium \">{{ album.isPublic ? 'Public' : 'Private' }}</div>\n </div>\n </div>\n </div>\n\n <!-- Genres & Tags -->\n <div v-if=\"(album.genres && album.genres.length) || (album.tags && album.tags.length)\" class=\"tags-section mn-b-medium\">\n <h3 class=\"t-medium mn-b-small\">Genres & Tags</h3>\n <div class=\"flex gap-thin flex-wrap\">\n <span \n v-for=\"genre in album.genres\" \n :key=\"genre\"\n class=\"tag bg-main t-medium pd-thin radius-thin t-small cursor-pointer\"\n >\n {{ genre }}\n </span>\n <span \n v-for=\"tag in album.tags\" \n :key=\"tag\"\n class=\"tag bg-light t-transp pd-thin-big radius-small t-small hover-bg-light cursor-pointer\"\n >\n #{{ tag }}\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Album Tracks -->\n <section v-if=\"!isLoading && album && albumTracks.length\" class=\"tracks-section mn-t-big\">\n <h2 class=\"h2 mn-b-medium\">Tracklist</h2>\n <Feed\n :store=\"{\n read: () => Promise.resolve(albumTracks),\n state: { isLoading: false }\n }\"\n :external=\"true\"\n :items=\"albumTracks\"\n :states=\"{\n empty: {\n title: 'No tracks in album',\n description: 'This album appears to be empty',\n class: 'pd-medium t-center'\n }\n }\"\n >\n <template #default=\"{ items }\">\n <div class=\"bg-light radius-medium o-hidden\">\n <TrackListCard\n v-for=\"(track, index) in items\"\n :key=\"track._id\"\n :track=\"track\"\n :index=\"index + 1\"\n :showAlbum=\"false\"\n :showCover=\"false\"\n />\n </div>\n </template>\n </Feed>\n </section>\n\n <!-- More from Artists -->\n <section v-if=\"!isLoading && album && moreAlbums.length\" class=\"more-albums-section mn-t-big\">\n <div class=\"flex justify-between items-center mn-b-medium\">\n <h2 class=\"h2\">More Albums</h2>\n <router-link \n v-if=\"album.artists && album.artists[0]\"\n :to=\"{ name: 'artist', params: { url: album.artists[0].url } }\" \n class=\"t-primary hover-opacity\"\n >\n See all\n </router-link>\n </div>\n <div class=\"flex flex-nowrap gap-small o-x-scroll overscroll-behavior-x-contain scroll-behavior-smooth scroll-snap-type-x-mandatory scroll-hide\"\n >\n <li v-for=\"relatedAlbum in moreAlbums\" :key=\"album._id\" class=\"flex-none scroll-snap-align-start\">\n <AlbumCard :album=\"relatedAlbum\" class=\"w-min-15r transition-cubic-in-out\" />\n </li>\n </div>\n </section>\n </div>\n</template>\n\n<script setup>\nimport { ref, computed, onMounted, watch } from 'vue';\nimport { useRoute, useRouter } from 'vue-router';\nimport Button from '@martyrs/src/components/Button/Button.vue';\nimport Loader from '@martyrs/src/components/Loader/Loader.vue';\nimport Media from '@martyrs/src/components/Media/Media.vue';\nimport Dropdown from '@martyrs/src/components/Dropdown/Dropdown.vue';\nimport Feed from '@martyrs/src/components/Feed/Feed.vue';\n\n// Icons\nimport IconPlay from '@martyrs/src/modules/icons/navigation/IconPlay.vue';\nimport IconLike from '@martyrs/src/modules/icons/navigation/IconLike.vue';\nimport IconEllipsis from '@martyrs/src/modules/icons/navigation/IconEllipsis.vue';\nimport IconShuffle from '@martyrs/src/modules/icons/navigation/IconShuffle.vue';\nimport IconCalendar from '@martyrs/src/modules/icons/entities/IconCalendar.vue';\nimport IconClock from '@martyrs/src/modules/icons/entities/IconTime.vue';\nimport IconEye from '@martyrs/src/modules/icons/actions/IconShow.vue';\nimport IconDisc from '@martyrs/src/modules/icons/entities/IconMusic.vue';\nimport IconVerified from '@martyrs/src/modules/icons/navigation/IconCheckmark.vue';\n\n// Components\nimport TrackListCard from '../cards/TrackListCard.vue';\nimport AlbumCard from '../cards/AlbumCard.vue';\nimport ArtistCardSmall from '../cards/ArtistCardSmall.vue';\n\n// Store\nimport { state as albumsState, actions as albumsActions } from '../../store/albums.js';\nimport { actions as playerActions } from '../../store/player.js';\nimport { state as authState } from '@martyrs/src/modules/auth/views/store/auth.js';\n\nconst route = useRoute();\nconst router = useRouter();\n\n// Emits\nconst emits = defineEmits(['page-loading', 'page-loaded']);\n\n// State\nconst hasLoaded = ref(false);\nconst isFavorite = ref(false);\nconst showDropdown = ref(false);\nconst followedArtists = ref([]);\nconst moreAlbums = ref([]);\n\n// Clear state\nalbumsState.currentAlbum = null;\nalbumsState.currentAlbumTracks = [];\n\n// Computed\nconst album = computed(() => albumsState.currentAlbum);\nconst albumTracks = computed(() => albumsState.currentAlbumTracks || []);\n\nconst isOwner = computed(() => {\n return album.value?.owner?.target === authState.user?._id;\n});\n\nconst totalDuration = computed(() => {\n if (!albumTracks.value.length) return '0:00';\n const totalSeconds = albumTracks.value.reduce((sum, track) => sum + (track.duration || 0), 0);\n return formatDuration(totalSeconds);\n});\n\n// Format helpers\nconst formatDate = (dateString) => {\n if (!dateString) return 'Unknown';\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n};\n\nconst formatDuration = (seconds) => {\n if (!seconds) return '0:00';\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = Math.floor(seconds % 60);\n \n if (h > 0) {\n return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;\n }\n return `${m}:${s.toString().padStart(2, '0')}`;\n};\n\nconst formatNumber = (num) => {\n if (!num) return '0';\n if (num >= 1000000) {\n return (num / 1000000).toFixed(1) + 'M';\n } else if (num >= 1000) {\n return (num / 1000).toFixed(1) + 'K';\n }\n return num.toString();\n};\n\n// Actions\nconst playAlbum = () => {\n if (albumTracks.value && albumTracks.value.length > 0) {\n playerActions.setQueue(albumTracks.value);\n }\n};\n\nconst shufflePlay = () => {\n if (albumTracks.value && albumTracks.value.length > 0) {\n const shuffled = [...albumTracks.value].sort(() => Math.random() - 0.5);\n playerActions.setQueue(shuffled);\n }\n};\n\nconst toggleFavorite = () => {\n isFavorite.value = !isFavorite.value;\n // TODO: Implement actual saving\n};\n\nconst toggleFollowArtist = (artistId) => {\n const index = followedArtists.value.indexOf(artistId);\n if (index > -1) {\n followedArtists.value.splice(index, 1);\n } else {\n followedArtists.value.push(artistId);\n }\n // TODO: Implement actual following\n};\n\nconst addToQueue = () => {\n if (albumTracks.value.length > 0) {\n albumTracks.value.forEach(track => {\n playerActions.addToQueue(track);\n });\n showDropdown.value = false;\n }\n};\n\nconst editAlbum = () => {\n router.push({ name: 'album-edit', params: { url: album.value.url } });\n};\n\nconst deleteAlbum = async () => {\n if (confirm('Are you sure you want to delete this album?')) {\n try {\n await albumsActions.deleteAlbum(album.value._id);\n router.push({ name: 'music-library' });\n } catch (error) {\n console.error('Failed to delete album:', error);\n }\n }\n};\n\nconst copyLink = () => {\n navigator.clipboard.writeText(window.location.href);\n showDropdown.value = false;\n};\n\n// Data fetching\nconst fetchAlbumData = async () => {\n try {\n await albumsActions.fetchAlbumByUrl(route.params.url);\n \n // Fetch more albums from the same artists\n if (album.value?.artists?.length) {\n const artistIds = album.value.artists.map(a => a._id);\n const albums = await albumsActions.fetchAlbums({\n artist: { $in: artistIds },\n status: 'published',\n isPublic: true,\n limit: 6\n });\n \n // Filter out current album\n moreAlbums.value = albums.filter(a => a._id !== album.value._id).slice(0, 5);\n }\n } catch (error) {\n console.error('Error fetching album data:', error);\n }\n};\n\n// Lifecycle\nonMounted(async () => {\n emits('page-loading');\n \n await fetchAlbumData();\n \n hasLoaded.value = true;\n emits('page-loaded');\n});\n</script>\n\n<style scoped>\n</style>"],"names":["albumsState","authState","playerActions","albumsActions"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsRA,UAAM,QAAQ,SAAQ;AACtB,UAAM,SAAS,UAAS;AAGxB,UAAM,QAAQ;AAGd,UAAM,YAAY,IAAI,KAAK;AAC3B,UAAM,aAAa,IAAI,KAAK;AAC5B,UAAM,eAAe,IAAI,KAAK;AAC9B,UAAM,kBAAkB,IAAI,EAAE;AAC9B,UAAM,aAAa,IAAI,EAAE;AAGzBA,UAAY,eAAe;AAC3BA,UAAY,qBAAqB,CAAA;AAGjC,UAAM,QAAQ,SAAS,MAAMA,MAAY,YAAY;AACrD,UAAM,cAAc,SAAS,MAAMA,MAAY,sBAAsB,CAAA,CAAE;AAEvE,UAAM,UAAU,SAAS,MAAM;AAC7B,aAAO,MAAM,OAAO,OAAO,WAAWC,QAAU,MAAM;AAAA,IACxD,CAAC;AAED,UAAM,gBAAgB,SAAS,MAAM;AACnC,UAAI,CAAC,YAAY,MAAM,OAAQ,QAAO;AACtC,YAAM,eAAe,YAAY,MAAM,OAAO,CAAC,KAAK,UAAU,OAAO,MAAM,YAAY,IAAI,CAAC;AAC5F,aAAO,eAAe,YAAY;AAAA,IACpC,CAAC;AAGD,UAAM,aAAa,CAAC,eAAe;AACjC,UAAI,CAAC,WAAY,QAAO;AACxB,aAAO,IAAI,KAAK,UAAU,EAAE,mBAAmB,SAAS;AAAA,QACtD,MAAM;AAAA,QACN,OAAO;AAAA,QACP,KAAK;AAAA,MACT,CAAG;AAAA,IACH;AAEA,UAAM,iBAAiB,CAAC,YAAY;AAClC,UAAI,CAAC,QAAS,QAAO;AACrB,YAAM,IAAI,KAAK,MAAM,UAAU,IAAI;AACnC,YAAM,IAAI,KAAK,MAAO,UAAU,OAAQ,EAAE;AAC1C,YAAM,IAAI,KAAK,MAAM,UAAU,EAAE;AAEjC,UAAI,IAAI,GAAG;AACT,eAAO,GAAG,CAAC,IAAI,EAAE,SAAQ,EAAG,SAAS,GAAG,GAAG,CAAC,IAAI,EAAE,SAAQ,EAAG,SAAS,GAAG,GAAG,CAAC;AAAA,MAC/E;AACA,aAAO,GAAG,CAAC,IAAI,EAAE,SAAQ,EAAG,SAAS,GAAG,GAAG,CAAC;AAAA,IAC9C;AAEA,UAAM,eAAe,CAAC,QAAQ;AAC5B,UAAI,CAAC,IAAK,QAAO;AACjB,UAAI,OAAO,KAAS;AAClB,gBAAQ,MAAM,KAAS,QAAQ,CAAC,IAAI;AAAA,MACtC,WAAW,OAAO,KAAM;AACtB,gBAAQ,MAAM,KAAM,QAAQ,CAAC,IAAI;AAAA,MACnC;AACA,aAAO,IAAI,SAAQ;AAAA,IACrB;AAGA,UAAM,YAAY,MAAM;AACtB,UAAI,YAAY,SAAS,YAAY,MAAM,SAAS,GAAG;AACrDC,kBAAc,SAAS,YAAY,KAAK;AAAA,MAC1C;AAAA,IACF;AAEA,UAAM,cAAc,MAAM;AACxB,UAAI,YAAY,SAAS,YAAY,MAAM,SAAS,GAAG;AACrD,cAAM,WAAW,CAAC,GAAG,YAAY,KAAK,EAAE,KAAK,MAAM,KAAK,OAAM,IAAK,GAAG;AACtEA,kBAAc,SAAS,QAAQ;AAAA,MACjC;AAAA,IACF;AAEA,UAAM,iBAAiB,MAAM;AAC3B,iBAAW,QAAQ,CAAC,WAAW;AAAA,IAEjC;AAEA,UAAM,qBAAqB,CAAC,aAAa;AACvC,YAAM,QAAQ,gBAAgB,MAAM,QAAQ,QAAQ;AACpD,UAAI,QAAQ,IAAI;AACd,wBAAgB,MAAM,OAAO,OAAO,CAAC;AAAA,MACvC,OAAO;AACL,wBAAgB,MAAM,KAAK,QAAQ;AAAA,MACrC;AAAA,IAEF;AAEA,UAAM,aAAa,MAAM;AACvB,UAAI,YAAY,MAAM,SAAS,GAAG;AAChC,oBAAY,MAAM,QAAQ,WAAS;AACjCA,oBAAc,WAAW,KAAK;AAAA,QAChC,CAAC;AACD,qBAAa,QAAQ;AAAA,MACvB;AAAA,IACF;AAEA,UAAM,YAAY,MAAM;AACtB,aAAO,KAAK,EAAE,MAAM,cAAc,QAAQ,EAAE,KAAK,MAAM,MAAM,IAAG,EAAE,CAAE;AAAA,IACtE;AAEA,UAAM,cAAc,YAAY;AAC9B,UAAI,QAAQ,6CAA6C,GAAG;AAC1D,YAAI;AACF,gBAAMC,QAAc,YAAY,MAAM,MAAM,GAAG;AAC/C,iBAAO,KAAK,EAAE,MAAM,gBAAe,CAAE;AAAA,QACvC,SAAS,OAAO;AACd,kBAAQ,MAAM,2BAA2B,KAAK;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,MAAM;AACrB,gBAAU,UAAU,UAAU,OAAO,SAAS,IAAI;AAClD,mBAAa,QAAQ;AAAA,IACvB;AAGA,UAAM,iBAAiB,YAAY;AACjC,UAAI;AACF,cAAMA,QAAc,gBAAgB,MAAM,OAAO,GAAG;AAGpD,YAAI,MAAM,OAAO,SAAS,QAAQ;AAChC,gBAAM,YAAY,MAAM,MAAM,QAAQ,IAAI,OAAK,EAAE,GAAG;AACpD,gBAAM,SAAS,MAAMA,QAAc,YAAY;AAAA,YAC7C,QAAQ,EAAE,KAAK,UAAS;AAAA,YACxB,QAAQ;AAAA,YACR,UAAU;AAAA,YACV,OAAO;AAAA,UACf,CAAO;AAGD,qBAAW,QAAQ,OAAO,OAAO,OAAK,EAAE,QAAQ,MAAM,MAAM,GAAG,EAAE,MAAM,GAAG,CAAC;AAAA,QAC7E;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ,MAAM,8BAA8B,KAAK;AAAA,MACnD;AAAA,IACF;AAGA,cAAU,YAAY;AACpB,YAAM,cAAc;AAEpB,YAAM,eAAc;AAEpB,gBAAU,QAAQ;AAClB,YAAM,aAAa;AAAA,IACrB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"Album.vue.js","sources":["../../../../../../../src/modules/music/components/pages/Album.vue"],"sourcesContent":["<!-- components/pages/Album.vue -->\n<template>\n <div class=\"album-page pd-small\">\n <!-- Loading -->\n <div v-if=\"isLoading\" class=\"w-100 h-25r flex-center flex\">\n <Loader />\n </div>\n \n <!-- Not Found -->\n <div v-if=\"hasLoaded && !album\" class=\"t-center pd-big\">\n <h2 class=\"\">Album not found</h2>\n <p class=\"t-transp t-medium\">The album you're looking for doesn't exist or has been removed.</p>\n </div>\n \n <!-- Album Content -->\n <div v-if=\"album\" class=\"album-content cols-2 mobile:cols-1 gap-big\">\n <!-- Left Column - Cover & Stats -->\n <div class=\"pos-sticky pos-t-0 mobile:pos-relative album-cover-section\">\n <!-- Cover -->\n <Media \n :url=\"album.coverArt || '/logo/logo-placeholder.jpg'\"\n :alt=\"album.title\"\n class=\"aspect-1x1 w-100 mn-b-small radius-medium o-hidden\"\n />\n <!-- Quick Stats -->\n <div class=\"stats-grid grid cols-2 gap-small\">\n <div class=\"stat-card bg-light pd-medium radius-medium t-center\">\n <div class=\" mn-b-thin\">{{ album.totalTracks || 0 }}</div>\n <div class=\"t-small t-transp t-uppercase\">Tracks</div>\n </div>\n <div class=\"stat-card bg-light pd-medium radius-medium t-center\">\n <div class=\" mn-b-thin\">{{ formatNumber(album.views) }}</div>\n <div class=\"t-small t-transp t-uppercase\">Views</div>\n </div>\n </div>\n </div>\n\n <!-- Right Column - Album Details -->\n <div class=\"album-details-section\">\n <!-- Album Type Badge -->\n <div class=\"flex items-center gap-small mn-b-small\">\n <span class=\"bg-light t-medium pd-thin radius-thin uppercase t-small t-uppercase\">\n {{ album.type }}\n </span>\n <span v-if=\"album.status === 'published'\" class=\"bg-light t-medium pd-thin radius-thin uppercase t-small t-uppercase\">\n Published\n </span>\n </div>\n\n <!-- Album Title -->\n <h1 class=\"h1 mn-b-medium\">{{ album.title }}</h1>\n\n <!-- Action Buttons -->\n <ActionButtons :buttons=\"actionButtons\" />\n\n <!-- Artists Cards -->\n <div class=\"artists-section mn-b-medium\">\n <h3 class=\"t-medium mn-b-small\" v-if=\"album.artists\">Artists</h3>\n <div class=\"flex flex-column gap-small\">\n <ArtistCardSmall \n v-for=\"artist in album.artists\" \n :key=\"artist._id\"\n :artist=\"artist\"\n :is-following=\"followedArtists.includes(artist._id)\"\n :show-follow-button=\"!isOwner\"\n @toggle-follow=\"toggleFollowArtist\"\n />\n </div>\n </div>\n\n <!-- Metadata Cards -->\n <h3 class=\"t-medium mn-b-small\">Metadata</h3>\n <div class=\"metadata-grid grid cols-2 gap-small mn-b-medium\">\n <!-- Release Date -->\n <div class=\"metadata-card bg-light pd-medium radius-medium flex items-center gap-regular\">\n <IconCalendar class=\"i-medium t-primary\" />\n <div>\n <div class=\"t-small t-transp t-uppercase\">Released</div>\n <div class=\"t-medium \">{{ formatDate(album.releaseDate) }}</div>\n </div>\n </div>\n\n <!-- Total Duration -->\n <div class=\"metadata-card bg-light pd-medium radius-medium flex items-center gap-regular\">\n <IconClock class=\"i-medium t-primary\" />\n <div>\n <div class=\"t-small t-transp t-uppercase\">Duration</div>\n <div class=\"t-medium \">{{ totalDuration }}</div>\n </div>\n </div>\n\n <!-- Label -->\n <div v-if=\"album.label\" class=\"metadata-card bg-light pd-medium radius-medium flex items-center gap-regular\">\n <IconDisc class=\"i-medium t-primary\" />\n <div>\n <div class=\"t-small t-transp t-uppercase\">Label</div>\n <div class=\"t-medium \">{{ album.label }}</div>\n </div>\n </div>\n\n <!-- Visibility -->\n <div class=\"metadata-card bg-light pd-medium radius-medium flex items-center gap-regular\">\n <IconEye class=\"i-medium t-primary\" />\n <div>\n <div class=\"t-small t-transp t-uppercase\">Visibility</div>\n <div class=\"t-medium \">{{ album.isPublic ? 'Public' : 'Private' }}</div>\n </div>\n </div>\n </div>\n\n <!-- Genres & Tags -->\n <div v-if=\"(album.genres && album.genres.length) || (album.tags && album.tags.length)\" class=\"tags-section mn-b-medium\">\n <h3 class=\"t-medium mn-b-small\">Genres & Tags</h3>\n <div class=\"flex gap-thin flex-wrap\">\n <span \n v-for=\"genre in album.genres\" \n :key=\"genre\"\n class=\"tag bg-main t-medium pd-thin radius-thin t-small cursor-pointer\"\n >\n {{ genre }}\n </span>\n <span \n v-for=\"tag in album.tags\" \n :key=\"tag\"\n class=\"tag bg-light t-transp pd-thin-big radius-small t-small hover-bg-light cursor-pointer\"\n >\n #{{ tag }}\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Album Tracks -->\n <section v-if=\"!isLoading && album && albumTracks.length\" class=\"tracks-section mn-t-big\">\n <h2 class=\"h2 mn-b-medium\">Tracklist</h2>\n <Feed\n :store=\"{\n read: () => Promise.resolve(albumTracks),\n state: { isLoading: false }\n }\"\n :external=\"true\"\n :items=\"albumTracks\"\n :states=\"{\n empty: {\n title: 'No tracks in album',\n description: 'This album appears to be empty',\n class: 'pd-medium t-center'\n }\n }\"\n >\n <template #default=\"{ items }\">\n <div class=\"bg-light radius-medium o-hidden\">\n <TrackListCard\n v-for=\"(track, index) in items\"\n :key=\"track._id\"\n :track=\"track\"\n :index=\"index + 1\"\n :showAlbum=\"false\"\n :showCover=\"false\"\n />\n </div>\n </template>\n </Feed>\n </section>\n\n <!-- More from Artists -->\n <section v-if=\"!isLoading && album && moreAlbums.length\" class=\"more-albums-section mn-t-big\">\n <div class=\"flex justify-between items-center mn-b-medium\">\n <h2 class=\"h2\">More Albums</h2>\n <router-link \n v-if=\"album.artists && album.artists[0]\"\n :to=\"{ name: 'artist', params: { url: album.artists[0].url } }\" \n class=\"t-primary hover-opacity\"\n >\n See all\n </router-link>\n </div>\n <div class=\"flex flex-nowrap gap-small o-x-scroll overscroll-behavior-x-contain scroll-behavior-smooth scroll-snap-type-x-mandatory scroll-hide\"\n >\n <li v-for=\"relatedAlbum in moreAlbums\" :key=\"album._id\" class=\"flex-none scroll-snap-align-start\">\n <AlbumCard :album=\"relatedAlbum\" class=\"w-15r transition-cubic-in-out\" />\n </li>\n </div>\n </section>\n </div>\n</template>\n\n<script setup>\nimport { ref, computed, onMounted, watch } from 'vue';\nimport { useRoute, useRouter } from 'vue-router';\nimport Button from '@martyrs/src/components/Button/Button.vue';\nimport Loader from '@martyrs/src/components/Loader/Loader.vue';\nimport Media from '@martyrs/src/components/Media/Media.vue';\nimport Dropdown from '@martyrs/src/components/Dropdown/Dropdown.vue';\nimport Feed from '@martyrs/src/components/Feed/Feed.vue';\n\n// Icons\nimport IconPlay from '@martyrs/src/modules/icons/navigation/IconPlay.vue';\nimport IconLike from '@martyrs/src/modules/icons/navigation/IconLike.vue';\nimport IconEllipsis from '@martyrs/src/modules/icons/navigation/IconEllipsis.vue';\nimport IconShuffle from '@martyrs/src/modules/icons/navigation/IconShuffle.vue';\nimport IconCalendar from '@martyrs/src/modules/icons/entities/IconCalendar.vue';\nimport IconClock from '@martyrs/src/modules/icons/entities/IconTime.vue';\nimport IconEye from '@martyrs/src/modules/icons/actions/IconShow.vue';\nimport IconDisc from '@martyrs/src/modules/icons/entities/IconMusic.vue';\nimport IconVerified from '@martyrs/src/modules/icons/navigation/IconCheckmark.vue';\n\n// Components\nimport ActionButtons from '../blocks/ActionButtons.vue';\nimport TrackListCard from '../cards/TrackListCard.vue';\nimport AlbumCard from '../cards/AlbumCard.vue';\nimport ArtistCardSmall from '../cards/ArtistCardSmall.vue';\n\n// Store\nimport { state as albumsState, actions as albumsActions } from '../../store/albums.js';\nimport { actions as playerActions } from '../../store/player.js';\nimport { state as authState } from '@martyrs/src/modules/auth/views/store/auth.js';\n\nconst route = useRoute();\nconst router = useRouter();\n\n// Emits\nconst emits = defineEmits(['page-loading', 'page-loaded']);\n\n// State\nconst hasLoaded = ref(false);\nconst isFavorite = ref(false);\nconst followedArtists = ref([]);\nconst moreAlbums = ref([]);\n\n// Clear state\nalbumsState.currentAlbum = null;\nalbumsState.currentAlbumTracks = [];\n\n// Computed\nconst album = computed(() => albumsState.currentAlbum);\nconst albumTracks = computed(() => albumsState.currentAlbumTracks || []);\n\nconst isOwner = computed(() => {\n return album.value?.owner?.target === authState.user?._id;\n});\n\nconst totalDuration = computed(() => {\n if (!albumTracks.value.length) return '0:00';\n const totalSeconds = albumTracks.value.reduce((sum, track) => sum + (track.duration || 0), 0);\n return formatDuration(totalSeconds);\n});\n\nconst actionButtons = computed(() => {\n const buttons = [\n {\n type: 'button',\n class: 't-white bg-black',\n icon: IconPlay,\n iconFill: 'rgb(var(--white))',\n text: 'Play All',\n action: playAlbum\n },\n {\n type: 'button',\n class: 'bg-light',\n icon: IconShuffle,\n text: 'Shuffle',\n action: shufflePlay\n },\n {\n type: 'button',\n class: 'bg-light',\n icon: IconLike,\n iconFill: isFavorite.value ? 'rgb(var(--main))' : 'rgb(var(--black))',\n text: isFavorite.value ? 'Liked' : 'Like',\n action: toggleFavorite\n },\n {\n type: 'dropdown',\n items: [\n { text: 'Add to Queue', action: addToQueue },\n { text: 'Copy Link', action: copyLink },\n { text: 'Add to Playlist', action: addToPlaylist }\n ]\n }\n ];\n\n if (isOwner.value) {\n const items = buttons[3].items;\n items.push({ separator: true });\n items.push({ text: 'Edit Album', action: editAlbum });\n items.push({ text: 'Delete Album', action: deleteAlbum, color: 'danger' });\n }\n\n return buttons;\n});\n\n// Format helpers\nconst formatDate = (dateString) => {\n if (!dateString) return 'Unknown';\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n};\n\nconst formatDuration = (seconds) => {\n if (!seconds) return '0:00';\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = Math.floor(seconds % 60);\n \n if (h > 0) {\n return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;\n }\n return `${m}:${s.toString().padStart(2, '0')}`;\n};\n\nconst formatNumber = (num) => {\n if (!num) return '0';\n if (num >= 1000000) {\n return (num / 1000000).toFixed(1) + 'M';\n } else if (num >= 1000) {\n return (num / 1000).toFixed(1) + 'K';\n }\n return num.toString();\n};\n\n// Actions\nconst playAlbum = () => {\n if (albumTracks.value && albumTracks.value.length > 0) {\n playerActions.setQueue(albumTracks.value);\n }\n};\n\nconst shufflePlay = () => {\n if (albumTracks.value && albumTracks.value.length > 0) {\n const shuffled = [...albumTracks.value].sort(() => Math.random() - 0.5);\n playerActions.setQueue(shuffled);\n }\n};\n\nconst toggleFavorite = () => {\n isFavorite.value = !isFavorite.value;\n // TODO: Implement actual saving\n};\n\nconst toggleFollowArtist = (artistId) => {\n const index = followedArtists.value.indexOf(artistId);\n if (index > -1) {\n followedArtists.value.splice(index, 1);\n } else {\n followedArtists.value.push(artistId);\n }\n // TODO: Implement actual following\n};\n\nconst addToQueue = () => {\n if (albumTracks.value.length > 0) {\n albumTracks.value.forEach(track => {\n playerActions.addToQueue(track);\n });\n }\n};\n\nconst addToPlaylist = () => {\n // TODO: Implement add to playlist\n console.log('Add album to playlist');\n};\n\nconst editAlbum = () => {\n router.push({ name: 'album-edit', params: { url: album.value.url } });\n};\n\nconst deleteAlbum = async () => {\n if (confirm('Are you sure you want to delete this album?')) {\n try {\n await albumsActions.deleteAlbum(album.value._id);\n router.push({ name: 'music-library' });\n } catch (error) {\n console.error('Failed to delete album:', error);\n }\n }\n};\n\nconst copyLink = () => {\n navigator.clipboard.writeText(window.location.href);\n};\n\n// Data fetching\nconst fetchAlbumData = async () => {\n try {\n await albumsActions.fetchAlbumByUrl(route.params.url);\n \n // Fetch more albums from the same artists\n if (album.value?.artists?.length) {\n const artistIds = album.value.artists.map(a => a._id);\n const albums = await albumsActions.fetchAlbums({\n artist: { $in: artistIds },\n status: 'published',\n isPublic: true,\n limit: 6\n });\n \n // Filter out current album\n moreAlbums.value = albums.filter(a => a._id !== album.value._id).slice(0, 5);\n }\n } catch (error) {\n console.error('Error fetching album data:', error);\n }\n};\n\n// Lifecycle\nonMounted(async () => {\n emits('page-loading');\n \n await fetchAlbumData();\n \n hasLoaded.value = true;\n emits('page-loaded');\n});\n</script>\n\n<style scoped>\n</style>"],"names":["albumsState","authState","IconPlay","IconShuffle","IconLike","playerActions","albumsActions"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2NA,UAAM,QAAQ,SAAQ;AACtB,UAAM,SAAS,UAAS;AAGxB,UAAM,QAAQ;AAGd,UAAM,YAAY,IAAI,KAAK;AAC3B,UAAM,aAAa,IAAI,KAAK;AAC5B,UAAM,kBAAkB,IAAI,EAAE;AAC9B,UAAM,aAAa,IAAI,EAAE;AAGzBA,UAAY,eAAe;AAC3BA,UAAY,qBAAqB,CAAA;AAGjC,UAAM,QAAQ,SAAS,MAAMA,MAAY,YAAY;AACrD,UAAM,cAAc,SAAS,MAAMA,MAAY,sBAAsB,CAAA,CAAE;AAEvE,UAAM,UAAU,SAAS,MAAM;AAC7B,aAAO,MAAM,OAAO,OAAO,WAAWC,QAAU,MAAM;AAAA,IACxD,CAAC;AAED,UAAM,gBAAgB,SAAS,MAAM;AACnC,UAAI,CAAC,YAAY,MAAM,OAAQ,QAAO;AACtC,YAAM,eAAe,YAAY,MAAM,OAAO,CAAC,KAAK,UAAU,OAAO,MAAM,YAAY,IAAI,CAAC;AAC5F,aAAO,eAAe,YAAY;AAAA,IACpC,CAAC;AAED,UAAM,gBAAgB,SAAS,MAAM;AACnC,YAAM,UAAU;AAAA,QACd;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,MAAMC;AAAAA,UACN,UAAU;AAAA,UACV,MAAM;AAAA,UACN,QAAQ;AAAA,QACd;AAAA,QACI;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,MAAMC;AAAAA,UACN,MAAM;AAAA,UACN,QAAQ;AAAA,QACd;AAAA,QACI;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,UACP,MAAMC;AAAAA,UACN,UAAU,WAAW,QAAQ,qBAAqB;AAAA,UAClD,MAAM,WAAW,QAAQ,UAAU;AAAA,UACnC,QAAQ;AAAA,QACd;AAAA,QACI;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,YACL,EAAE,MAAM,gBAAgB,QAAQ,WAAU;AAAA,YAC1C,EAAE,MAAM,aAAa,QAAQ,SAAQ;AAAA,YACrC,EAAE,MAAM,mBAAmB,QAAQ,cAAa;AAAA,UACxD;AAAA,QACA;AAAA,MACA;AAEE,UAAI,QAAQ,OAAO;AACjB,cAAM,QAAQ,QAAQ,CAAC,EAAE;AACzB,cAAM,KAAK,EAAE,WAAW,KAAI,CAAE;AAC9B,cAAM,KAAK,EAAE,MAAM,cAAc,QAAQ,WAAW;AACpD,cAAM,KAAK,EAAE,MAAM,gBAAgB,QAAQ,aAAa,OAAO,UAAU;AAAA,MAC3E;AAEA,aAAO;AAAA,IACT,CAAC;AAGD,UAAM,aAAa,CAAC,eAAe;AACjC,UAAI,CAAC,WAAY,QAAO;AACxB,aAAO,IAAI,KAAK,UAAU,EAAE,mBAAmB,SAAS;AAAA,QACtD,MAAM;AAAA,QACN,OAAO;AAAA,QACP,KAAK;AAAA,MACT,CAAG;AAAA,IACH;AAEA,UAAM,iBAAiB,CAAC,YAAY;AAClC,UAAI,CAAC,QAAS,QAAO;AACrB,YAAM,IAAI,KAAK,MAAM,UAAU,IAAI;AACnC,YAAM,IAAI,KAAK,MAAO,UAAU,OAAQ,EAAE;AAC1C,YAAM,IAAI,KAAK,MAAM,UAAU,EAAE;AAEjC,UAAI,IAAI,GAAG;AACT,eAAO,GAAG,CAAC,IAAI,EAAE,SAAQ,EAAG,SAAS,GAAG,GAAG,CAAC,IAAI,EAAE,SAAQ,EAAG,SAAS,GAAG,GAAG,CAAC;AAAA,MAC/E;AACA,aAAO,GAAG,CAAC,IAAI,EAAE,SAAQ,EAAG,SAAS,GAAG,GAAG,CAAC;AAAA,IAC9C;AAEA,UAAM,eAAe,CAAC,QAAQ;AAC5B,UAAI,CAAC,IAAK,QAAO;AACjB,UAAI,OAAO,KAAS;AAClB,gBAAQ,MAAM,KAAS,QAAQ,CAAC,IAAI;AAAA,MACtC,WAAW,OAAO,KAAM;AACtB,gBAAQ,MAAM,KAAM,QAAQ,CAAC,IAAI;AAAA,MACnC;AACA,aAAO,IAAI,SAAQ;AAAA,IACrB;AAGA,UAAM,YAAY,MAAM;AACtB,UAAI,YAAY,SAAS,YAAY,MAAM,SAAS,GAAG;AACrDC,gBAAc,SAAS,YAAY,KAAK;AAAA,MAC1C;AAAA,IACF;AAEA,UAAM,cAAc,MAAM;AACxB,UAAI,YAAY,SAAS,YAAY,MAAM,SAAS,GAAG;AACrD,cAAM,WAAW,CAAC,GAAG,YAAY,KAAK,EAAE,KAAK,MAAM,KAAK,OAAM,IAAK,GAAG;AACtEA,gBAAc,SAAS,QAAQ;AAAA,MACjC;AAAA,IACF;AAEA,UAAM,iBAAiB,MAAM;AAC3B,iBAAW,QAAQ,CAAC,WAAW;AAAA,IAEjC;AAEA,UAAM,qBAAqB,CAAC,aAAa;AACvC,YAAM,QAAQ,gBAAgB,MAAM,QAAQ,QAAQ;AACpD,UAAI,QAAQ,IAAI;AACd,wBAAgB,MAAM,OAAO,OAAO,CAAC;AAAA,MACvC,OAAO;AACL,wBAAgB,MAAM,KAAK,QAAQ;AAAA,MACrC;AAAA,IAEF;AAEA,UAAM,aAAa,MAAM;AACvB,UAAI,YAAY,MAAM,SAAS,GAAG;AAChC,oBAAY,MAAM,QAAQ,WAAS;AACjCA,kBAAc,WAAW,KAAK;AAAA,QAChC,CAAC;AAAA,MACH;AAAA,IACF;AAEA,UAAM,gBAAgB,MAAM;AAE1B,cAAQ,IAAI,uBAAuB;AAAA,IACrC;AAEA,UAAM,YAAY,MAAM;AACtB,aAAO,KAAK,EAAE,MAAM,cAAc,QAAQ,EAAE,KAAK,MAAM,MAAM,IAAG,EAAE,CAAE;AAAA,IACtE;AAEA,UAAM,cAAc,YAAY;AAC9B,UAAI,QAAQ,6CAA6C,GAAG;AAC1D,YAAI;AACF,gBAAMC,UAAc,YAAY,MAAM,MAAM,GAAG;AAC/C,iBAAO,KAAK,EAAE,MAAM,gBAAe,CAAE;AAAA,QACvC,SAAS,OAAO;AACd,kBAAQ,MAAM,2BAA2B,KAAK;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,MAAM;AACrB,gBAAU,UAAU,UAAU,OAAO,SAAS,IAAI;AAAA,IACpD;AAGA,UAAM,iBAAiB,YAAY;AACjC,UAAI;AACF,cAAMA,UAAc,gBAAgB,MAAM,OAAO,GAAG;AAGpD,YAAI,MAAM,OAAO,SAAS,QAAQ;AAChC,gBAAM,YAAY,MAAM,MAAM,QAAQ,IAAI,OAAK,EAAE,GAAG;AACpD,gBAAM,SAAS,MAAMA,UAAc,YAAY;AAAA,YAC7C,QAAQ,EAAE,KAAK,UAAS;AAAA,YACxB,QAAQ;AAAA,YACR,UAAU;AAAA,YACV,OAAO;AAAA,UACf,CAAO;AAGD,qBAAW,QAAQ,OAAO,OAAO,OAAK,EAAE,QAAQ,MAAM,MAAM,GAAG,EAAE,MAAM,GAAG,CAAC;AAAA,QAC7E;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ,MAAM,8BAA8B,KAAK;AAAA,MACnD;AAAA,IACF;AAGA,cAAU,YAAY;AACpB,YAAM,cAAc;AAEpB,YAAM,eAAc;AAEpB,gBAAU,QAAQ;AAClB,YAAM,aAAa;AAAA,IACrB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|