PageSourceSearch

https://www.gamestrat.com/assets/appDeviceLogs/football/logViewer/…0fb87a763479e5ed5cea9207890d8ad64245e5a8.js

js gamestrat.com collected 2026-09-24 18:05:05 UTC 9,179 bytes, 251 lines download raw bytes

1import { ref, computed, watch, onMounted } from "vue"
2import { QBtn, QIcon, QInput, QItem, QTooltip, QMenu, QVirtualScroll } from "quasar"
3import { useWindowSize } from "@vueuse/core"
4import LaptopInfo from "appDeviceLogs/football/logViewer/overview/LaptopInfo"
5import iosDeviceCard from "appDeviceLogs/football/logViewer/deviceInfo/iosDeviceCard"
6import Jdenticon, { useJdenticon, refreshJdenticon } from "appDeviceLogs/composables/jdenticon"
7import dateFormat from "dateformat"
8import _ from "lodash"
9import socketDisconnectReason from "appDeviceLogs/utils/socketDisconnectReasons"
10
11export default {
12  components: {
13    QBtn,
14    QIcon,
15    QInput,
16    QItem,
17    QTooltip,
18    QMenu,
19    QVirtualScroll,
20    LaptopInfo,
21    iosDeviceCard,
22    Jdenticon,
23  },
24  props: {
25    selectedLog: Object,
26    isLeftPanelDocked: Boolean,
27  },
28  setup(props) {
29    useJdenticon()
30    const { width } = useWindowSize()
31    const rootElem = ref(null)
32    const cardWidth = ref({ maxWidth: "0px" })
33    function updateCardWidth() {
34      const widthVal = `${document.body.clientWidth}px`
35      const { paddingLeft, paddingRight } = getComputedStyle(rootElem.value.parentElement)
36      const leftPanel = document.getElementById("leftPanelContainer")
37      const panelWidth = props.isLeftPanelDocked ? `${leftPanel.clientWidth}px` : "0px"
38      cardWidth.value.maxWidth = `calc(${widthVal} - ${panelWidth} - ${paddingLeft} - ${paddingRight}) !important`
39    }
40    watch(width, updateCardWidth)
41    watch(() => props.isLeftPanelDocked, updateCardWidth)
42    onMounted(() => {
43      updateCardWidth()
44    })
45    const searchTerm = ref("")
46    const sections = computed(() => {
47      let result = []
48      props.selectedLog.events.forEach((event) => {
49        const eventCopy = _.cloneDeep(event)
50        // Set additional props on event
51        if (eventCopy.level == "error") {
52          const error = (() => {
53            if (typeof eventCopy.eventData == "string") return { message: eventCopy.eventData }
54            if (typeof eventCopy.eventData != "object")
55              return { message: JSON.stringify(eventCopy.eventData) }
56            if (eventCopy.eventData.stack) {
57              eventCopy.eventData.stack = eventCopy.eventData.stack.split("\n").map((line) => {
58                return line.trim().replaceAll("<anonymous>", "anonymous")
59              })
60            }
61            return eventCopy.eventData
62          })()
63          eventCopy.label = `${eventCopy.eventID} ${error.message || ""}`
64          eventCopy.error = error
65        } else if (eventCopy.eventID == "Disconnected from Server") {
66          const disconnectReason = socketDisconnectReason(event.eventData)
67          eventCopy.label = `${eventCopy.eventID}: ${disconnectReason}`
68        } else {
69          eventCopy.label = `${eventCopy.eventID} ${JSON.stringify(eventCopy.eventData) || ""}`
70        }
71        if (
72          searchTerm.value &&
73          !eventCopy.label.toLowerCase().includes(searchTerm.value.toLowerCase())
74        ) {
75          return
76        }
77        // Create new section if needed
78        if (result.length == 0 || _.last(result).name != eventCopy.section) {
79          result.push({
80            name: eventCopy.section,
81            start: eventCopy.timestamp,
82            end: eventCopy.timestamp,
83            events: [eventCopy],
84          })
85        }
86        // Append event to existing section
87        else {
88          _.last(result).end = event.timestamp
89          _.last(result).events.push(eventCopy)
90        }
91      })
92      return result
93    })
94
95    const devices = computed(() => {
96      let map = new Map()
97      props.selectedLog.devices.forEach((device) => {
98        map.set(device.id, device)
99      })
100      return map
101    })
102
103    function timeSpread(index, events) {
104      if (index == events.length - 1) return ``
105      const spread = events[index + 1].timestamp - events[index].timestamp
106      const format = (() => {
107        if (spread < 60 * 1000) return `s.l "s"`
108        if (spread < 60 * 60 * 1000) return `M "m" s "s"`
109        return `h "h" M "m" s "s"`
110      })()
111      const d = new Date()
112      d.setHours(0, 0, 0, 0)
113      return dateFormat(d.getTime() + spread, format)
114    }
115
116    function deviceForEvent(event) {
117      return devices.value.get(event.deviceID) || { id: event.deviceID, name: "unknown device" }
118    }
119
120    return {
121      rootElem,
122      cardWidth,
123      searchTerm,
124      devices,
125      dateFormat,
126      sections,
127      deviceForEvent,
128      timeSpread,
129      refreshJdenticon,
130    }
131  },
132  template: /*html*/ `
133  <div ref="rootElem" class="w-full">
134
135    <!-- Search bar -->
136    <div class="sticky z-50 top-[3.25rem] mb-4">
137      <div class="abs-c w-screen lg:w-[calc(100%+2rem)] h-full bg-gray-50">
138      </div>
139      <div class="relative row items-center px-4 py-2">
140        <q-input v-model="searchTerm" label="Search Events" bg-color="white" filled dense class="w-full">
141          <template #prepend>
142            <q-icon name="mdi-magnify" />
143          </template>
144          <template #append>
145            <q-icon
146            name="mdi-close-circle"
147            size="1rem"
148            class="text-gray-500 hover:text-gray-600 cursor-pointer"
149            :class="{
150              'invisible': searchTerm == ''
151            }"
152            @click="searchTerm = ''"/>
153          </template>
154        </q-input>
155
156      </div>
157    </div>
158
159    <!-- Session internal sections -->
160    <div v-for="(section, index) in sections" class="pl-2">
161    
162      <!-- Section label -->
163      <span class="w-full row gap-2 text-2xs text-gray-600">
164        <span>{{section.name}}</span>
165        <span> | </span>
166        <span>{{dateFormat(section.start, "hh:MMtt ss")}}s</span>  
167        <span>&ndash;</span>  
168        <span>{{dateFormat(section.end, "hh:MMtt ss")}}s</span>
169      </span>
170
171      <!-- Events list table -->
172      <div class="card p-4 text-sm w-full mt-2" :style="cardWidth">
173        <q-virtual-scroll 
174          type="table"
175          :items="Object.freeze(section.events)"
176          :virtual-scroll-item-size="48"
177          v-slot="{ item: event, index }"
178          separator="none"
179          class="w-full text-left text-xs text-gray-800 max-h-[30rem] shadow-none"
180          @virtual-scroll="refreshJdenticon"
181        >
182          <tr :key="index" class="h-[48px] relative">
183            <!-- Time spread -->
184            <td class="w-16 p-2 py-3">
185              <div class="relative h-full w-full">
186                <span class="absolute top-full left-0 text-2xs text-gray-500 whitespace-nowrap">
187                  {{timeSpread(index, section.events)}}
188                </span>
189              </div>
190            </td>
191            <!-- Timestamp -->
192            <td class="w-40 p-2 py-3">
193              {{ dateFormat(event.timestamp, "hh:MMtt ss") }}s
194            </td>
195            <!-- Device Identicon -->
196            <td class="w-8 p-2 py-3">
197              <Jdenticon :jdid="deviceForEvent(event).id"/>
198            </td>
199            <!-- Device -->
200            <td class="w-36 p-2 py-3">
201              <span>{{ deviceForEvent(event).name }}</span>
202              <q-tooltip
203                :delay="500"
204                transition-show="jump-down"
205                transition-hide="jump-up"
206                class="bg-transparent p-0 overflow-visible text-gray-800"
207              >
208
209                <laptop-info v-if="deviceForEvent(event).type == 'Laptop'" :selected-log="selectedLog"/>
210                <ios-device-card 
211                  v-else
212                  :selected-log="selectedLog" 
213                  :device-id="deviceForEvent(event).id" 
214                  :device-name="deviceForEvent(event).name" 
215                  class="text-gray-800"
216                />
217              </q-tooltip>
218            </td>
219            <!-- Icon -->
220            <td class="w-10 p-2 py-3">
221              <div class="relative w-full h-full">
222                <q-icon v-if="event.level == 'warning'" name="mdi-alert-rhombus" size="1.5em" class="abs-c text-amber-600"/>
223                <q-icon v-if="event.level == 'error'" name="mdi-alert" size="1.5em" class="abs-c text-red-700"/>
224              </div>
225            </td>
226            <!-- Event name -->
227            <td class="flex-grow p-2 py-3">
228              <span v-if="event.error === undefined">
229                {{ event.label }}
230              </span>
231              <div v-else class="row items-center justify-between">
232                <span>{{ event.label }}</span>
233                <q-btn v-if="event.error.stack != undefined" icon="mdi-information-outline" size="xs" flat rounded class="h-full sticky right-0 z-50 bg-white">
234                  <q-menu transition-show="jump-down" transition-hide="jump-up" class="column items-start shadow-lg max-h-40 p-4 bg-red-50 text-red-700 border-l-8 border-red-700">
235                    <span v-for="line of event.error.stack" class="whitespace-nowrap">{{ line }}</span> 
236                  </q-menu>
237                </q-btn>
238              </div>
239            </td>
240          </tr>
241  
242        </q-virtual-scroll>
243      </div>
244
245      <!-- Line -->
246      <div v-if="index < sections.length-1" class="w-[2px] h-12 bg-gray-300 m-8 mb-4"></div>
247      
248    </div>
249  </div>
250  `,
251};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.