@necrolab/dashboard 0.4.220 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/.prettierrc +27 -1
  2. package/.vscode/extensions.json +1 -1
  3. package/README.md +64 -2
  4. package/artwork/image.png +0 -0
  5. package/backend/api.js +26 -24
  6. package/backend/auth.js +2 -2
  7. package/backend/batching.js +1 -1
  8. package/backend/endpoints.js +8 -11
  9. package/backend/index.js +2 -2
  10. package/backend/mock-data.js +27 -36
  11. package/backend/mock-src/classes/logger.js +5 -7
  12. package/backend/mock-src/classes/utils.js +3 -2
  13. package/backend/mock-src/ticketmaster.js +4 -4
  14. package/backend/validator.js +2 -2
  15. package/config/configs.json +0 -1
  16. package/dev-server.js +134 -0
  17. package/exit +209 -0
  18. package/index.html +78 -8
  19. package/index.js +1 -1
  20. package/jsconfig.json +16 -0
  21. package/package.json +39 -25
  22. package/postcss.config.js +1 -1
  23. package/postinstall.js +124 -20
  24. package/public/android-chrome-192x192.png +0 -0
  25. package/public/android-chrome-512x512.png +0 -0
  26. package/public/apple-touch-icon.png +0 -0
  27. package/public/favicon-16x16.png +0 -0
  28. package/public/favicon-32x32.png +0 -0
  29. package/public/favicon.ico +0 -0
  30. package/public/img/logo_trans.png +0 -0
  31. package/public/img/necro_logo.png +0 -0
  32. package/public/manifest.json +16 -10
  33. package/run +176 -9
  34. package/src/App.vue +498 -85
  35. package/src/assets/css/base/reset.scss +43 -0
  36. package/src/assets/css/base/scroll.scss +114 -0
  37. package/src/assets/css/base/typography.scss +37 -0
  38. package/src/assets/css/components/buttons.scss +216 -0
  39. package/src/assets/css/components/forms.scss +221 -0
  40. package/src/assets/css/components/modals.scss +13 -0
  41. package/src/assets/css/components/tables.scss +27 -0
  42. package/src/assets/css/components/toasts.scss +100 -0
  43. package/src/assets/css/main.scss +201 -122
  44. package/src/assets/img/background.svg +2 -2
  45. package/src/assets/img/background.svg.backup +11 -0
  46. package/src/assets/img/logo_trans.png +0 -0
  47. package/src/components/Auth/LoginForm.vue +62 -11
  48. package/src/components/Editors/Account/Account.vue +116 -40
  49. package/src/components/Editors/Account/AccountCreator.vue +88 -39
  50. package/src/components/Editors/Account/AccountView.vue +102 -34
  51. package/src/components/Editors/Account/CreateAccount.vue +80 -32
  52. package/src/components/Editors/Profile/CreateProfile.vue +269 -83
  53. package/src/components/Editors/Profile/Profile.vue +132 -47
  54. package/src/components/Editors/Profile/ProfileCountryChooser.vue +82 -20
  55. package/src/components/Editors/Profile/ProfileView.vue +89 -32
  56. package/src/components/Editors/TagLabel.vue +67 -6
  57. package/src/components/Editors/TagToggle.vue +7 -2
  58. package/src/components/Filter/Filter.vue +288 -71
  59. package/src/components/Filter/FilterPreview.vue +202 -31
  60. package/src/components/Filter/PriceSortToggle.vue +76 -6
  61. package/src/components/Table/Header.vue +1 -1
  62. package/src/components/Table/Row.vue +1 -1
  63. package/src/components/Table/Table.vue +19 -2
  64. package/src/components/Tasks/CheckStock.vue +6 -8
  65. package/src/components/Tasks/Controls/DesktopControls.vue +27 -17
  66. package/src/components/Tasks/Controls/MobileControls.vue +8 -45
  67. package/src/components/Tasks/CreateTaskAXS.vue +80 -72
  68. package/src/components/Tasks/CreateTaskTM.vue +95 -141
  69. package/src/components/Tasks/MassEdit.vue +4 -6
  70. package/src/components/Tasks/QuickSettings.vue +199 -30
  71. package/src/components/Tasks/ScrapeVenue.vue +5 -6
  72. package/src/components/Tasks/Stats.vue +50 -24
  73. package/src/components/Tasks/Task.vue +384 -179
  74. package/src/components/Tasks/TaskLabel.vue +2 -2
  75. package/src/components/Tasks/TaskView.vue +136 -48
  76. package/src/components/Tasks/Utilities.vue +25 -10
  77. package/src/components/Tasks/ViewTask.vue +321 -0
  78. package/src/components/icons/Bag.vue +1 -1
  79. package/src/components/icons/Check.vue +5 -0
  80. package/src/components/icons/Close.vue +21 -0
  81. package/src/components/icons/CloseX.vue +5 -0
  82. package/src/components/icons/Eye.vue +6 -0
  83. package/src/components/icons/Key.vue +21 -0
  84. package/src/components/icons/Loyalty.vue +1 -1
  85. package/src/components/icons/Mail.vue +2 -2
  86. package/src/components/icons/Pencil.vue +21 -0
  87. package/src/components/icons/Play.vue +2 -2
  88. package/src/components/icons/Profile.vue +18 -0
  89. package/src/components/icons/Reload.vue +4 -5
  90. package/src/components/icons/Sandclock.vue +2 -2
  91. package/src/components/icons/Sell.vue +21 -0
  92. package/src/components/icons/Spinner.vue +42 -0
  93. package/src/components/icons/SquareCheck.vue +18 -0
  94. package/src/components/icons/SquareUncheck.vue +18 -0
  95. package/src/components/icons/Stadium.vue +1 -1
  96. package/src/components/icons/Wildcard.vue +18 -0
  97. package/src/components/icons/index.js +26 -1
  98. package/src/components/ui/Modal.vue +107 -13
  99. package/src/components/ui/Navbar.vue +175 -40
  100. package/src/components/ui/ReconnectIndicator.vue +351 -55
  101. package/src/components/ui/Splash.vue +5 -35
  102. package/src/components/ui/controls/CountryChooser.vue +200 -62
  103. package/src/components/ui/controls/atomic/Checkbox.vue +119 -10
  104. package/src/components/ui/controls/atomic/Dropdown.vue +216 -39
  105. package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
  106. package/src/components/ui/controls/atomic/MultiDropdown.vue +300 -37
  107. package/src/components/ui/controls/atomic/Switch.vue +53 -25
  108. package/src/composables/useClickOutside.js +21 -0
  109. package/src/composables/useDropdownPosition.js +174 -0
  110. package/src/libs/Filter.js +60 -24
  111. package/src/registerServiceWorker.js +1 -1
  112. package/src/stores/connection.js +4 -4
  113. package/src/stores/sampleData.js +172 -199
  114. package/src/stores/ui.js +55 -20
  115. package/src/stores/utils.js +30 -4
  116. package/src/types/index.js +41 -0
  117. package/src/utils/debug.js +1 -0
  118. package/src/views/Accounts.vue +116 -50
  119. package/src/views/Console.vue +394 -79
  120. package/src/views/Editor.vue +1176 -123
  121. package/src/views/FilterBuilder.vue +528 -250
  122. package/src/views/Login.vue +76 -14
  123. package/src/views/Profiles.vue +119 -34
  124. package/src/views/Tasks.vue +266 -98
  125. package/static/offline.html +192 -50
  126. package/switch-branch.sh +41 -0
  127. package/tailwind.config.js +119 -27
  128. package/vite.config.js +73 -16
  129. package/workbox-config.cjs +63 -0
  130. package/ICONS.md +0 -21
  131. package/public/img/background.svg +0 -14
  132. package/public/img/logo.png +0 -0
  133. package/public/img/logo_icon.png +0 -0
  134. package/public/img/logo_icon_2.png +0 -0
  135. package/src/assets/css/_input.scss +0 -143
  136. package/src/assets/img/logo.png +0 -0
  137. package/src/assets/img/logo_icon.png +0 -0
  138. package/src/assets/img/logo_icon_2.png +0 -0
  139. package/vue.config.js +0 -32
  140. package/workbox-config.js +0 -7
