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