ActivityItem.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <script setup lang="ts">
  2. import { useStore } from "vuex";
  3. import { ref, computed, onMounted } from "vue";
  4. import { formatDistance, parseISO } from "date-fns";
  5. const store = useStore();
  6. const props = defineProps({
  7. activity: {
  8. type: Object,
  9. default: () => {}
  10. }
  11. });
  12. const theme = ref("blue");
  13. const messageParts = computed(() => {
  14. const { message } = props.activity.payload;
  15. const messageParts = message.split(
  16. /((?:<youtubeId>.*<\/youtubeId>)|(?:<reportId>.*<\/reportId>)|(?:<playlistId>.*<\/playlistId>)|(?:<stationId>.*<\/stationId>))/g
  17. );
  18. return messageParts;
  19. });
  20. const messageStripped = computed(() => {
  21. let { message } = props.activity.payload;
  22. message = message.replace(/<reportId>(.*)<\/reportId>/g, "report");
  23. message = message.replace(/<youtubeId>(.*)<\/youtubeId>/g, "$1");
  24. message = message.replace(/<playlistId>(.*)<\/playlistId>/g, `$1`);
  25. message = message.replace(/<stationId>(.*)<\/stationId>/g, `$1`);
  26. return message;
  27. });
  28. function getMessagePartType(messagePart) {
  29. return messagePart.substring(1, messagePart.indexOf(">"));
  30. }
  31. function getMessagePartText(messagePart) {
  32. let message = messagePart;
  33. message = message.replace(/<reportId>(.*)<\/reportId>/g, "report");
  34. message = message.replace(/<youtubeId>(.*)<\/youtubeId>/g, "$1");
  35. message = message.replace(/<playlistId>(.*)<\/playlistId>/g, `$1`);
  36. message = message.replace(/<stationId>(.*)<\/stationId>/g, `$1`);
  37. return message;
  38. }
  39. function getIcon() {
  40. const icons = {
  41. /** User */
  42. user__joined: "account_circle",
  43. user__edit_bio: "create",
  44. user__edit_avatar: "insert_photo",
  45. user__edit_name: "create",
  46. user__edit_location: "place",
  47. user__toggle_nightmode: "nightlight_round",
  48. user__toggle_autoskip_disliked_songs: "thumb_down_alt",
  49. user__toggle_activity_watch: "visibility",
  50. /** Songs */
  51. song__report: "flag",
  52. song__like: "thumb_up_alt",
  53. song__dislike: "thumb_down_alt",
  54. song__unlike: "not_interested",
  55. song__undislike: "not_interested",
  56. /** Stations */
  57. station__favorite: "star",
  58. station__unfavorite: "star_border",
  59. station__create: "create",
  60. station__remove: "delete",
  61. station__edit_theme: "color_lens",
  62. station__edit_name: "create",
  63. station__edit_display_name: "create",
  64. station__edit_description: "create",
  65. station__edit_privacy: "security",
  66. station__edit_genres: "create",
  67. station__edit_blacklisted_genres: "create",
  68. /** Playlists */
  69. playlist__create: "create",
  70. playlist__remove: "delete",
  71. playlist__remove_song: "not_interested",
  72. playlist__remove_songs: "not_interested",
  73. playlist__add_song: "library_add",
  74. playlist__add_songs: "library_add",
  75. playlist__edit_privacy: "security",
  76. playlist__edit_display_name: "create",
  77. playlist__import_playlist: "publish"
  78. };
  79. return icons[props.activity.type];
  80. }
  81. function openModal(payload) {
  82. store.dispatch("modalVisibility/openModal", payload);
  83. }
  84. onMounted(() => {
  85. if (props.activity.type === "station__edit_theme")
  86. theme.value = props.activity.payload.message.replace(
  87. /to\s(\w+)/g,
  88. "$1"
  89. );
  90. });
  91. </script>
  92. <template>
  93. <div class="item activity-item universal-item">
  94. <div :class="[theme, 'thumbnail']">
  95. <img
  96. v-if="activity.payload.thumbnail"
  97. :src="activity.payload.thumbnail"
  98. onerror="this.src='/assets/notes.png'"
  99. :alt="messageStripped"
  100. />
  101. <i class="material-icons activity-type-icon">{{ getIcon() }}</i>
  102. </div>
  103. <div class="left-part">
  104. <p :title="messageStripped" class="item-title">
  105. <span v-for="messagePart in messageParts" :key="messagePart">
  106. <span
  107. v-if="getMessagePartType(messagePart) === 'youtubeId'"
  108. >{{ getMessagePartText(messagePart) }}</span
  109. >
  110. <a
  111. v-else-if="
  112. getMessagePartType(messagePart) === 'reportId'
  113. "
  114. class="activity-item-link"
  115. @click="
  116. openModal({
  117. modal: 'viewReport',
  118. data: { reportId: activity.payload.reportId }
  119. })
  120. "
  121. >report</a
  122. >
  123. <a
  124. v-else-if="
  125. getMessagePartType(messagePart) === 'playlistId'
  126. "
  127. class="activity-item-link"
  128. @click="
  129. openModal({
  130. modal: 'editPlaylist',
  131. data: {
  132. playlistId: activity.payload.playlistId
  133. }
  134. })
  135. "
  136. >{{ getMessagePartText(messagePart) }}
  137. </a>
  138. <router-link
  139. v-else-if="
  140. getMessagePartType(messagePart) === 'stationId'
  141. "
  142. class="activity-item-link"
  143. :to="{
  144. name: 'station',
  145. params: { id: activity.payload.stationId }
  146. }"
  147. >{{ getMessagePartText(messagePart) }}</router-link
  148. >
  149. <span v-else>
  150. {{ messagePart }}
  151. </span>
  152. </span>
  153. </p>
  154. <p class="item-description">
  155. {{
  156. formatDistance(parseISO(activity.createdAt), new Date(), {
  157. addSuffix: true
  158. })
  159. }}
  160. </p>
  161. </div>
  162. <div class="universal-item-actions">
  163. <slot name="actions" />
  164. </div>
  165. </div>
  166. </template>
  167. <style lang="less">
  168. .activity-item-link {
  169. color: var(--primary-color) !important;
  170. &:hover {
  171. border-color: var(--light-grey-2) !important;
  172. }
  173. }
  174. </style>
  175. <style lang="less" scoped>
  176. .activity-item {
  177. height: 72px;
  178. border: 0.5px var(--light-grey-3) solid;
  179. border-radius: @border-radius;
  180. padding: 0;
  181. .thumbnail {
  182. position: relative;
  183. display: flex;
  184. align-items: center;
  185. justify-content: center;
  186. min-width: 70.5px;
  187. max-width: 70.5px;
  188. height: 70.5px;
  189. margin-left: 0px;
  190. &.red {
  191. background-color: var(--dark-red);
  192. }
  193. &.green {
  194. background-color: var(--green);
  195. }
  196. &.blue {
  197. background-color: var(--primary-color);
  198. }
  199. &.orange {
  200. background-color: var(--orange);
  201. }
  202. &.yellow {
  203. background-color: var(--yellow);
  204. }
  205. &.purple {
  206. background-color: var(--purple);
  207. }
  208. &.teal {
  209. background-color: var(--teal);
  210. }
  211. .activity-type-icon {
  212. position: absolute;
  213. color: var(--light-grey);
  214. font-size: 25px;
  215. background-color: rgba(0, 0, 0, 0.8);
  216. padding: 5px;
  217. border-radius: 100%;
  218. }
  219. }
  220. .left-part {
  221. flex: 1;
  222. padding: 12px;
  223. min-width: 0;
  224. .item-title {
  225. margin: 0;
  226. font-size: 16px;
  227. }
  228. }
  229. .universal-item-actions {
  230. right: 10px;
  231. position: sticky;
  232. a {
  233. border-bottom: 0;
  234. }
  235. }
  236. }
  237. </style>