@@ -1,13 +1,16 @@
1
1
  <template>
2
2
  <div>
3
- <h4 class="text-white font-bold mb-5 pt-5 flex gap-2 text-xl items-center">Console <ConsoleIcon /></h4>
3
+ <h4 class="mb-2 flex items-center gap-2 pt-5 text-sm font-bold text-white lg:mt-1">
4
+ Console
5
+ <ConsoleIcon />
6
+ </h4>
4
7
 
5
8
  <div>
6
- <div class="flex items-center mb-3">
7
- <div class="flex w-full justify-start text-exl items-center">
8
- <div class="w-40 flex rounded input-default">
9
+ <div class="mb-3 flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
10
+ <div class="flex flex-col gap-3 md:flex-row md:items-center md:flex-1">
11
+ <div class="w-full md:w-64">
9
12
  <Dropdown
10
- class="w-40"
13
+ class="console-dropdown input-default w-full border-2 border-dark-550 bg-dark-500"
11
14
  rightAmount="right-2"
12
15
  default="All logs"
13
16
  :allowDefault="true"
@@ -17,62 +20,118 @@
17
20
  Object.entries(taskLogMapping)
18
21
  .map(([k, v]) => `${k} (${v.length})`)
19
22
  .sort((a, b) => a.localeCompare(b))
