@ozdao/martyrs 0.2.565 → 0.2.566

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.
Files changed (99) hide show
  1. package/dist/martyrs/dist/main-B9o1iBAZ.js +943 -0
  2. package/dist/martyrs/dist/main-B9o1iBAZ.js.map +1 -0
  3. package/dist/martyrs/dist/web-BF3ijvEr.js +55 -0
  4. package/dist/martyrs/dist/web-BF3ijvEr.js.map +1 -0
  5. package/dist/martyrs/src/components/BottomSheet/BottomSheet.vue.js +96 -0
  6. package/dist/martyrs/src/components/BottomSheet/BottomSheet.vue.js.map +1 -0
  7. package/dist/martyrs/src/modules/core/views/components/layouts/App.vue.js +1 -1
  8. package/dist/martyrs/src/modules/core/views/components/layouts/Client.vue.js +1 -0
  9. package/dist/martyrs/src/modules/core/views/components/layouts/Client.vue.js.map +1 -1
  10. package/dist/martyrs/src/modules/core/views/components/sections/Walkthrough.vue.js +1 -1
  11. package/dist/martyrs/src/modules/events/components/elements/ButtonCheck.vue.js +1 -1
  12. package/dist/martyrs/src/modules/events/events.client.js +15 -12
  13. package/dist/martyrs/src/modules/events/events.client.js.map +1 -1
  14. package/dist/martyrs/src/modules/music/components/blocks/ActionButtons.vue.js +95 -0
  15. package/dist/martyrs/src/modules/music/components/blocks/ActionButtons.vue.js.map +1 -0
  16. package/dist/martyrs/src/modules/music/components/cards/AlbumCard.vue.js +5 -2
  17. package/dist/martyrs/src/modules/music/components/cards/AlbumCard.vue.js.map +1 -1
  18. package/dist/martyrs/src/modules/music/components/cards/ArtistCardSmall.vue.js +24 -24
  19. package/dist/martyrs/src/modules/music/components/cards/ArtistCardSmall.vue.js.map +1 -1
  20. package/dist/martyrs/src/modules/music/components/layouts/MusicBottomPlayer.vue.js +31 -6
  21. package/dist/martyrs/src/modules/music/components/layouts/MusicBottomPlayer.vue.js.map +1 -1
  22. package/dist/martyrs/src/modules/music/components/pages/Album.vue.js +120 -205
  23. package/dist/martyrs/src/modules/music/components/pages/Album.vue.js.map +1 -1
  24. package/dist/martyrs/src/modules/music/components/pages/MusicHome.vue.js +9 -13
  25. package/dist/martyrs/src/modules/music/components/pages/MusicHome.vue.js.map +1 -1
  26. package/dist/martyrs/src/modules/music/components/pages/Playlist.vue.js +166 -245
  27. package/dist/martyrs/src/modules/music/components/pages/Playlist.vue.js.map +1 -1
  28. package/dist/martyrs/src/modules/music/components/pages/Track.vue.js +135 -220
  29. package/dist/martyrs/src/modules/music/components/pages/Track.vue.js.map +1 -1
  30. package/dist/martyrs/src/modules/music/components/player/FullscreenPlayer.vue.js +171 -0
  31. package/dist/martyrs/src/modules/music/components/player/FullscreenPlayer.vue.js.map +1 -0
  32. package/dist/martyrs/src/modules/music/components/player/MusicPlayer.vue.js +31 -153
  33. package/dist/martyrs/src/modules/music/components/player/MusicPlayer.vue.js.map +1 -1
  34. package/dist/martyrs/src/modules/music/components/player/PlayerControls.vue.js +96 -0
  35. package/dist/martyrs/src/modules/music/components/player/PlayerControls.vue.js.map +1 -0
  36. package/dist/martyrs/src/modules/music/components/player/VolumeControl.vue.js +55 -27
  37. package/dist/martyrs/src/modules/music/components/player/VolumeControl.vue.js.map +1 -1
  38. package/dist/martyrs/src/modules/music/components/player/tonar.png.js +5 -0
  39. package/dist/martyrs/src/modules/music/components/player/tonar.png.js.map +1 -0
  40. package/dist/martyrs/src/modules/music/store/albums.js +8 -2
  41. package/dist/martyrs/src/modules/music/store/albums.js.map +1 -1
  42. package/dist/martyrs/src/modules/music/store/player.js +83 -65
  43. package/dist/martyrs/src/modules/music/store/player.js.map +1 -1
  44. package/dist/martyrs/src/modules/music/store/tracks.js +4 -13
  45. package/dist/martyrs/src/modules/music/store/tracks.js.map +1 -1
  46. package/dist/martyrs/src/modules/notifications/notifications.client.js +2 -2
  47. package/dist/martyrs.css +1 -1
  48. package/dist/music.server.js +33 -6
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. package/dist/node_modules/.pnpm/{@capacitor_core@7.0.1 → @capacitor_core@7.4.4}/node_modules/@capacitor/core/dist/index.js +2 -1
  56. package/dist/node_modules/.pnpm/@capacitor_core@7.4.4/node_modules/@capacitor/core/dist/index.js.map +1 -0
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. 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
  69. 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
  70. package/dist/style.css +214 -138
  71. package/package.json +1 -1
  72. package/src/components/BottomSheet/BottomSheet.vue +4 -4
  73. package/src/modules/LAYOUT.MD +767 -0
  74. package/src/modules/core/views/components/layouts/Client.vue +1 -1
  75. package/src/modules/events/events.client.js +3 -0
  76. package/src/modules/music/components/blocks/ActionButtons.vue +74 -0
  77. package/src/modules/music/components/cards/AlbumCard.vue +1 -1
  78. package/src/modules/music/components/cards/ArtistCardSmall.vue +8 -6
  79. package/src/modules/music/components/layouts/MusicBottomPlayer.vue +94 -4
  80. package/src/modules/music/components/pages/Album.vue +55 -67
  81. package/src/modules/music/components/pages/MusicHome.vue +4 -6
  82. package/src/modules/music/components/pages/Playlist.vue +61 -70
  83. package/src/modules/music/components/pages/Track.vue +54 -71
  84. package/src/modules/music/components/player/FullscreenPlayer.vue +248 -0
  85. package/src/modules/music/components/player/MusicPlayer.vue +21 -216
  86. package/src/modules/music/components/player/PlayerControls.vue +112 -0
  87. package/src/modules/music/components/player/Visualizer.vue +151 -0
  88. package/src/modules/music/components/player/VolumeControl.vue +75 -23
  89. package/src/modules/music/components/player/tonar.png +0 -0
  90. package/src/modules/music/routes/albums.routes.js +13 -12
  91. package/src/modules/music/routes/tracks.routes.js +39 -0
  92. package/src/modules/music/store/albums.js +10 -2
  93. package/src/modules/music/store/player.js +101 -89
  94. package/src/modules/music/store/tracks.js +5 -21
  95. package/src/styles/config.scss +6 -6
  96. 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
  97. 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
  98. package/dist/node_modules/.pnpm/@capacitor_core@7.0.1/node_modules/@capacitor/core/dist/index.js.map +0 -1
  99. /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
