UserItem.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <script lang="ts" setup>
  2. import { computed, defineAsyncComponent } from "vue";
  3. import { useRouter } from "vue-router";
  4. import Toast from "toasters";
  5. import { User } from "@/types/user";
  6. import { useUserAuthStore } from "@/stores/userAuth";
  7. import { Station } from "@/types/station";
  8. import { useWebsocketsStore } from "@/stores/websockets";
  9. import DropdownListItem from "@/pages/NewStation/Components/DropdownListItem.vue";
  10. const Button = defineAsyncComponent(
  11. () => import("@/pages/NewStation/Components/Button.vue")
  12. );
  13. const DropdownList = defineAsyncComponent(
  14. () => import("@/pages/NewStation/Components/DropdownList.vue")
  15. );
  16. const ProfilePicture = defineAsyncComponent(
  17. () => import("@/pages/NewStation/Components/ProfilePicture.vue")
  18. );
  19. const props = defineProps<{
  20. station: Station;
  21. user: User;
  22. }>();
  23. const { hasPermissionForStation } = useUserAuthStore();
  24. const { socket } = useWebsocketsStore();
  25. const router = useRouter();
  26. const name = computed(() => props.user.name ?? props.user.username);
  27. const isOwner = computed(() => props.station.owner === props.user._id);
  28. const isDj = computed(
  29. () => !!props.station.djs.find(dj => dj._id === props.user._id)
  30. );
  31. const status = computed(() => {
  32. if (!props.user.state) return null;
  33. switch (props.user.state) {
  34. case "participate":
  35. return "Participating";
  36. case "local_paused":
  37. return "Paused";
  38. case "muted":
  39. return "Muted";
  40. case "playing":
  41. return "Listening";
  42. case "unavailable":
  43. return "Unavailable";
  44. case "buffering":
  45. return "Loading";
  46. case "station_paused":
  47. case "no_song":
  48. return "Waiting";
  49. case "unknown":
  50. default:
  51. return "Unknown";
  52. }
  53. });
  54. const promoteToDj = () => {
  55. socket.dispatch(
  56. "stations.addDj",
  57. props.station._id,
  58. props.user._id,
  59. res => {
  60. new Toast(res.message);
  61. }
  62. );
  63. };
  64. const demoteFromDj = () => {
  65. socket.dispatch(
  66. "stations.removeDj",
  67. props.station._id,
  68. props.user._id,
  69. res => {
  70. new Toast(res.message);
  71. }
  72. );
  73. };
  74. const viewProfile = () => {
  75. router.push({
  76. name: "profile",
  77. params: { username: props.user.username }
  78. });
  79. };
  80. </script>
  81. <template>
  82. <div class="user-item">
  83. <ProfilePicture :avatar="user.avatar" :name="name" />
  84. <div class="user-item__content">
  85. <p class="user-item__name">
  86. <span
  87. v-if="isOwner"
  88. class="material-icons user-item__rank"
  89. title="Station Owner"
  90. >
  91. local_police
  92. </span>
  93. <span
  94. v-else-if="isDj"
  95. class="material-icons user-item__rank"
  96. title="Station DJ"
  97. >
  98. shield
  99. </span>
  100. <span :title="name">{{ name }}</span>
  101. </p>
  102. <p v-if="status" class="user-item__status" :title="status">
  103. {{ status }}
  104. </p>
  105. </div>
  106. <DropdownList>
  107. <Button icon="more_horiz" square inverse title="Actions" />
  108. <template #options>
  109. <DropdownListItem
  110. v-if="
  111. hasPermissionForStation(
  112. station._id,
  113. 'stations.djs.add'
  114. ) &&
  115. !isOwner &&
  116. !isDj
  117. "
  118. icon="add_moderator"
  119. label="Promote to DJ"
  120. @click="promoteToDj"
  121. />
  122. <DropdownListItem
  123. v-if="
  124. hasPermissionForStation(
  125. station._id,
  126. 'stations.djs.remove'
  127. ) &&
  128. !isOwner &&
  129. isDj
  130. "
  131. icon="remove_moderator"
  132. label="Demote from DJ"
  133. @click="demoteFromDj"
  134. />
  135. <DropdownListItem
  136. icon="account_circle"
  137. label="View profile"
  138. @click="viewProfile"
  139. />
  140. </template>
  141. </DropdownList>
  142. </div>
  143. </template>
  144. <style lang="less" scoped>
  145. .user-item {
  146. display: flex;
  147. background-color: var(--white);
  148. border-radius: 5px;
  149. border: solid 1px var(--light-grey-1);
  150. padding: 5px;
  151. gap: 5px;
  152. :deep(.profile-picture) {
  153. height: 30px;
  154. width: 30px;
  155. flex-shrink: 0;
  156. &--initials span {
  157. font-size: 14px;
  158. }
  159. }
  160. &__name {
  161. display: inline-flex;
  162. align-items: center;
  163. gap: 2px;
  164. font-size: 12px !important;
  165. line-height: 16px;
  166. overflow: hidden;
  167. text-overflow: ellipsis;
  168. white-space: nowrap;
  169. }
  170. &__rank {
  171. color: var(--primary-color);
  172. font-size: 12px !important;
  173. }
  174. &__status {
  175. display: inline-flex;
  176. align-items: center;
  177. font-size: 10px !important;
  178. font-weight: 500 !important;
  179. line-height: 12px;
  180. color: var(--dark-grey-1);
  181. overflow: hidden;
  182. text-overflow: ellipsis;
  183. white-space: nowrap;
  184. }
  185. &__content {
  186. display: flex;
  187. flex-direction: column;
  188. flex-grow: 1;
  189. min-width: 0;
  190. justify-content: center;
  191. }
  192. }
  193. </style>