20
- "
21
- />
23
+ " />
24
+ </div>
25
+ <div class="flex items-center gap-2 flex-1">
26
+ <div class="input-default flex items-center flex-1 md:max-w-64">
27
+ <input
28
+ v-model="searchQuery"
29
+ type="text"
30
+ placeholder="Search logs..."
31
+ class="h-full w-full bg-transparent text-sm text-white outline-none" />
32
+ <span v-if="searchQuery" class="ml-2 text-xs text-light-500">{{ filteredCount }}</span>
33
+ </div>
34
+ <!-- Scroll buttons on mobile - inline with search -->
35
+ <button
36
+ class="console-scroll-btn flex h-10 w-10 items-center justify-center rounded border-2 bg-dark-400 shadow-sm md:hidden"
37
+ @mousedown="startScrolling('up')"
38
+ @mouseup="stopScrolling"
39
+ @mouseleave="stopScrolling"
40
+ @touchstart="startScrolling('up')"
41
+ @touchend="stopScrolling">
42
+ <UpIcon class="pointer-events-none h-5 w-5" />
43
+ </button>
44
+ <button
45
+ class="console-scroll-btn flex h-10 w-10 items-center justify-center rounded border-2 bg-dark-400 shadow-sm md:hidden"
46
+ @mousedown="startScrolling('down')"
47
+ @mouseup="stopScrolling"
48
+ @mouseleave="stopScrolling"
49
+ @touchstart="startScrolling('down')"
50
+ @touchend="stopScrolling">
51
+ <DownIcon class="pointer-events-none h-5 w-5" />
52
+ </button>
22
53
  </div>
23
54
  </div>
24
- <div class="flex justify-right justify-end text-2xl items-center gap-3">
25
- <button
26
- class="hidden ipadlg:flex rounded gap-3 bg-dark-500 border-2 border-dark-550 shadow-lg px-2 h-10 justify-center items-center"
27
- >
28
- <h3 class="text-sm text-white">Hide Monitors</h3>
29
- <Switch class="scale-75" v-model="filteredLogs" />
30
- </button>
31
- <button
32
- class="hidden ipadlg:flex rounded gap-3 bg-dark-500 border-2 border-dark-550 shadow-lg px-2 h-10 justify-center items-center"
33
- >
34
- <h3 class="text-sm text-white">Auto</h3>
35
- <Switch class="scale-75" v-model="autoscrollToggled" />
36
- </button>
55
+ <div class="flex items-center gap-3 hidden md:flex">
56
+ <!-- Hide Monitors and Auto buttons only on desktop -->
57
+ <div class="hidden items-center gap-3 md:flex">
58
+ <button
59
+ class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
60
+ <h3 class="text-sm text-white">Hide Monitors</h3>
61
+ <Switch class="scale-75" v-model="filteredLogs" />
62
+ </button>
63
+ <button
64
+ class="relative flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
65
+ <h3 class="text-sm text-white">Auto</h3>
66
+ <Switch class="scale-75" v-model="autoscrollToggled" @change="onAutoscrollToggle" />
67
+ <div
68
+ v-if="userScrolledUp && autoscrollToggled"
69
+ class="absolute -right-1 -top-1 h-2 w-2 animate-pulse rounded-full bg-yellow-500"
70
+ title="Autoscroll paused - scroll to bottom to resume"></div>
71
+ </button>
72
+ </div>
73
+ <!-- Scroll buttons - desktop only (mobile has them inline with search) -->
37
74
  <button
