ActivityItem.vue 6.0 KB

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