@@ -13,7 +13,7 @@
13
13
  }"
14
14
  >
15
15
  <transition name="moveFromTop" appear>
16
- <Loader v-if="!page || store.core.state.loading" class="pos-fixed"/>
16
+ <Loader v-if="!page || store.core.state.loading" :centered="true" class="pos-fixed"/>
17
17
  </transition>
18
18
 
19
19
  <transition
@@ -14,6 +14,7 @@ import EditEvent from './components/pages/EditEvent.vue';
14
14
  import Event from './components/pages/Event.vue';
15
15
  import EventsPage from './components/pages/Events.vue';
16
16
  import FeaturedEvents from './components/sections/FeaturedEvents.vue';
17
+ import EventsHot from './components/sections/EventsHot.vue';
17
18
  import Feed from './components/sections/Feed.vue';
18
19
  import List from './components/sections/List.vue';
19
20
 
@@ -44,6 +45,7 @@ const ModuleEvents = {
44
45
  Feed,
45
46
  List,
46
47
  FeaturedEvents,
48
+ EventsHot,
47
49
  // Layouts
48
50
  EventsLayout,
49
51
  // Pages
@@ -64,6 +66,7 @@ export {
64
66
  EventsLayout,
65
67
  EventsPage,
66
68
  FeaturedEvents,
69
+ EventsHot,
67
70
  Feed,
68
71
  initializeEvents,
69
72
  List,
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <div class="flex gap-small mn-b-medium">
3
+ <template v-for="(button, index) in buttons" :key="index">
4
+ <!-- Regular button -->
5
+ <Button
6
+ v-if="button.type !== 'dropdown'"
7
+ :class="['flex-1 radius-thin flex-center gap-thin', button.class]"
8
+ @click="button.action"
9
+ >
10
+ <component
11
+ v-if="button.icon"
12
+ :is="button.icon"
13
+ :fill="button.iconFill"
14
+ class="i-medium"
15
+ />
16
+ {{ button.text }}
17
+ </Button>
18
+
19
+ <!-- Dropdown button -->
20
+ <Dropdown
21
+ v-else
22
+ :label="{component: IconEllipsis, class: 'bg-light radius-thin pd-thin i-big' }"
23
+ v-model="dropdownOpen"
24
+ class="relative"
25
+ >
26
+ <template #trigger>
27
+ <Button color="transp" size="medium" class="w-3r h-3r radius-full">
28
+ <IconEllipsis class="i-medium" />
29
+ </Button>
30
+ </template>
31
+ <template #default>
32
+ <div class="dropdown-menu bg-white pd-small radius-medium shadow-big mn-t-thin">
33
+ <template v-for="(item, idx) in button.items" :key="idx">
34
+ <hr v-if="item.separator" class="mn-v-thin border-dark-transp-10" />
35
+ <Button
36
+ v-else
37
+ @click="handleDropdownClick(item.action)"
38
+ :color="item.color || 'transp'"
39
+ size="small"
40
+ class="w-100 justify-start"
41
+ :class="item.class"
42
+ >
43
+ {{ item.text }}
44
+ </Button>
45
+ </template>
46
+ </div>
47
+ </template>
48
+ </Dropdown>
49
+ </template>
50
+ </div>
51
+ </template>
52
+
53
+ <script setup>
54
+ import { ref } from 'vue';
55
+ import Button from '@martyrs/src/components/Button/Button.vue';
56
+ import Dropdown from '@martyrs/src/components/Dropdown/Dropdown.vue';
57
+ import IconEllipsis from '@martyrs/src/modules/icons/navigation/IconEllipsis.vue';
58
+
59
+ const props = defineProps({
60
+ buttons: {
61
+ type: Array,
62
+ required: true
63
+ }
64
+ });
65
+
66
+ const dropdownOpen = ref(false);
67
+
68
+ const handleDropdownClick = (action) => {
69
+ if (action) {
70
+ action();
71
+ }
72
+ dropdownOpen.value = false;
73
+ };
74
+ </script>
@@ -21,7 +21,7 @@
21
21
  </div>
22
22
  </div>
23
23
  <div class="album-info pd-medium bg-light">
24
- <h3 class="mn-b-thin t-medium t-truncate">{{ album.title }}</h3>
24
+ <h3 class="mn-b-thin t-medium t-truncate" style="height: 1.50rem">{{ album.title }}</h3>
25
25
  <p class="t-transp t-small t-truncate">{{ albumInfo }}</p>
26
26
  </div>
27
27
  </router-link>
@@ -1,9 +1,11 @@
1
1
  <!-- components/cards/ArtistCard.vue -->
2
2
  <template>
3
- <div class="artist-card bg-light pd-medium radius-medium flex flex-center gap-big">
4
- <router-link
5
- :to="artist.url ? { name: 'artist', params: { url: artist.url } } : artist.to"
6
- class="flex flex-v-center gap-thin flex-1 hover-opacity"
3
+ <router-link
4
+ :to="artist.url ? { name: 'artist', params: { url: artist.url } } : artist.to"
5
+ class="artist-card bg-light pd-medium radius-medium flex flex-v-center flex-justify-between gap-thin flex-1 hover-opacity"
6
+ >
7
+ <div
8
+ class="flex flex-nowrap flex-v-center gap-thin"
7
9
  >
8
10
  <div class="artist-avatar">
9
11
  <Media
@@ -24,7 +26,7 @@
24
26
  </div>
25
27
  <span class="t-small t-transp">{{ artistType }}</span>
26
28
  </div>
27
- </router-link>
29
+ </div>
28
30
 
29
31
  <Button
30
32
  v-if="showFollowButton"
@@ -35,7 +37,7 @@
35
37
  >
36
38
  {{ isFollowing ? 'Following' : 'Follow' }}
37
39
  </Button>
38
- </div>
40
+ </router-link>
39
41
  </template>
40
42
 
41
43
  <script setup>
@@ -1,17 +1,107 @@
1
1
  <!-- components/layouts/MusicBottomPlayer.vue -->
2
2
  <template>
3
- <MusicPlayer v-if="currentTrack" class="pos-absolute pos-b-0 w-100" />
3
+ <div>
4
+ <MusicPlayer v-if="currentTrack && !showFullPlayer" class="pos-absolute pos-b-0 w-100" />
5
+
6
+ <BottomSheet
7
+ v-if="showFullPlayer"
8
+ :show="showFullPlayer"
9
+ @toggle="playerActions.toggleFullPlayer()"
10
+ >
11
+ <FullscreenPlayer />
12
+ </BottomSheet>
13
+ </div>
4
14
  </template>
5
15
 
6
16
  <script setup>
7
- import { computed } from 'vue';
17
+ import { computed, watch } from 'vue';
18
+ import { useRoute } from 'vue-router';
8
19
  import MusicPlayer from '../player/MusicPlayer.vue';
20
+ import FullscreenPlayer from '../player/FullscreenPlayer.vue';
21
+ import BottomSheet from '@martyrs/src/components/BottomSheet/BottomSheet.vue';
9
22
 
10
23
  // Import store modules
11
- import { state as playerState } from '../../store/player.js';
24
+ import { state as playerState, actions as playerActions } from '../../store/player.js';
25
+
26
+ const route = useRoute();
12
27
 
13
28
  // Current track from player store
14
29
  const currentTrack = computed(() => {
15
30
  return playerState.currentTrack;
16
31
  });
17
- </script>
32
+
33
+ // Fullscreen player state
34
+ const showFullPlayer = computed(() => {
35
+ return playerState.showFullPlayer;
36
+ });
37
+
38
+ // Close fullscreen player on route change
39
+ watch(() => route.path, () => {
40
+ if (playerState.showFullPlayer) {
41
+ playerActions.toggleFullPlayer();
42
+ }
43
+ });
44
+ </script>
45
+
46
+ <style>
47
+ /* Shared control button styles for both players */
48
+ .control-btn {
49
+ background: none;
50
+ border: none;
51
+ color: rgb(var(--grey));
52
+ cursor: pointer;
53
+ padding: 8px;
54
+ border-radius: 50%;
55
+ transition: all 0.2s ease;
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ }
60
+
61
+ .control-icon {
62
+ width: 16px;
63
+ height: 16px;
64
+ }
65
+
66
+ .control-btn.primary {
67
+ background: rgb(var(--second));
68
+ color: rgb(var(--white));
69
+ width: 32px;
70
+ height: 32px;
71
+ }
72
+
73
+ .play-icon {
74
+ width: 14px;
75
+ height: 14px;
76
+ fill: rgb(var(--white));
77
+ }
78
+
79
+ .control-btn.primary:hover {
80
+ background: rgb(var(--second));
81
+ transform: scale(1.06);
82
+ }
83
+
84
+ .control-btn.secondary:hover {
85
+ color: rgb(var(--white));
86
+ background: rgba(var(--black),0.1);
87
+ }
88
+
89
+ .control-btn.secondary:hover .control-icon {
90
+ fill: rgb(var(--white));
91
+ }
92
+
93
+ .control-btn.active {
94
+ color: rgb(var(--main));
95
+ background: rgba(var(--second),0.1);
96
+ }
97
+
98
+ .control-btn.active .control-icon {
99
+ fill: rgb(var(--main));
100
+ }
101
+
102
+ .control-btn.active:hover {
103
+ color: rgb(var(--main));
104
+ opacity: 0.8;
105
+ background: rgba(var(--second),0.2);
106
+ }
107
+ </style>
@@ -13,14 +13,14 @@
13
13
  </div>
14
14
 
15
15
  <!-- Album Content -->
16
- <div v-if="album" class="album-content cols-2-fit-content mobile:cols-1 gap-big">
16
+ <div v-if="album" class="album-content cols-2 mobile:cols-1 gap-big">
17
17
  <!-- Left Column - Cover & Stats -->
18
18
  <div class="pos-sticky pos-t-0 mobile:pos-relative album-cover-section">
19
19
  <!-- Cover -->
20
20
  <Media
21
21
  :url="album.coverArt || '/logo/logo-placeholder.jpg'"
22
22
  :alt="album.title"
23
- class="aspect-1x1 w-100 w-max-30r mn-b-small radius-medium o-hidden"
23
+ class="aspect-1x1 w-100 mn-b-small radius-medium o-hidden"
24
24
  />
25
25
  <!-- Quick Stats -->
26
26
  <div class="stats-grid grid cols-2 gap-small">
@@ -51,67 +51,7 @@
51
51
  <h1 class="h1 mn-b-medium">{{ album.title }}</h1>
52
52
 
53
53
  <!-- Action Buttons -->
54
- <div class="flex gap-small mn-b-medium">
55
- <Button
56
- @click="playAlbum"
57
- color="primary"
58
- size="medium"
59
- class="flex-1 t-white bg-black radius-thin flex-center gap-thin"
60
- >
61
- <IconPlay fill="rgb(var(--white))" class="i-medium" />
62
- Play All
63
- </Button>
64
-
65
- <Button
66
- @click="shufflePlay"
67
- color="primary"
68
- size="medium"
69
- class="flex-1 bg-light radius-thin flex-center gap-thin"
70
- >
71
- <IconShuffle class="i-medium" />
72
- Shuffle
73
- </Button>
74
-
75
- <Button
76
- @click="toggleFavorite"
77
- color="primary"
78
- size="medium"
79
- class="flex-1 bg-light radius-thin flex-center gap-thin"
80
- >
81
- <IconLike class="i-medium" :fill="isFavorite ? 'rgb(var(--main)':'rgb(var(--black)'" />
82
- {{isFavorite ? 'Liked' : 'Like'}}
83
- </Button>
84
-
85
- <Dropdown :label="{component: IconEllipsis, class: 'bg-light radius-thin pd-thin i-big' }" v-model="showDropdown" class="relative">
86
- <template #trigger>
87
- <Button color="transp" size="medium" class="w-3r h-3r radius-full">
88
- <IconEllipsis class="w-1-25r h-1-25r" />
89
- </Button>
90
- </template>
91
- <template #default>
92
- <div class="dropdown-menu bg-white pd-small radius-medium shadow-big mn-t-thin">
93
- <Button @click="addToQueue" color="transp" size="small" class="w-100 justify-start">
94
- Add to Queue
95
- </Button>
96
- <Button @click="copyLink" color="transp" size="small" class="w-100 justify-start">
97
- Copy Link
98
- </Button>
99
- <Button @click="addToPlaylist" color="transp" size="small" class="w-100 justify-start">
100
- Add to Playlist
101
- </Button>
102
- <template v-if="isOwner">
103
- <hr class="mn-v-thin border-dark-transp-10" />
104
- <Button @click="editAlbum" color="transp" size="small" class="w-100 justify-start">
105
- Edit Album
106
- </Button>
107
- <Button @click="deleteAlbum" color="danger" size="small" class="w-100 justify-start">
108
- Delete Album
109
- </Button>
110
- </template>
111
- </div>
112
- </template>
113
- </Dropdown>
114
- </div>
54
+ <ActionButtons :buttons="actionButtons" />
115
55
 
116
56
  <!-- Artists Cards -->
117
57
  <div class="artists-section mn-b-medium">
@@ -239,7 +179,7 @@
239
179
  <div class="flex flex-nowrap gap-small o-x-scroll overscroll-behavior-x-contain scroll-behavior-smooth scroll-snap-type-x-mandatory scroll-hide"
240
180
  >
241
181
  <li v-for="relatedAlbum in moreAlbums" :key="album._id" class="flex-none scroll-snap-align-start">
242
- <AlbumCard :album="relatedAlbum" class="w-min-15r transition-cubic-in-out" />
182
+ <AlbumCard :album="relatedAlbum" class="w-15r transition-cubic-in-out" />
243
183
  </li>
244
184
  </div>
245
185
  </section>
@@ -267,6 +207,7 @@ import IconDisc from '@martyrs/src/modules/icons/entities/IconMusic.vue';
267
207
  import IconVerified from '@martyrs/src/modules/icons/navigation/IconCheckmark.vue';
268
208
 
269
209
  // Components
210
+ import ActionButtons from '../blocks/ActionButtons.vue';
270
211
  import TrackListCard from '../cards/TrackListCard.vue';
271
212
  import AlbumCard from '../cards/AlbumCard.vue';
272
213
  import ArtistCardSmall from '../cards/ArtistCardSmall.vue';
@@ -285,7 +226,6 @@ const emits = defineEmits(['page-loading', 'page-loaded']);
285
226
  // State
286
227
  const hasLoaded = ref(false);
287
228
  const isFavorite = ref(false);
288
- const showDropdown = ref(false);
289
229
  const followedArtists = ref([]);
290
230
  const moreAlbums = ref([]);
291
231
 
@@ -307,6 +247,51 @@ const totalDuration = computed(() => {
307
247
  return formatDuration(totalSeconds);
308
248
  });
309
249
 
250
+ const actionButtons = computed(() => {
251
+ const buttons = [
252
+ {
253
+ type: 'button',
254
+ class: 't-white bg-black',
255
+ icon: IconPlay,
256
+ iconFill: 'rgb(var(--white))',
257
+ text: 'Play All',
258
+ action: playAlbum
259
+ },
260
+ {
261
+ type: 'button',
262
+ class: 'bg-light',
263
+ icon: IconShuffle,
264
+ text: 'Shuffle',
265
+ action: shufflePlay
266
+ },
267
+ {
268
+ type: 'button',
269
+ class: 'bg-light',
270
+ icon: IconLike,
271
+ iconFill: isFavorite.value ? 'rgb(var(--main))' : 'rgb(var(--black))',
272
+ text: isFavorite.value ? 'Liked' : 'Like',
273
+ action: toggleFavorite
274
+ },
275
+ {
276
+ type: 'dropdown',
277
+ items: [
278
+ { text: 'Add to Queue', action: addToQueue },
279
+ { text: 'Copy Link', action: copyLink },
280
+ { text: 'Add to Playlist', action: addToPlaylist }
281
+ ]
282
+ }
283
+ ];
284
+
285
+ if (isOwner.value) {
286
+ const items = buttons[3].items;
287
+ items.push({ separator: true });
288
+ items.push({ text: 'Edit Album', action: editAlbum });
289
+ items.push({ text: 'Delete Album', action: deleteAlbum, color: 'danger' });
290
+ }
291
+
292
+ return buttons;
293
+ });
294
+
310
295
  // Format helpers
311
296
  const formatDate = (dateString) => {
312
297
  if (!dateString) return 'Unknown';
@@ -373,10 +358,14 @@ const addToQueue = () => {
373
358
  albumTracks.value.forEach(track => {
374
359
  playerActions.addToQueue(track);
375
360
  });
376
- showDropdown.value = false;
377
361
  }
378
362
  };
379
363
 
364
+ const addToPlaylist = () => {
365
+ // TODO: Implement add to playlist
366
+ console.log('Add album to playlist');
367
+ };
368
+
380
369
  const editAlbum = () => {
381
370
  router.push({ name: 'album-edit', params: { url: album.value.url } });
382
371
  };
@@ -394,7 +383,6 @@ const deleteAlbum = async () => {
394
383
 
395
384
  const copyLink = () => {
396
385
  navigator.clipboard.writeText(window.location.href);
397
- showDropdown.value = false;
398
386
  };
399
387
 
400
388
  // Data fetching
@@ -41,7 +41,7 @@
41
41
  class="flex flex-nowrap gap-small o-x-scroll overscroll-behavior-x-contain scroll-behavior-smooth scroll-snap-type-x-mandatory scroll-hide"
42
42
  >
43
43
  <li v-for="album in items" :key="album._id" class="flex-none scroll-snap-align-start">
44
- <AlbumCard :album="album" class="w-min-15r transition-cubic-in-out" />
44
+ <AlbumCard :album="album" class="w-15r transition-cubic-in-out" />
45
45
  </li>
46
46
 
47
47
  </Feed>
@@ -79,9 +79,9 @@
79
79
  horizontal: true
80
80
  }"
81
81
  v-slot="{ items }"
82
- class="gap-zero"
82
+ class="gap-zero bg-light radius-medium o-hidden"
83
83
  >
84
- <div class="bg-light radius-medium o-hidden">
84
+ <div class="">
85
85
  <TrackListCard
86
86
  v-for="(track, index) in items"
87
87
  :key="track._id"
@@ -169,9 +169,7 @@
169
169
  v-slot="{ items }"
170
170
  class="cols-6 mobile:cols-3 gap-small"
171
171
  >
172
- <li class="flex-child-default w-100" v-for="artist in items" :key="artist._id">
173
- <ArtistCard :artist="artist" class="hover-scale-1 flex-child-default transition-cubic-in-out" />
174
- </li>
172
+ <ArtistCard v-for="artist in items" :key="artist._id" :artist="artist" class="hover-scale-1 transition-cubic-in-out" />
175
173
  </Feed>
176
174
  </section>
177
175
  </div>
@@ -7,9 +7,9 @@
7
7
  </div>
8
8
 
9
9
  <!-- Playlist Content -->
10
- <div v-if="playlist" class="playlist-content cols-2-fit-content mobile:cols-1 gap-big">
10
+ <div v-if="playlist" class="playlist-content cols-2 mobile:cols-1 gap-big">
11
11
  <!-- Left Column - Cover & Stats -->
12
- <div class="pos-sticky w-max-30r pos-t-0 mobile:pos-relative playlist-cover-section">
12
+ <div class="pos-sticky pos-t-0 mobile:pos-relative playlist-cover-section">
13
13
  <!-- Cover -->
14
14
  <div class="cover-container relative mn-b-medium radius-big overflow-hidden shadow-big">
15
15
  <Media
@@ -51,67 +51,7 @@
51
51
  <h1 class="h1 mn-b-medium">{{ playlist.title }}</h1>
52
52
 
53
53
  <!-- Action Buttons -->
54
- <div class="flex gap-small mn-b-medium">
55
- <Button
56
- @click="playPlaylist"
57
- color="primary"
58
- size="medium"
59
- class="flex-1 t-white bg-black radius-thin flex-center gap-thin"
60
- >
61
- <IconPlay fill="rgb(var(--white))" class="i-medium" />
62
- Play All
63
- </Button>
64
-
65
- <Button
66
- @click="shufflePlay"
67
- color="primary"
68
- size="medium"
69
- class="flex-1 bg-light radius-thin flex-center gap-thin"
70
- >
71
- <IconShuffle class="i-medium" />
72
- Shuffle
73
- </Button>
74
-
75
- <Button
76
- @click="toggleFollow"
77
- color="primary"
78
- size="medium"
79
- class="flex-1 bg-light radius-thin flex-center gap-thin"
80
- >
81
- {{isFollowing ? 'Follow' : 'Unfollow'}}
82
- </Button>
83
-
84
-
85
- <Dropdown :label="{component: IconEllipsis, class: 'bg-light radius-thin pd-thin i-big' }" v-model="showDropdown" class="relative">
86
- <template #trigger>
87
- <Button color="transp" size="medium" class="w-3r h-3r radius-full">
88
- <IconEllipsis class="w-1-25r h-1-25r" />
89
- </Button>
90
- </template>
91
- <template #default>
92
- <div class="dropdown-menu bg-white pd-small radius-medium shadow-big mn-t-thin">
93
- <Button @click="addToQueue" color="transp" size="small" class="w-100 t-nowrap justify-start">
94
- Add to Queue
95
- </Button>
96
- <Button @click="copyLink" color="transp" size="small" class="w-100 t-nowrap justify-start">
97
- Copy Link
98
- </Button>
99
- <template v-if="isOwner || isCollaborator">
100
- <hr class="mn-v-thin border-dark-transp-10" />
101
- <Button @click="editPlaylist" color="transp" size="small" class="w-100 t-nowrap justify-start">
102
- Edit Playlist
103
- </Button>
104
- <Button v-if="isOwner" @click="toggleCollaborative" color="transp" size="small" class="t-nowrap w-100 justify-start">
105
- {{ playlist.isCollaborative ? 'Make Private' : 'Make Collaborative' }}
106
- </Button>
107
- <Button v-if="isOwner" @click="deletePlaylist" color="danger" size="small" class="t-nowrap w-100 justify-start">
108
- Delete Playlist
109
- </Button>
110
- </template>
111
- </div>
112
- </template>
113
- </Dropdown>
114
- </div>
54
+ <ActionButtons :buttons="actionButtons" />
115
55
 
116
56
  <div class="artists-section mn-b-medium">
117
57
  <h3 class="t-medium mn-b-small">Created by</h3>
@@ -349,6 +289,7 @@ import IconRefresh from '@martyrs/src/modules/icons/navigation/IconRefresh.vue';
349
289
  import IconVerified from '@martyrs/src/modules/icons/navigation/IconCheckmark.vue';
350
290
 
351
291
  // Components
292
+ import ActionButtons from '../blocks/ActionButtons.vue';
352
293
  import TrackListCard from '../cards/TrackListCard.vue';
353
294
  import PlaylistCard from '../cards/PlaylistCard.vue';
354
295
  import PlaylistForm from '../forms/PlaylistForm.vue';
@@ -372,7 +313,6 @@ const emits = defineEmits(['page-loading', 'page-loaded']);
372
313
  // State
373
314
  const hasLoaded = ref(false);
374
315
  const isFollowing = ref(false);
375
- const showDropdown = ref(false);
376
316
  const showEditModal = ref(false);
377
317
  const showDeleteModal = ref(false);
378
318
  const followedUsers = ref([]);
@@ -407,6 +347,62 @@ const totalDuration = computed(() => {
407
347
  return formatDuration(totalSeconds);
408
348
  });
409
349
 
350
+ const actionButtons = computed(() => {
351
+ const buttons = [
352
+ {
353
+ type: 'button',
354
+ class: 't-white bg-black',
355
+ icon: IconPlay,
356
+ iconFill: 'rgb(var(--white))',
357
+ text: 'Play All',
358
+ action: playPlaylist
359
+ },
360
+ {
361
+ type: 'button',
362
+ class: 'bg-light',
363
+ icon: IconShuffle,
364
+ text: 'Shuffle',
365
+ action: shufflePlay
366
+ },
367
+ {
368
+ type: 'button',
369
+ class: 'bg-light',
370
+ icon: null,
371
+ text: isFollowing.value ? 'Unfollow' : 'Follow',
372
+ action: toggleFollow
373
+ },
374
+ {
375
+ type: 'dropdown',
376
+ items: [
377
+ { text: 'Add to Queue', action: addToQueue, class: 't-nowrap' },
378
+ { text: 'Copy Link', action: copyLink, class: 't-nowrap' }
379
+ ]
380
+ }
381
+ ];
382
+
383
+ if (isOwner.value || isCollaborator.value) {
384
+ const items = buttons[3].items;
385
+ items.push({ separator: true });
386
+ items.push({ text: 'Edit Playlist', action: editPlaylist, class: 't-nowrap' });
387
+
388
+ if (isOwner.value) {
389
+ items.push({
390
+ text: playlist.value?.isCollaborative ? 'Make Private' : 'Make Collaborative',
391
+ action: toggleCollaborative,
392
+ class: 't-nowrap'
393
+ });
394
+ items.push({
395
+ text: 'Delete Playlist',
396
+ action: deletePlaylist,
397
+ color: 'danger',
398
+ class: 't-nowrap'
399
+ });
400
+ }
401
+ }
402
+
403
+ return buttons;
404
+ });
405
+
410
406
  // Helper functions
411
407
  const getOwnerData = (playlist) => {
412
408
  if (!playlist) return null;
@@ -521,13 +517,11 @@ const addToQueue = () => {
521
517
  playlistTracks.value.forEach(track => {
522
518
  playerActions.addToQueue(track);
523
519
  });
524
- showDropdown.value = false;
525
520
  }
526
521
  };
527
522
 
528
523
  const editPlaylist = () => {
529
524
  showEditModal.value = true;
530
- showDropdown.value = false;
531
525
  };
532
526
 
533
527
  const toggleCollaborative = async () => {
@@ -536,9 +530,8 @@ const toggleCollaborative = async () => {
536
530
  _id: playlist.value._id,
537
531
  isCollaborative: !playlist.value.isCollaborative
538
532
  };
539
-
533
+
540
534
  await playlistsActions.updatePlaylist(updatedData);
541
- showDropdown.value = false;
542
535
  } catch (error) {
543
536
  console.error('Error updating playlist:', error);
544
537
  }
@@ -546,7 +539,6 @@ const toggleCollaborative = async () => {
546
539
 
547
540
  const deletePlaylist = () => {
548
541
  showDeleteModal.value = true;
549
- showDropdown.value = false;
550
542
  };
551
543
 
552
544
  const confirmDelete = async () => {
@@ -576,7 +568,6 @@ const removeTrack = async (trackId) => {
576
568
 
577
569
  const copyLink = () => {
578
570
  navigator.clipboard.writeText(window.location.href);
579
- showDropdown.value = false;
580
571
  };
581
572
 
582
573
  const handlePlaylistUpdated = () => {