38
- class="rounded bg-dark-500 border-2 border-dark-550 shadow-lg w-10 h-10 flex justify-center items-center"
39
- >
40
- <UpIcon class="cursor-pointer mx-1 lg:w-4 w-5 h-5" @click="scrollTo('up')" />
75
+ class="hidden md:flex h-10 w-10 items-center justify-center rounded border border-dark-650 bg-dark-400 shadow-sm transition-colors duration-150 hover:bg-dark-300 active:bg-dark-200"
76
+ @mousedown="startScrolling('up')"
77
+ @mouseup="stopScrolling"
78
+ @mouseleave="stopScrolling"
79
+ @touchstart="startScrolling('up')"
80
+ @touchend="stopScrolling">
81
+ <UpIcon class="pointer-events-none h-5 w-5" />
41
82
  </button>
42
83
  <button
43
- class="rounded bg-dark-500 border-2 border-dark-550 shadow-lg w-10 h-10 flex justify-center items-center"
44
- >
45
- <DownIcon class="cursor-pointer ml-1 lg:w-4 w-5 h-5" @click="scrollTo('down')" />
84
+ class="hidden md:flex h-10 w-10 items-center justify-center rounded border border-dark-650 bg-dark-400 shadow-sm transition-colors duration-150 hover:bg-dark-300 active:bg-dark-200"
85
+ @mousedown="startScrolling('down')"
86
+ @mouseup="stopScrolling"
87
+ @mouseleave="stopScrolling"
88
+ @touchstart="startScrolling('down')"
89
+ @touchend="stopScrolling">
90
+ <DownIcon class="pointer-events-none h-5 w-5" />
46
91
  </button>
47
92
  </div>
48
93
  </div>
49
94
 
50
95
  <Smoothie
51
- :weight="0.1"
52
- class="hidden-scrollbars console overflow-y-auto overflow-x-hidden font-mono text-white"
53
- style="min-height: 14rem !important"
96
+ :weight="0.2"
97
+ class="console scrollable smooth-scroll overflow-y-auto overflow-x-hidden font-mono text-white"
98
+ style="min-height: 12rem !important"
54
99
  ref="$autoscroll"
55
- >
100
+ @wheel.stop
101
+ @touchmove.stop
102
+ @scroll="handleScroll">
103
+ <div
104
+ v-if="displayedLogs.length === 0"
105
+ class="empty-state flex h-full flex-col items-center justify-center text-center">
106
+ <ConsoleIcon class="mb-3 h-12 w-12 text-dark-400 opacity-50" />
107
+ <p class="text-sm text-light-400">
108
+ {{ searchQuery ? "No logs match your search" : "No logs yet" }}
109
+ </p>
110
+ <p class="mt-1 text-xs text-light-500">
111
+ {{ searchQuery ? "Try a different search term" : "Logs will appear here in real time" }}
112
+ </p>
113
+ </div>
56
114
  <pre
57
- class="hidden-scrollbars"
58
- v-for="line in currentTaskLog
59
- ? taskLogMapping[currentTaskLog]
60
- : logLines.filter((l) => (filteredLogs ? !['-DISCORD'].some((s) => l.includes(s)) : true))"
61
- v-bind:key="line"
62
- ><code class="md:text-sm lg:text-base" v-html="line"></code></pre>
115
+ v-else
116
+ class="hidden-scrollbars log-entry"
117
+ v-for="(line, index) in displayedLogs"
118
+ v-bind:key="`log-${index}`"
119
+ :style="{ '--index': index }"><code class="md:text-sm lg:text-base" v-html="line"></code></pre>
63
120
  </Smoothie>
