Log.vue 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. <template>
  2. <div ref="container" class="flex flex-col flex-1 overflow-y-auto">
  3. <div
  4. class="sticky top-0 z-10 flex items-center justify-between flex-none pl-4 border-b bg-primary border-dividerLight"
  5. >
  6. <label for="log" class="py-2 font-semibold text-secondaryLight">
  7. {{ title }}
  8. </label>
  9. <div>
  10. <ButtonSecondary
  11. v-tippy="{ theme: 'tooltip' }"
  12. :title="t('action.delete')"
  13. svg="trash"
  14. @click.native="emit('delete')"
  15. />
  16. <ButtonSecondary
  17. id="bottompage"
  18. v-tippy="{ theme: 'tooltip' }"
  19. :title="t('action.scroll_to_top')"
  20. svg="arrow-up"
  21. @click.native="scrollTo('top')"
  22. />
  23. <ButtonSecondary
  24. id="bottompage"
  25. v-tippy="{ theme: 'tooltip' }"
  26. :title="t('action.scroll_to_bottom')"
  27. svg="arrow-down"
  28. @click.native="scrollTo('bottom')"
  29. />
  30. <ButtonSecondary
  31. id="bottompage"
  32. v-tippy="{ theme: 'tooltip' }"
  33. :title="t('action.autoscroll')"
  34. svg="chevrons-down"
  35. :class="toggleAutoscrollColor"
  36. @click.native="toggleAutoscroll()"
  37. />
  38. </div>
  39. </div>
  40. <div
  41. v-if="log.length !== 0"
  42. ref="logs"
  43. class="overflow-y-auto border-b border-dividerLight"
  44. >
  45. <div
  46. class="flex flex-col h-auto h-full border-r divide-y divide-dividerLight border-dividerLight"
  47. >
  48. <RealtimeLogEntry
  49. v-for="(entry, index) in log"
  50. :key="`entry-${index}`"
  51. :entry="entry"
  52. />
  53. </div>
  54. </div>
  55. </div>
  56. </template>
  57. <script setup lang="ts">
  58. import { ref, PropType, computed, watch } from "@nuxtjs/composition-api"
  59. import { useThrottleFn, useScroll } from "@vueuse/core"
  60. import { useI18n } from "~/helpers/utils/composables"
  61. export type LogEntryData = {
  62. prefix?: string
  63. ts: number | undefined
  64. source: "info" | "client" | "server" | "disconnected"
  65. payload: string
  66. event: "connecting" | "connected" | "disconnected" | "error"
  67. }
  68. const props = defineProps({
  69. log: { type: Array as PropType<LogEntryData[]>, default: () => [] },
  70. title: {
  71. type: String,
  72. default: "",
  73. },
  74. })
  75. const emit = defineEmits<{
  76. (e: "delete"): void
  77. }>()
  78. const t = useI18n()
  79. const container = ref<HTMLElement | null>(null)
  80. const logs = ref<HTMLElement | null>(null)
  81. const autoScrollEnabled = ref(true)
  82. const logListScroll = useScroll(logs)
  83. // Disable autoscroll when scrolling to top
  84. watch(logListScroll.isScrolling, (isScrolling) => {
  85. if (isScrolling && logListScroll.directions.top)
  86. autoScrollEnabled.value = false
  87. })
  88. const scrollTo = (position: "top" | "bottom") => {
  89. if (position === "top") {
  90. logs.value?.scroll({
  91. behavior: "smooth",
  92. top: 0,
  93. })
  94. } else if (position === "bottom") {
  95. logs.value?.scroll({
  96. behavior: "smooth",
  97. top: logs.value?.scrollHeight,
  98. })
  99. }
  100. }
  101. watch(
  102. () => props.log,
  103. useThrottleFn(() => {
  104. if (autoScrollEnabled.value) scrollTo("bottom")
  105. }, 200),
  106. { flush: "post" }
  107. )
  108. const toggleAutoscroll = () => {
  109. autoScrollEnabled.value = !autoScrollEnabled.value
  110. }
  111. const toggleAutoscrollColor = computed(() =>
  112. autoScrollEnabled.value ? "text-green-500" : "text-red-500"
  113. )
  114. </script>
  115. <style></style>