@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.
Files changed (122) hide show
  1. package/dist/{main-B9o1iBAZ.js → main-BFvlam0J.js} +9 -6
  2. package/dist/martyrs/dist/main-BFvlam0J.js +943 -0
  3. package/dist/martyrs/dist/main-BFvlam0J.js.map +1 -0
  4. package/dist/martyrs/dist/web-CH5wzMHy.js +55 -0
  5. package/dist/martyrs/dist/web-CH5wzMHy.js.map +1 -0
  6. package/dist/martyrs/src/components/BottomSheet/BottomSheet.vue.js +96 -0
  7. package/dist/martyrs/src/components/BottomSheet/BottomSheet.vue.js.map +1 -0
  8. package/dist/martyrs/src/components/Feed/Carousel.vue.js +2 -2
  9. package/dist/martyrs/src/components/Feed/Carousel.vue.js.map +1 -1
  10. package/dist/martyrs/src/components/Feed/Feed.vue.js +6 -3
  11. package/dist/martyrs/src/components/Feed/Feed.vue.js.map +1 -1
  12. package/dist/martyrs/src/components/Menu/{Menu.vue2.js → Menu.vue.js} +2 -2
  13. package/dist/martyrs/src/components/Menu/Menu.vue.js.map +1 -0
  14. package/dist/martyrs/src/modules/auth/views/components/pages/Profile.vue.js +1 -1
  15. package/dist/martyrs/src/modules/community/components/sections/HotPosts.vue.js.map +1 -1
  16. package/dist/martyrs/src/modules/core/views/components/layouts/App.vue.js +1 -1
  17. package/dist/martyrs/src/modules/core/views/components/layouts/Client.vue.js +1 -0
  18. package/dist/martyrs/src/modules/core/views/components/layouts/Client.vue.js.map +1 -1
  19. package/dist/martyrs/src/modules/core/views/components/sections/Walkthrough.vue.js +1 -1
  20. package/dist/martyrs/src/modules/events/components/elements/ButtonCheck.vue.js +1 -1
  21. package/dist/martyrs/src/modules/events/components/sections/EventsHot.vue.js +17 -1
  22. package/dist/martyrs/src/modules/events/components/sections/EventsHot.vue.js.map +1 -1
  23. package/dist/martyrs/src/modules/events/events.client.js +15 -12
  24. package/dist/martyrs/src/modules/events/events.client.js.map +1 -1
  25. package/dist/martyrs/src/modules/music/components/blocks/ActionButtons.vue.js +95 -0
  26. package/dist/martyrs/src/modules/music/components/blocks/ActionButtons.vue.js.map +1 -0
  27. package/dist/martyrs/src/modules/music/components/cards/AlbumCard.vue.js +5 -2
  28. package/dist/martyrs/src/modules/music/components/cards/AlbumCard.vue.js.map +1 -1
  29. package/dist/martyrs/src/modules/music/components/cards/ArtistCardSmall.vue.js +24 -24
  30. package/dist/martyrs/src/modules/music/components/cards/ArtistCardSmall.vue.js.map +1 -1
  31. package/dist/martyrs/src/modules/music/components/cards/TrackListCard.vue.js +3 -3
  32. package/dist/martyrs/src/modules/music/components/cards/TrackListCard.vue.js.map +1 -1
  33. package/dist/martyrs/src/modules/music/components/layouts/MusicBottomPlayer.vue.js +31 -6
  34. package/dist/martyrs/src/modules/music/components/layouts/MusicBottomPlayer.vue.js.map +1 -1
  35. package/dist/martyrs/src/modules/music/components/pages/Album.vue.js +120 -205
  36. package/dist/martyrs/src/modules/music/components/pages/Album.vue.js.map +1 -1
  37. package/dist/martyrs/src/modules/music/components/pages/MusicHome.vue.js +9 -13
  38. package/dist/martyrs/src/modules/music/components/pages/MusicHome.vue.js.map +1 -1
  39. package/dist/martyrs/src/modules/music/components/pages/Playlist.vue.js +166 -245
  40. package/dist/martyrs/src/modules/music/components/pages/Playlist.vue.js.map +1 -1
  41. package/dist/martyrs/src/modules/music/components/pages/Track.vue.js +135 -220
  42. package/dist/martyrs/src/modules/music/components/pages/Track.vue.js.map +1 -1
  43. package/dist/martyrs/src/modules/music/components/player/FullscreenPlayer.vue.js +171 -0
  44. package/dist/martyrs/src/modules/music/components/player/FullscreenPlayer.vue.js.map +1 -0
  45. package/dist/martyrs/src/modules/music/components/player/MusicPlayer.vue.js +31 -153
  46. package/dist/martyrs/src/modules/music/components/player/MusicPlayer.vue.js.map +1 -1
  47. package/dist/martyrs/src/modules/music/components/player/PlayerControls.vue.js +96 -0
  48. package/dist/martyrs/src/modules/music/components/player/PlayerControls.vue.js.map +1 -0
  49. package/dist/martyrs/src/modules/music/components/player/VolumeControl.vue.js +55 -27
  50. package/dist/martyrs/src/modules/music/components/player/VolumeControl.vue.js.map +1 -1
  51. package/dist/martyrs/src/modules/music/components/player/tonar.png.js +5 -0
  52. package/dist/martyrs/src/modules/music/components/player/tonar.png.js.map +1 -0
  53. package/dist/martyrs/src/modules/music/store/albums.js +8 -2
  54. package/dist/martyrs/src/modules/music/store/albums.js.map +1 -1
  55. package/dist/martyrs/src/modules/music/store/player.js +83 -65
  56. package/dist/martyrs/src/modules/music/store/player.js.map +1 -1
  57. package/dist/martyrs/src/modules/music/store/tracks.js +4 -13
  58. package/dist/martyrs/src/modules/music/store/tracks.js.map +1 -1
  59. package/dist/martyrs/src/modules/notifications/notifications.client.js +2 -2
  60. package/dist/martyrs/src/modules/organizations/components/pages/Organization.vue.js +1 -1
  61. package/dist/martyrs/src/modules/organizations/components/pages/OrganizationBackoffice.vue.js +1 -1
  62. package/dist/martyrs.css +1 -1
  63. package/dist/martyrs.es.js +1 -1
  64. package/dist/music.server.js +33 -6
  65. 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
  66. 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
  67. 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
  68. 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
  69. 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
  70. 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
  71. package/dist/node_modules/.pnpm/{@capacitor_core@7.0.1 → @capacitor_core@7.4.4}/node_modules/@capacitor/core/dist/index.js +2 -1
  72. package/dist/node_modules/.pnpm/@capacitor_core@7.4.4/node_modules/@capacitor/core/dist/index.js.map +1 -0
  73. 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
  74. 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
  75. 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
  76. 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
  77. 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
  78. 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
  79. 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
  80. 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
  81. 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
  82. 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
  83. 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
  84. 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
  85. 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
  86. package/dist/style.css +221 -145
  87. package/dist/{web-BF3ijvEr.js → web-CH5wzMHy.js} +1 -1
  88. package/package.json +1 -1
  89. package/src/components/BottomSheet/BottomSheet.vue +4 -4
  90. package/src/components/Feed/Carousel.vue +1 -1
  91. package/src/components/Feed/Feed.vue +3 -3
  92. package/src/modules/LAYOUT.MD +767 -0
  93. package/src/modules/community/components/sections/HotPosts.vue +8 -4
  94. package/src/modules/core/views/components/layouts/Client.vue +1 -1
  95. package/src/modules/events/components/sections/EventsHot.vue +21 -4
  96. package/src/modules/events/events.client.js +3 -0
  97. package/src/modules/music/components/blocks/ActionButtons.vue +74 -0
  98. package/src/modules/music/components/cards/AlbumCard.vue +1 -1
  99. package/src/modules/music/components/cards/ArtistCardSmall.vue +8 -6
  100. package/src/modules/music/components/cards/TrackListCard.vue +6 -6
  101. package/src/modules/music/components/layouts/MusicBottomPlayer.vue +94 -4
  102. package/src/modules/music/components/pages/Album.vue +55 -67
  103. package/src/modules/music/components/pages/MusicHome.vue +4 -6
  104. package/src/modules/music/components/pages/Playlist.vue +61 -70
  105. package/src/modules/music/components/pages/Track.vue +54 -71
  106. package/src/modules/music/components/player/FullscreenPlayer.vue +248 -0
  107. package/src/modules/music/components/player/MusicPlayer.vue +21 -216
  108. package/src/modules/music/components/player/PlayerControls.vue +112 -0
  109. package/src/modules/music/components/player/Visualizer.vue +151 -0
  110. package/src/modules/music/components/player/VolumeControl.vue +75 -23
  111. package/src/modules/music/components/player/tonar.png +0 -0
  112. package/src/modules/music/routes/albums.routes.js +13 -12
  113. package/src/modules/music/routes/tracks.routes.js +39 -0
  114. package/src/modules/music/store/albums.js +10 -2
  115. package/src/modules/music/store/player.js +101 -89
  116. package/src/modules/music/store/tracks.js +5 -21
  117. package/src/styles/config.scss +6 -6
  118. package/dist/martyrs/src/components/Menu/Menu.vue2.js.map +0 -1
  119. 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
  120. 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
  121. package/dist/node_modules/.pnpm/@capacitor_core@7.0.1/node_modules/@capacitor/core/dist/index.js.map +0 -1
  122. /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