64
- <div class="flex ipadlg:hidden justify-between mt-3">
121
+ <div class="mt-3 flex justify-between md:hidden">
65
122
  <button
66
- class="flex rounded gap-3 bg-dark-500 border-2 border-dark-550 shadow-lg px-2 h-10 justify-center items-center"
67
- >
123
+ class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
68
124
  <h3 class="text-sm text-white">Hide Monitors</h3>
69
125
  <Switch class="scale-75" v-model="filteredLogs" />
70
126
  </button>
71
127
  <button
72
- class="flex rounded gap-3 bg-dark-500 border-2 border-dark-550 shadow-lg px-2 h-10 justify-center items-center"
73
- >
128
+ class="relative flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
74
129
  <h3 class="text-sm text-white">Auto</h3>
75
- <Switch class="scale-75" v-model="autoscrollToggled" />
130
+ <Switch class="scale-75" v-model="autoscrollToggled" @change="onAutoscrollToggle" />
131
+ <div
132
+ v-if="userScrolledUp && autoscrollToggled"
133
+ class="absolute -right-1 -top-1 h-2 w-2 animate-pulse rounded-full bg-yellow-500"
134
+ title="Autoscroll paused - scroll to bottom to resume"></div>
76
135
  </button>
77
136
  </div>
78
137
  </div>
@@ -80,23 +139,124 @@
80
139
  </template>
81
140
  <style lang="scss" scoped>
82
141
  .console {
83
- @apply bg-dark-400 lg:p-5 p-2 rounded relative border-dark-550 border-2;
84
- height: calc(100vh - 16.5rem);
142
+ @apply relative rounded border-2 border-dark-550 bg-dark-400 p-2 lg:p-5;
143
+ height: calc(100vh - 18rem);
144
+ scrollbar-width: thin;
145
+ scrollbar-color: oklch(0.35 0 0) oklch(0.19 0 0);
146
+
147
+ @media (min-width: 768px) {
148
+ height: calc(100vh - 16rem);
149
+ }
150
+
151
+ @media (min-width: 1024px) {
152
+ height: calc(100vh - 14rem);
153
+ }
154
+
155
+ &::-webkit-scrollbar {
156
+ width: 8px;
157
+ }
158
+
159
+ &::-webkit-scrollbar-track {
160
+ background: oklch(0.19 0 0);
161
+ border-radius: 4px;
162
+ }
163
+
164
+ &::-webkit-scrollbar-thumb {
165
+ background: oklch(0.35 0 0);
166
+ border-radius: 4px;
167
+ transition: background-color 0.2s ease;
168
+ }
169
+
170
+ &::-webkit-scrollbar-thumb:hover {
171
+ background: oklch(0.45 0 0);
172
+ }
173
+
174
+ // Smooth scrolling behavior with momentum
175
+ &.smooth-scroll {
176
+ scroll-behavior: smooth;
177
+ scroll-padding: 0.5rem;
178
+ -webkit-overflow-scrolling: touch;
179
+ overscroll-behavior: contain;
180
+ }
181
+
182
+ // Improved log entry animations
183
+ .log-entry {
184
+ opacity: 0;
185
+ transform: translateY(4px);
186
+ animation: slideInLog 0.2s ease-out forwards;
187
+ transition: all 0.15s ease;
188
+
189
+ &:hover {
190
+ background-color: rgba(255, 255, 255, 0.02);
191
+ transform: translateX(2px);
192
+ }
193
+ }
194
+
195
+ // Stagger animation for new logs
196
+ .log-entry:last-child {
197
+ animation-delay: 0.05s;
198
+ }
199
+
200
+ @keyframes slideInLog {
201
+ to {
202
+ opacity: 1;
203
+ transform: translateY(0);
204
+ }
205
+ }
206
+
207
+ // Empty state styling
208
+ .empty-state {
209
+ min-height: 14rem;
210
+ font-family:
211
+ "Inter",
212
+ -apple-system,
213
+ BlinkMacSystemFont,
214
+ "Segoe UI",
215
+ Helvetica,
216
+ Arial,
217
+ sans-serif;
218
+ }
219
+
85
220
  textarea {
86
221
  background: transparent;
87
222
  resize: none;
223
+ @apply w-full text-white focus:outline-none;
224
+ }
225
+ }
226
+
227
+ .console-scroll-btn {
228
+ border-color: oklch(0.2809 0 0);
229
+ transition: all 0.15s ease;
88
230
 
89
- @apply w-full focus:outline-none text-white;
90
- }
91
- // $border: 1px;
92
- // &:before {
93
- // content: "";
94
- // @apply absolute top-0 left-0 right-0 bottom-0 opacity-60;
95
- // z-index: -2;
96
- // margin: -$border;
97
- // border-radius: inherit;
98
- // background: radial-gradient(rgba(96, 66, 255, 0.6), rgba(255, 255, 255, 0));
99
- // }
231
+ &:hover, &:active {
232
+ border-color: oklch(0.72 0.15 145) !important;
233
+ outline: 1px solid oklch(0.72 0.15 145);
234
+ outline-offset: 0;
235
+ }
236
+ }
237
+
238
+ /* Mobile portrait console optimizations */
239
+ @screen mobile-portrait {
240
+ .console {
241
+ height: calc(100vh - 19.5rem);
242
+ @apply p-1 text-xs;
243
+ overflow: auto;
244
+
245
+ pre {
246
+ line-height: 1.2;
247
+ }
248
+
249
+ code {
250
+ font-size: 0.7rem !important;
251
+ }
252
+ }
253
+ }
254
+
255
+ /* Mobile landscape console optimizations */
256
+ @media (max-width: 1024px) and (orientation: landscape) {
257
+ .console {
258
+ height: calc(100vh - 10.5rem);
259
+ }
100
260
  }
