RecentActivity.vue 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <script setup lang="ts">
  2. import { defineAsyncComponent, ref, onMounted, onUnmounted } from "vue";
  3. import Toast from "toasters";
  4. import { storeToRefs } from "pinia";
  5. import { useWebsocketsStore } from "@/stores/websockets";
  6. import { useUserAuthStore } from "@/stores/userAuth";
  7. import ws from "@/ws";
  8. const ActivityItem = defineAsyncComponent(
  9. () => import("@/components/ActivityItem.vue")
  10. );
  11. const { socket } = useWebsocketsStore();
  12. const props = defineProps({
  13. userId: {
  14. type: String,
  15. default: ""
  16. }
  17. });
  18. const username = ref("");
  19. const activities = ref([]);
  20. const position = ref(1);
  21. const maxPosition = ref(1);
  22. const offsettedFromNextSet = ref(0);
  23. const isGettingSet = ref(false);
  24. const userAuthStore = useUserAuthStore();
  25. const { userId: myUserId } = storeToRefs(userAuthStore);
  26. const { getBasicUser } = userAuthStore;
  27. const hideActivity = activityId => {
  28. socket.dispatch("activities.hideActivity", activityId, res => {
  29. if (res.status !== "success") new Toast(res.message);
  30. });
  31. };
  32. const getSet = () => {
  33. if (isGettingSet.value) return;
  34. if (position.value >= maxPosition.value) return;
  35. isGettingSet.value = true;
  36. socket.dispatch(
  37. "activities.getSet",
  38. props.userId,
  39. position.value,
  40. offsettedFromNextSet.value,
  41. res => {
  42. if (res.status === "success") {
  43. activities.value.push(...res.data.activities);
  44. position.value += 1;
  45. }
  46. isGettingSet.value = false;
  47. }
  48. );
  49. };
  50. const init = () => {
  51. if (myUserId.value !== props.userId)
  52. getBasicUser(props.userId).then((user: any) => {
  53. if (user && user.username) username.value = user.username;
  54. });
  55. socket.dispatch("activities.length", props.userId, res => {
  56. if (res.status === "success") {
  57. maxPosition.value = Math.ceil(res.data.length / 15) + 1;
  58. getSet();
  59. }
  60. });
  61. };
  62. const handleScroll = () => {
  63. const scrollPosition = document.body.clientHeight + window.scrollY;
  64. const bottomPosition = document.body.scrollHeight;
  65. if (scrollPosition + 400 >= bottomPosition) getSet();
  66. return maxPosition.value === position.value;
  67. };
  68. onMounted(() => {
  69. window.addEventListener("scroll", handleScroll);
  70. ws.onConnect(init);
  71. socket.on("event:activity.updated", res => {
  72. activities.value.find(
  73. activity => activity._id === res.data.activityId
  74. ).payload.message = res.data.message;
  75. });
  76. socket.on("event:activity.created", res => {
  77. activities.value.unshift(res.data.activity);
  78. offsettedFromNextSet.value += 1;
  79. });
  80. socket.on("event:activity.hidden", res => {
  81. activities.value = activities.value.filter(
  82. activity => activity._id !== res.data.activityId
  83. );
  84. offsettedFromNextSet.value -= 1;
  85. });
  86. socket.on("event:activity.removeAllForUser", () => {
  87. activities.value = [];
  88. position.value = 1;
  89. maxPosition.value = 1;
  90. offsettedFromNextSet.value = 0;
  91. });
  92. });
  93. onUnmounted(() => {
  94. window.removeEventListener("scroll", handleScroll);
  95. });
  96. </script>
  97. <template>
  98. <div class="content recent-activity-tab">
  99. <div v-if="activities.length > 0">
  100. <h4 class="section-title">Recent activity</h4>
  101. <p class="section-description">
  102. This is a log of all actions
  103. {{ userId === myUserId ? "you have" : `${username} has` }}
  104. taken recently
  105. </p>
  106. <hr class="section-horizontal-rule" />
  107. <div id="activity-items">
  108. <activity-item
  109. class="item activity-item universal-item"
  110. v-for="activity in activities"
  111. :key="activity._id"
  112. :activity="activity"
  113. >
  114. <template #actions>
  115. <quick-confirm
  116. v-if="userId === myUserId"
  117. @confirm="hideActivity(activity._id)"
  118. >
  119. <a content="Hide Activity" v-tippy>
  120. <i class="material-icons hide-icon"
  121. >visibility_off</i
  122. >
  123. </a>
  124. </quick-confirm>
  125. </template>
  126. </activity-item>
  127. </div>
  128. </div>
  129. <div v-else>
  130. <h5>No recent activity.</h5>
  131. </div>
  132. </div>
  133. </template>
  134. <style lang="less" scoped>
  135. .night-mode #activity-items .activity-item {
  136. background-color: var(--dark-grey-2) !important;
  137. border: 0 !important;
  138. }
  139. .content a {
  140. border-bottom: 0;
  141. }
  142. </style>