@@ -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 = () => {
@@ -13,7 +13,7 @@
13
13
  </div>
14
14
 
15
15
  <!-- Track Content -->
16
- <div v-if="track" class="track-content cols-2-fit-content mobile:cols-1 gap-big">
16
+ <div v-if="track" class="track-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 track-cover-section">
19
19
  <!-- Cover with Play Overlay -->
@@ -62,78 +62,18 @@
62
62
  <!-- Track Title -->
63
63
  <h1 class="h1 mn-b-medium">{{ track.title }}</h1>
64
64
  <!-- Action Buttons -->
65
- <div class="flex gap-small mn-b-medium">
66
- <Button
67
- @click="playTrack"
68
- color="primary"
69
- size="medium"
70
- class="flex-1 t-white bg-black radius-thin flex-center gap-thin"
71
- >
72
- <IconPlay v-if="!isPlaying" fill="rgb(var(--white))" class="i-medium" />
73
- <IconPause v-else fill="rgb(var(--white))" class="i-medium" />
74
- {{ !isPlaying ? 'Play' : 'Pause'}}
75
- </Button>
76
-
77
- <Button
78
- @click="addToQueue"
79
- color="primary"
80
- size="medium"
81
- class="flex-1 bg-light radius-thin flex-center gap-thin"
82
- >
83
- <IconAdd class="i-medium" />
84
- Add to Queue
85
- </Button>
86
-
87
- <Button
88
- @click="toggleFavorite"
89
- color="primary"
90
- size="medium"
91
- class="flex-1 bg-light radius-thin flex-center gap-thin"
92
- >
93
- <IconLike class="i-medium" :fill="isFavorite ? 'rgb(var(--main)':'rgb(var(--black)'" />
94
- {{isFavorite ? 'Liked' : 'Like'}}
95
- </Button>
96
-
97
- <Dropdown :label="{component: IconEllipsis, class: 'bg-light radius-thin pd-thin i-big' }" v-model="showDropdown" class="relative">
98
- <template #trigger>
99
- <Button color="transp" size="medium" class="w-3r h-3r radius-full">
100
- <IconEllipsis class="w-1-25r h-1-25r" />
101
- </Button>
102
- </template>
103
- <template #default>
104
- <div class="dropdown-menu bg-dark pd-small radius-medium shadow-big mn-t-thin">
105
- <Button @click="showAddToPlaylistModal = true" color="transp" size="small" class="w-100 justify-start">
106
- Add to Playlist
107
- </Button>
108
- <Button @click="copyLink" color="transp" size="small" class="w-100 justify-start">
109
- Copy Link
110
- </Button>
111
- <template v-if="isOwner">
112
- <hr class="mn-v-thin border-dark-transp-10" />
113
- <Button @click="editTrack" color="transp" size="small" class="w-100 justify-start">
114
- Edit Track
115
- </Button>
116
- <Button @click="deleteTrack" color="danger" size="small" class="w-100 justify-start">
117
- Delete Track
118
- </Button>
119
- </template>
120
- </div>
121
- </template>
122
- </Dropdown>
123
- </div>
65
+ <ActionButtons :buttons="actionButtons" />
124
66
 
125
67
  <!-- Artist Card -->
126
68
  <div class="artists-section mn-b-medium">
127
69
  <h3 class="t-medium mn-b-small" v-if="track.artist">Artist</h3>
128
- <div class="flex flex-column gap-small">
129
- <ArtistCardSmall
130
- :key="track.artist._id"
131
- :artist="track.artist"
132
- :is-following="isFollowingArtist"
133
- :show-follow-button="!isOwner"
134
- @toggle-follow="toggleFollowArtist"
135
- />
136
- </div>
70
+ <ArtistCardSmall
71
+ :key="track.artist._id"
72
+ :artist="track.artist"
73
+ :is-following="isFollowingArtist"
74
+ :show-follow-button="!isOwner"
75
+ @toggle-follow="toggleFollowArtist"
76
+ />
137
77
  </div>
138
78
 
139
79
  <!-- Metadata Cards -->
@@ -302,6 +242,7 @@ import IconEye from '@martyrs/src/modules/icons/actions/IconShow.vue';
302
242
  import IconVerified from '@martyrs/src/modules/icons/navigation/IconCheckmark.vue';
303
243
 
304
244
  // Components
245
+ import ActionButtons from '../blocks/ActionButtons.vue';
305
246
  import TrackListCard from '../cards/TrackListCard.vue';
306
247
  import ArtistCardSmall from '../cards/ArtistCardSmall.vue';
307
248
  // import PlaylistSelector from '../forms/PlaylistSelector.vue';
@@ -321,7 +262,6 @@ const emits = defineEmits(['page-loading', 'page-loaded']);
321
262
 
322
263
  // State
323
264
  const hasLoaded = ref(false);
324
- const showDropdown = ref(false);
325
265
  const showAddToPlaylistModal = ref(false);
326
266
  const isFavorite = ref(false);
327
267
  const isFollowingArtist = ref(false);
@@ -338,6 +278,50 @@ const isOwner = computed(() => {
338
278
  return track.value?.owner?.target === authState.user?._id;
339
279
  });
340
280
 
281
+ const actionButtons = computed(() => {
282
+ const buttons = [
283
+ {
284
+ type: 'button',
285
+ class: 't-white bg-black',
286
+ icon: isPlaying.value ? IconPause : IconPlay,
287
+ iconFill: 'rgb(var(--white))',
288
+ text: isPlaying.value ? 'Pause' : 'Play',
289
+ action: playTrack
290
+ },
291
+ {
292
+ type: 'button',
293
+ class: 'bg-light',
294
+ icon: IconAdd,
295
+ text: 'Add to Queue',
296
+ action: addToQueue
297
+ },
298
+ {
299
+ type: 'button',
300
+ class: 'bg-light',
301
+ icon: IconLike,
302
+ iconFill: isFavorite.value ? 'rgb(var(--main))' : 'rgb(var(--black))',
303
+ text: isFavorite.value ? 'Liked' : 'Like',
304
+ action: toggleFavorite
305
+ },
306
+ {
307
+ type: 'dropdown',
308
+ items: [
309
+ { text: 'Add to Playlist', action: () => showAddToPlaylistModal.value = true },
310
+ { text: 'Copy Link', action: copyLink }
311
+ ]
312
+ }
313
+ ];
314
+
315
+ if (isOwner.value) {
316
+ const items = buttons[3].items;
317
+ items.push({ separator: true });
318
+ items.push({ text: 'Edit Track', action: editTrack });
319
+ items.push({ text: 'Delete Track', action: deleteTrack, color: 'danger' });
320
+ }
321
+
322
+ return buttons;
323
+ });
324
+
341
325
  // Format helpers
342
326
  const formatDuration = (seconds) => {
343
327
  if (!seconds) return '0:00';
@@ -410,7 +394,6 @@ const deleteTrack = async () => {
410
394
 
411
395
  const copyLink = () => {
412
396
  navigator.clipboard.writeText(window.location.href);
413
- showDropdown.value = false;
414
397
  };
415
398
 
416
399
  // Data fetching
@@ -0,0 +1,248 @@
1
+ <template>
2
+ <section
3
+ class="o-y-scroll o-x-hidden bg-light pd-small pos-relative z-index-2 t-center t-black w-100 h-100"
4
+ >
5
+
6
+ <div
7
+ class="pos-relative gap-small flex-nowrap flex-column flex-center flex z-index-1"
8
+ >
9
+ <!-- COVER -->
10
+ <div class="mn-b-thin h-max-30r br-2px br-solid br-black-transp-10 radius-extra w-100 pos-relative aspect-1x1 flex-center flex "
11
+ style="
12
+ background: linear-gradient(180deg, #BDBDBD 0%, #D9D9D9 79.69%, #A5A5A5 100%);
13
+ box-shadow: 0 2px 0px 2px #aaaaaa;
14
+ "
15
+ >
16
+
17
+ <div class="pos-relative">
18
+ <div class="radioTonarWrapper" :class="{'activeTonar': playerState.isPlaying}">
19
+ <img
20
+ class="radioTonar z-index-2"
21
+ src="./tonar.png"
22
+ :class="{'playingTonar': playerState.isPlaying}"
23
+ >
24
+ </div>
25
+ <div
26
+ ref="disk"
27
+ :class="['radius-100 rotate-gradient flex-center flex']"
28
+ :style="{
29
+ transform: `rotate(${playerState.rotationAngle}deg)`,
30
+ width: '24rem',
31
+ height: '24rem',
32
+ border: '3px solid var(--black)',
33
+ 'box-shadow': 'rgba(0,0,0,0.5) 0 0 64px 0px'
34
+ }"
35
+ class="pos-relative z-index-1"
36
+ >
37
+ <div
38
+ v-for="i in 11"
39
+ :key="i"
40
+ :style="{
41
+ border: '1px solid rgba(0,0,0,0.25)',
42
+ position: 'absolute',
43
+ width: 23 - i * 1 + 'rem',
44
+ height: 23 - i * 1 + 'rem',
45
+ 'border-radius': '100%',
46
+ 'box-shadow': 'rgb(47 47 47 / 18%) 0px 0px 5px 10px'
47
+ }"
48
+ >
49
+ </div>
50
+
51
+ <div
52
+ :style="{
53
+ width: '10rem',
54
+ height: '10rem',
55
+ backgroundImage: `url('${playerState.currentTrack?.coverUrl || playerState.currentTrack?.album?.coverArt}')`,
56
+ backgroundPosition: 'center center',
57
+ backgroundSize: '106%',
58
+ }"
59
+ class="radius-extra br-solid br-black-transp-10 br-1px"
60
+ >
61
+ </div>
62
+ </div>
63
+
64
+ </div>
65
+
66
+ </div>
67
+
68
+ <!-- NOW PLAYING -->
69
+ <div
70
+ class="pd-thin bg-white radius-medium o-hidden w-100 pos-relative"
71
+ >
72
+ <p class="mn-l-auto mn-b-thin mn-r-auto radius-big pd-thin bg-second w-min">NOW&nbsp;PLAYING</p>
73
+ <!-- SONG -->
74
+ <Marquee
75
+ v-if="playerState.currentTrack"
76
+ class="h2 uppercase mn-b-thin"
77
+ :gradient="true"
78
+ :gradientColor="'rgb(var(--white))'"
79
+ :clone="true"
80
+ gradient-length="10%"
81
+ >
82
+ {{ playerState.currentTrack.title + '\xa0\xa0'}}
83
+ </Marquee>
84
+
85
+ <!-- AUTHOR -->
86
+ <p
87
+ v-if="playerState.currentTrack"
88
+ class="h3 uppercase t-transp"
89
+ >
90
+ {{ playerState.currentTrack.artist?.name || 'Unknown Artist' }}
91
+ </p>
92
+ </div>
93
+
94
+ <!-- LIKES -->
95
+ <div
96
+ class="pd-thin bg-white radius-medium w-100 pos-relative flex flex-v-center gap-thin"
97
+ >
98
+ <div v-if="authState.user" class="flex-nowrap flex gap-thin flex-v-center">
99
+ <IconLike
100
+ @click="toggleLike()"
101
+ :isLiked="isLiked"
102
+ fill="rgb(var(--black))"
103
+ class="i-medium cursor-pointer"
104
+ :class="{'no-events': isLoading}"
105
+ />
106
+ <p class="t-semi h4">{{ isLiked ? 'Liked' : 'Like' }}</p>
107
+ </div>
108
+
109
+ <p class="t-semi h4" :class="{'mn-l-auto': authState.user}">{{ likesCount }} {{ likesCount === 1 ? 'person' : 'people' }} liked</p>
110
+ </div>
111
+
112
+ <!-- CONTROLS -->
113
+ <div
114
+ class="gap-thin flex-center flex-nowrap flex pd-small bg-white radius-medium o-hidden w-100 pos-relative"
115
+ >
116
+ <PlayerControls />
117
+ </div>
118
+
119
+ <!-- VOLUME -->
120
+ <div class="pd-thin bg-white radius-medium w-100 pos-relative">
121
+ <VolumeControl />
122
+ </div>
123
+
124
+
125
+ <!-- QUEUE LIST -->
126
+ <div
127
+ v-if="playerState.queue.length > 0"
128
+ class="mn-b-small bg-white radius-medium o-hidden w-100 pos-relative"
129
+ >
130
+
131
+ <div class="pd-regular w-100 gap-thin flex-nowrap flex flex-v-center">
132
+ <IconEvents :fill="'rgb(var(--black))'" class="t-transp i-medium"/>
133
+ <p class="w-100 t-transp t-left">QUEUE</p>
134
+ </div>
135
+
136
+ <TrackListCard
137
+ v-for="(track, index) in playerState.queue.slice(0, 10)"
138
+ :key="track._id || index"
139
+ :track="track"
140
+ :index="index"
141
+ :showCover="true"
142
+ :showAlbum="false"
143
+ />
144
+
145
+ </div>
146
+
147
+ </div>
148
+
149
+ </section>
150
+ </template>
151
+
152
+ <script setup>
153
+ import { ref, computed, onMounted } from 'vue';
154
+
155
+ import { Text, Tooltip, Marquee } from '@ozdao/martyrs';
156
+ import IconLike from '@martyrs/src/modules/icons/navigation/IconLike.vue';
157
+ import IconEvents from '@martyrs/src/modules/icons/entities/IconEvents.vue';
158
+
159
+ import TrackListCard from '../cards/TrackListCard.vue';
160
+ import VolumeControl from './VolumeControl.vue';
161
+ import PlayerControls from './PlayerControls.vue';
162
+ import { state as playerState, actions as playerActions } from '../../store/player.js';
163
+ import { state as authState } from '@martyrs/src/modules/auth/views/store/auth.js';
164
+
165
+ const isLiked = ref(false);
166
+ const isLoading = ref(false);
167
+ const likesCount = ref(0);
168
+
169
+ async function toggleLike() {
170
+ // TODO: Implement likes integration with community module
171
+ isLiked.value = !isLiked.value;
172
+ likesCount.value += isLiked.value ? 1 : -1;
173
+ }
174
+
175
+ onMounted(() => {
176
+ if ('mediaSession' in navigator) {
177
+ navigator.mediaSession.setActionHandler('pause', () => playerActions.togglePlay());
178
+ navigator.mediaSession.setActionHandler('play', () => playerActions.togglePlay());
179
+ navigator.mediaSession.setActionHandler('stop', () => playerActions.togglePlay());
180
+ navigator.mediaSession.setActionHandler('previoustrack', () => playerActions.playPrevious());
181
+ navigator.mediaSession.setActionHandler('nexttrack', () => playerActions.playNext());
182
+ }
183
+ });
184
+
185
+ </script>
186
+
187
+
188
+ <style lang="scss" scoped>
189
+ .radioTonarWrapper {
190
+ width: 15rem;
191
+ position: absolute;
192
+ z-index: 30;
193
+ transform: rotate(253deg);
194
+ transform-origin: 75% center;
195
+ top: 0;
196
+ right: -2.5rem;
197
+ transition: transform 1s ease-in-out;
198
+ }
199
+
200
+ .radioTonar {
201
+ width: 15rem;
202
+ transform-origin: 75% center;
203
+ }
204
+
205
+ .activeTonar {
206
+ transform: rotate(285deg);
207
+ }
208
+
209
+ .playingTonar {
210
+ animation: playingTonar 2s ease-in-out infinite;
211
+ }
212
+
213
+ @keyframes playingTonar {
214
+ 0%, 100% {
215
+ transform: rotate(0deg);
216
+ }
217
+ 25% {
218
+ transform: rotate(2deg);
219
+ }
220
+ 50% {
221
+ transform: rotate(-2deg);
222
+ }
223
+ }
224
+
225
+ @property --a {
226
+ syntax: '<angle>';
227
+ inherits: false;
228
+ initial-value: 0deg;
229
+ }
230
+
231
+ @property --b {
232
+ syntax: '<angle>';
233
+ inherits: false;
234
+ initial-value: 0deg;
235
+ }
236
+
237
+ .rotate-gradient {
238
+ --a: 0deg;
239
+ --b: 0deg;
240
+ transition: --a 0.5s, --b 0.5s;
241
+
242
+ background:
243
+ conic-gradient(from var(--a), rgba(255, 255, 255, 0.1), rgba(0, 0, 0, 0.33), rgba(255, 255, 255, 0.1)),
244
+ linear-gradient(180deg, rgba(0, 0, 0, 1) 0%, rgba(255, 255, 255, 0.1) 50%, rgba(0, 0, 0, 1) 100%),
245
+ linear-gradient(180deg, #000 0%, rgba(10, 10, 10, 0.00) 100%),
246
+ #000;
247
+ }
248
+ </style>