101
261
 
102
262
  .text-xxs {
@@ -110,28 +270,22 @@
110
270
  height: 30px;
111
271
  }
112
272
  }
113
- .max-h-big-con {
114
- max-height: calc(100vh - 12rem);
115
- overflow: hidden;
116
- }
273
+ /* Console-specific styles handled by utilities */
117
274
  </style>
118
275
  <script setup>
119
276
  import { Smoothie } from "vue-smoothie";
120
-
121
- const DEBUG = window.location.href.startsWith("http://localhost:5173");
277
+ import { DEBUG } from "@/utils/debug";
122
278
 
123
279
  import Filter from "@/libs/ansii.js";
124
280
  import { ConsoleIcon, DownIcon, UpIcon } from "@/components/icons";
125
281
  import Switch from "@/components/ui/controls/atomic/Switch.vue";
126
282
  import WebsocketHeartbeatJs from "websocket-heartbeat-js";
127
- import { onMounted, ref, nextTick } from "vue";
283
+ import { onMounted, onUnmounted, ref, nextTick, computed, watch } from "vue";
128
284
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
129
285
  import { sortAlphaNum } from "@/stores/utils";
130
286
 
131
287
  import { useUIStore } from "@/stores/ui";
132
288
 
133
- const DEBUG = window.location.href.startsWith("http://localhost:5173");
134
-
135
289
  const ui = useUIStore();
136
290
 
137
291
  const $autoscroll = ref(null);
@@ -139,27 +293,169 @@ const logLines = ref([]);
139
293
  const ansii = new Filter();
140
294
  const autoscrollToggled = ref(true);
141
295
  const taskLogMapping = ref({});
142
- const currentTaskLog = ref(false);
296
+ const currentTaskLog = ref("");
143
297
  const filteredLogs = ref(true);
298
+ const userScrolledUp = ref(false);
299
+ const lastScrollTime = ref(0);
300
+ const scrollInterval = ref(null);
301
+ const isScrolling = ref(false);
302
+ const searchQuery = ref("");
303
+
304
+ // Computed filtered logs based on search query
305
+ const displayedLogs = computed(() => {
306
+ let logs =
307
+ currentTaskLog.value && currentTaskLog.value !== ""
308
+ ? taskLogMapping.value[currentTaskLog.value]
309
+ : logLines.value.filter((l) => (filteredLogs.value ? !["-DISCORD"].some((s) => l.includes(s)) : true));
310
+
311
+ if (searchQuery.value.trim()) {
312
+ const query = searchQuery.value.toLowerCase();
313
+ logs = logs.filter((log) => {
314
+ // Remove HTML tags for search
315
+ const plainText = log.replace(/<[^>]*>/g, "").toLowerCase();
316
+ return plainText.includes(query);
317
+ });
318
+ }
319
+
320
+ return logs;
321
+ });
322
+
323
+ const filteredCount = computed(() => {
324
+ if (!searchQuery.value.trim()) return "";
325
+ return `${displayedLogs.value.length}`;
326
+ });
144
327
 
145
328
  const path = "/api/updates?type=console";
146
329
  const url = (window.location.protocol === "http:" ? "ws://" : "wss://") + window.location.host + path;
147
- const scrollTo = (dir) => {
330
+ // Handle manual scroll detection
331
+ const handleScroll = (event) => {
332
+ if (!autoscrollToggled.value) return;
333
+
334
+ const element = event.target;
335
+ if (!element) return;
336
+
337
+ // Check if user is near bottom (within 50px)
338
+ const threshold = 50;
339
+ const isNearBottom = element.scrollTop + element.clientHeight >= element.scrollHeight - threshold;
340
+
341
+ // Only update if there's an actual change
342
+ if (userScrolledUp.value === isNearBottom) {
343
+ userScrolledUp.value = !isNearBottom;
344
+ }
345
+ };
346
+
347
+ // Bulletproof scroll function with multiple fallbacks
348
+ const performScroll = (direction, smooth = true) => {
148
349
  try {
149
- if (dir === "up") $autoscroll.value.el.scrollTop = 0;
150
- else $autoscroll.value.el.scrollTop = parseInt($autoscroll.value.el.children[1].style.height);
350
+ if (!$autoscroll.value?.el) {
351
+ if (DEBUG) console.log("Autoscroll element not ready");
352
+ return false;
353
+ }
354
+
355
+ const element = $autoscroll.value.el;
356
+
357
+ if (direction === "up") {
358
+ if (smooth && element.scrollTo) {
359
+ element.scrollTo({ top: 0, behavior: "smooth" });
360
+ } else {
361
+ element.scrollTop = 0;
362
+ }
363
+ } else {
364
+ const targetTop = element.scrollHeight - element.clientHeight;
365
+ if (smooth && element.scrollTo) {
366
+ element.scrollTo({ top: targetTop, behavior: "smooth" });
367
+ } else {
368
+ element.scrollTop = targetTop;
369
+ }
370
+ userScrolledUp.value = false;
371
+ }
372
+
373
+ return true;
151
374
  } catch (e) {
152
375
  if (DEBUG) console.log("Error scrolling", e);
376
+ return false;
377
+ }
378
+ };
379
+
380
+ // Continuous scrolling for held buttons
381
+ const startScrolling = (direction) => {
382
+ if (isScrolling.value) return;
383
+
384
+ isScrolling.value = true;
385
+
386
+ // Immediate scroll
387
+ performScroll(direction, true);
388
+
389
+ // Continue scrolling while held (for long content)
390
+ scrollInterval.value = setInterval(() => {
391
+ if (!isScrolling.value) return;
392
+
393
+ const element = $autoscroll.value?.el;
394
+ if (!element) return;
395
+
396
+ const scrollAmount = 100;
397
+ if (direction === "up") {
398
+ element.scrollTop = Math.max(0, element.scrollTop - scrollAmount);
399
+ } else {
400
+ element.scrollTop = Math.min(element.scrollHeight - element.clientHeight, element.scrollTop + scrollAmount);
401
+ }
402
+ }, 50);
403
+ };
404
+
405
+ const stopScrolling = () => {
406
+ isScrolling.value = false;
407
+ if (scrollInterval.value) {
408
+ clearInterval(scrollInterval.value);
409
+ scrollInterval.value = null;
410
+ }
411
+ };
412
+
413
+ // Legacy function for compatibility
414
+ const scrollTo = (dir) => {
415
+ performScroll(dir, true);
416
+ };
417
+
418
+ // Simple autoscroll to bottom
419
+ const autoScrollToBottom = () => {
420
+ if (!$autoscroll.value?.el || !autoscrollToggled.value) return;
421
+
422
+ // Only scroll if user hasn't manually scrolled up
423
+ if (!userScrolledUp.value) {
424
+ const element = $autoscroll.value.el;
425
+
426
+ // Calculate the target scroll position to show the last log
427
+ const targetScrollTop = element.scrollHeight - element.clientHeight;
428
+
429
+ // Use smooth scrolling to ensure new logs are visible
430
+ if (element.scrollTo && Math.abs(element.scrollTop - targetScrollTop) > 5) {
431
+ element.scrollTo({
432
+ top: targetScrollTop,
433
+ behavior: "smooth"
434
+ });
435
+ } else {
436
+ // For small differences or fallback, use instant scroll
437
+ element.scrollTop = targetScrollTop;
438
+ }
439
+ }
440
+ };
441
+
442
+ // Handle autoscroll toggle
443
+ const onAutoscrollToggle = () => {
444
+ if (autoscrollToggled.value) {
445
+ userScrolledUp.value = false;
446
+ nextTick().then(autoScrollToBottom);
153
447
  }
154
448
  };
155
449
 
156
450
  const addAnsiToOutput = (a) => {
157
- const html = ansii.toHtml(a.log);
451
+ const html = ansii.toHtml(a?.log || a);
158
452
  logLines.value.push(html);
159
- if (autoscrollToggled.value)
160
- nextTick().then(() => {
161
- scrollTo("down");
162
- });
453
+
454
+ // Auto scroll after adding new content with proper timing
455
+ nextTick().then(() => {
456
+ // Use a small delay to ensure DOM is fully updated
457
+ setTimeout(autoScrollToBottom, 10);
458
+ });
163
459
  };
164
460
 
165
461
  const handleWebsocketMessages = (msg) => {
@@ -194,16 +490,30 @@ const makeTaskLogMapping = (lines) => {
194
490
 
195
491
  window.startDebugConsoleMessages = () => {
196
492
  setInterval(() => {
197
- const line = `\u001b[96m[12:16:02.273]\u001b[00m \u001b[96m[TASK-${Math.round(
198
- Math.random() * 10
199
- )}]\u001b[00m AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Needs To Queue: false - Queue up: false`;
200
- addAnsiToOutput(line);
201
- makeTaskLogMapping([line]);
202
- }, 500);
493
+ var taskId = Math.round(Math.random() * 10);
494
+ const log = {
495
+ log: `\u001b[96m[12:16:02.273]\u001b[00m \u001b[96m[TASK-${taskId}]\u001b[00m TEST TEST TEST TEST TEST TEST TEST`,
496
+ metadata: {
497
+ taskId: taskId,
498
+ siteId: "TM_US",
499
+ global: false
500
+ }
501
+ };
502
+ addAnsiToOutput(log);
503
+ makeTaskLogMapping([log]);
504
+ }, 100);
203
505
  };
204
506
 
205
507
  if (DEBUG) window.startDebugConsoleMessages();
206
508
 
509
+ // Watch for log filter changes and reset scroll state
510
+ watch([currentTaskLog, filteredLogs], () => {
511
+ userScrolledUp.value = false;
512
+ nextTick().then(() => {
513
+ setTimeout(autoScrollToBottom, 10);
514
+ });
515
+ });
516
+
207
517
  // Listen for messages
208
518
  onMounted(() => {
209
519
  const socket = new WebsocketHeartbeatJs({ url, pingMsg: "ping" });
@@ -216,4 +526,9 @@ onMounted(() => {
216
526
  });
217
527
  };
218
528
  });
529
+
530
+ // Cleanup on unmount
531
+ onUnmounted(() => {
532
+ stopScrolling();
533
+ });
219
534
  </script>