@necrolab/dashboard 0.4.39 → 0.4.40
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.
- package/.claude/settings.local.json +5 -1
- package/.prettierrc +14 -1
- package/backend/api.js +5 -0
- package/backend/mock-data.js +8 -8
- package/dev-server.js +131 -44
- package/package.json +3 -2
- package/postinstall.js +25 -13
- package/src/assets/css/_input.scss +12 -12
- package/src/assets/css/main.scss +63 -10
- package/src/components/Auth/LoginForm.vue +12 -5
- package/src/components/Editors/Account/AccountCreator.vue +51 -22
- package/src/components/Editors/Account/AccountView.vue +64 -13
- package/src/components/Editors/Account/CreateAccount.vue +47 -28
- package/src/components/Editors/Profile/ProfileView.vue +46 -8
- package/src/components/Filter/FilterPreview.vue +0 -4
- package/src/components/Tasks/Stats.vue +22 -16
- package/src/components/Tasks/Task.vue +78 -47
- package/src/components/Tasks/TaskView.vue +10 -9
- package/src/stores/sampleData.js +84 -60
- package/src/stores/ui.js +30 -4
- package/src/views/Console.vue +208 -21
- package/src/views/Editor.vue +2 -6
- package/src/views/FilterBuilder.vue +28 -46
- package/src/views/Login.vue +134 -12
- package/tailwind.config.js +1 -1
- package/vite.config.js +26 -0
- package/src/assets/img/android-chrome-192x192.png +0 -0
package/src/views/Console.vue
CHANGED
|
@@ -26,28 +26,48 @@
|
|
|
26
26
|
<h3 class="text-sm text-white">Hide Monitors</h3>
|
|
27
27
|
<Switch class="scale-75" v-model="filteredLogs" />
|
|
28
28
|
</button>
|
|
29
|
-
<button class="flex rounded gap-3 card-dark px-2 h-10 flex-center">
|
|
29
|
+
<button class="flex rounded gap-3 card-dark px-2 h-10 flex-center relative">
|
|
30
30
|
<h3 class="text-sm text-white">Auto</h3>
|
|
31
|
-
<Switch class="scale-75" v-model="autoscrollToggled" />
|
|
31
|
+
<Switch class="scale-75" v-model="autoscrollToggled" @change="onAutoscrollToggle" />
|
|
32
|
+
<div
|
|
33
|
+
v-if="userScrolledUp && autoscrollToggled"
|
|
34
|
+
class="absolute -top-1 -right-1 w-2 h-2 bg-yellow-500 rounded-full animate-pulse"
|
|
35
|
+
title="Autoscroll paused - scroll to bottom to resume"
|
|
36
|
+
></div>
|
|
32
37
|
</button>
|
|
33
38
|
</div>
|
|
34
39
|
<!-- Scroll buttons always visible -->
|
|
35
|
-
<button
|
|
36
|
-
|
|
40
|
+
<button
|
|
41
|
+
class="rounded card-dark w-10 h-10 flex-center hover:bg-dark-300 active:bg-dark-200 transition-colors duration-150"
|
|
42
|
+
@mousedown="startScrolling('up')"
|
|
43
|
+
@mouseup="stopScrolling"
|
|
44
|
+
@mouseleave="stopScrolling"
|
|
45
|
+
@touchstart="startScrolling('up')"
|
|
46
|
+
@touchend="stopScrolling"
|
|
47
|
+
>
|
|
48
|
+
<UpIcon class="w-5 h-5 pointer-events-none" />
|
|
37
49
|
</button>
|
|
38
|
-
<button
|
|
39
|
-
|
|
50
|
+
<button
|
|
51
|
+
class="rounded card-dark w-10 h-10 flex-center hover:bg-dark-300 active:bg-dark-200 transition-colors duration-150"
|
|
52
|
+
@mousedown="startScrolling('down')"
|
|
53
|
+
@mouseup="stopScrolling"
|
|
54
|
+
@mouseleave="stopScrolling"
|
|
55
|
+
@touchstart="startScrolling('down')"
|
|
56
|
+
@touchend="stopScrolling"
|
|
57
|
+
>
|
|
58
|
+
<DownIcon class="w-5 h-5 pointer-events-none" />
|
|
40
59
|
</button>
|
|
41
60
|
</div>
|
|
42
61
|
</div>
|
|
43
62
|
|
|
44
63
|
<Smoothie
|
|
45
|
-
:weight="0.
|
|
46
|
-
class="console overflow-y-auto overflow-x-hidden font-mono text-white scrollable"
|
|
64
|
+
:weight="0.2"
|
|
65
|
+
class="console overflow-y-auto overflow-x-hidden font-mono text-white scrollable smooth-scroll"
|
|
47
66
|
style="min-height: 14rem !important"
|
|
48
67
|
ref="$autoscroll"
|
|
49
68
|
@wheel.stop
|
|
50
69
|
@touchmove.stop
|
|
70
|
+
@scroll="handleScroll"
|
|
51
71
|
>
|
|
52
72
|
<div
|
|
53
73
|
v-if="
|
|
@@ -64,11 +84,12 @@
|
|
|
64
84
|
</div>
|
|
65
85
|
<pre
|
|
66
86
|
v-else
|
|
67
|
-
class="hidden-scrollbars"
|
|
87
|
+
class="hidden-scrollbars log-entry"
|
|
68
88
|
v-for="(line, index) in currentTaskLog && currentTaskLog !== ''
|
|
69
89
|
? taskLogMapping[currentTaskLog]
|
|
70
90
|
: logLines.filter((l) => (filteredLogs ? !['-DISCORD'].some((s) => l.includes(s)) : true))"
|
|
71
91
|
v-bind:key="`log-${index}`"
|
|
92
|
+
:style="{ '--index': index }"
|
|
72
93
|
><code class="md:text-sm lg:text-base" v-html="line"></code></pre>
|
|
73
94
|
</Smoothie>
|
|
74
95
|
<div class="flex ipadlg:hidden justify-between mt-3">
|
|
@@ -76,9 +97,14 @@
|
|
|
76
97
|
<h3 class="text-sm text-white">Hide Monitors</h3>
|
|
77
98
|
<Switch class="scale-75" v-model="filteredLogs" />
|
|
78
99
|
</button>
|
|
79
|
-
<button class="flex rounded gap-3 card-dark px-2 h-10 flex-center">
|
|
100
|
+
<button class="flex rounded gap-3 card-dark px-2 h-10 flex-center relative">
|
|
80
101
|
<h3 class="text-sm text-white">Auto</h3>
|
|
81
|
-
<Switch class="scale-75" v-model="autoscrollToggled" />
|
|
102
|
+
<Switch class="scale-75" v-model="autoscrollToggled" @change="onAutoscrollToggle" />
|
|
103
|
+
<div
|
|
104
|
+
v-if="userScrolledUp && autoscrollToggled"
|
|
105
|
+
class="absolute -top-1 -right-1 w-2 h-2 bg-yellow-500 rounded-full animate-pulse"
|
|
106
|
+
title="Autoscroll paused - scroll to bottom to resume"
|
|
107
|
+
></div>
|
|
82
108
|
</button>
|
|
83
109
|
</div>
|
|
84
110
|
</div>
|
|
@@ -99,17 +125,52 @@
|
|
|
99
125
|
|
|
100
126
|
&::-webkit-scrollbar-track {
|
|
101
127
|
background: #2e2f34;
|
|
128
|
+
border-radius: 4px;
|
|
102
129
|
}
|
|
103
130
|
|
|
104
131
|
&::-webkit-scrollbar-thumb {
|
|
105
132
|
background: #555;
|
|
106
133
|
border-radius: 4px;
|
|
134
|
+
transition: background-color 0.2s ease;
|
|
107
135
|
}
|
|
108
136
|
|
|
109
137
|
&::-webkit-scrollbar-thumb:hover {
|
|
110
138
|
background: #777;
|
|
111
139
|
}
|
|
112
140
|
|
|
141
|
+
// Smooth scrolling behavior with momentum
|
|
142
|
+
&.smooth-scroll {
|
|
143
|
+
scroll-behavior: smooth;
|
|
144
|
+
scroll-padding: 0.5rem;
|
|
145
|
+
-webkit-overflow-scrolling: touch;
|
|
146
|
+
overscroll-behavior: contain;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Improved log entry animations
|
|
150
|
+
.log-entry {
|
|
151
|
+
opacity: 0;
|
|
152
|
+
transform: translateY(4px);
|
|
153
|
+
animation: slideInLog 0.2s ease-out forwards;
|
|
154
|
+
transition: all 0.15s ease;
|
|
155
|
+
|
|
156
|
+
&:hover {
|
|
157
|
+
background-color: rgba(255, 255, 255, 0.02);
|
|
158
|
+
transform: translateX(2px);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Stagger animation for new logs
|
|
163
|
+
.log-entry:last-child {
|
|
164
|
+
animation-delay: 0.05s;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@keyframes slideInLog {
|
|
168
|
+
to {
|
|
169
|
+
opacity: 1;
|
|
170
|
+
transform: translateY(0);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
113
174
|
// Empty state styling
|
|
114
175
|
.empty-state {
|
|
115
176
|
min-height: 14rem;
|
|
@@ -175,7 +236,7 @@ import Filter from "@/libs/ansii.js";
|
|
|
175
236
|
import { ConsoleIcon, DownIcon, UpIcon } from "@/components/icons";
|
|
176
237
|
import Switch from "@/components/ui/controls/atomic/Switch.vue";
|
|
177
238
|
import WebsocketHeartbeatJs from "websocket-heartbeat-js";
|
|
178
|
-
import { onMounted, ref, nextTick } from "vue";
|
|
239
|
+
import { onMounted, onUnmounted, ref, nextTick, computed, watch } from "vue";
|
|
179
240
|
import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
|
|
180
241
|
import { sortAlphaNum } from "@/stores/utils";
|
|
181
242
|
|
|
@@ -190,29 +251,142 @@ const autoscrollToggled = ref(true);
|
|
|
190
251
|
const taskLogMapping = ref({});
|
|
191
252
|
const currentTaskLog = ref("");
|
|
192
253
|
const filteredLogs = ref(true);
|
|
254
|
+
const userScrolledUp = ref(false);
|
|
255
|
+
const lastScrollTime = ref(0);
|
|
256
|
+
const scrollInterval = ref(null);
|
|
257
|
+
const isScrolling = ref(false);
|
|
193
258
|
|
|
194
259
|
const path = "/api/updates?type=console";
|
|
195
260
|
const url = (window.location.protocol === "http:" ? "ws://" : "wss://") + window.location.host + path;
|
|
196
|
-
|
|
261
|
+
// Handle manual scroll detection
|
|
262
|
+
const handleScroll = (event) => {
|
|
263
|
+
if (!autoscrollToggled.value) return;
|
|
264
|
+
|
|
265
|
+
const element = event.target;
|
|
266
|
+
if (!element) return;
|
|
267
|
+
|
|
268
|
+
// Check if user is near bottom (within 50px)
|
|
269
|
+
const threshold = 50;
|
|
270
|
+
const isNearBottom = element.scrollTop + element.clientHeight >= element.scrollHeight - threshold;
|
|
271
|
+
|
|
272
|
+
// Only update if there's an actual change
|
|
273
|
+
if (userScrolledUp.value === isNearBottom) {
|
|
274
|
+
userScrolledUp.value = !isNearBottom;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Bulletproof scroll function with multiple fallbacks
|
|
279
|
+
const performScroll = (direction, smooth = true) => {
|
|
197
280
|
try {
|
|
198
281
|
if (!$autoscroll.value?.el) {
|
|
199
282
|
if (DEBUG) console.log("Autoscroll element not ready");
|
|
200
|
-
return;
|
|
283
|
+
return false;
|
|
201
284
|
}
|
|
202
|
-
|
|
203
|
-
|
|
285
|
+
|
|
286
|
+
const element = $autoscroll.value.el;
|
|
287
|
+
|
|
288
|
+
if (direction === "up") {
|
|
289
|
+
if (smooth && element.scrollTo) {
|
|
290
|
+
element.scrollTo({ top: 0, behavior: "smooth" });
|
|
291
|
+
} else {
|
|
292
|
+
element.scrollTop = 0;
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
const targetTop = element.scrollHeight - element.clientHeight;
|
|
296
|
+
if (smooth && element.scrollTo) {
|
|
297
|
+
element.scrollTo({ top: targetTop, behavior: "smooth" });
|
|
298
|
+
} else {
|
|
299
|
+
element.scrollTop = targetTop;
|
|
300
|
+
}
|
|
301
|
+
userScrolledUp.value = false;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return true;
|
|
204
305
|
} catch (e) {
|
|
205
306
|
if (DEBUG) console.log("Error scrolling", e);
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// Continuous scrolling for held buttons
|
|
312
|
+
const startScrolling = (direction) => {
|
|
313
|
+
if (isScrolling.value) return;
|
|
314
|
+
|
|
315
|
+
isScrolling.value = true;
|
|
316
|
+
|
|
317
|
+
// Immediate scroll
|
|
318
|
+
performScroll(direction, true);
|
|
319
|
+
|
|
320
|
+
// Continue scrolling while held (for long content)
|
|
321
|
+
scrollInterval.value = setInterval(() => {
|
|
322
|
+
if (!isScrolling.value) return;
|
|
323
|
+
|
|
324
|
+
const element = $autoscroll.value?.el;
|
|
325
|
+
if (!element) return;
|
|
326
|
+
|
|
327
|
+
const scrollAmount = 100;
|
|
328
|
+
if (direction === "up") {
|
|
329
|
+
element.scrollTop = Math.max(0, element.scrollTop - scrollAmount);
|
|
330
|
+
} else {
|
|
331
|
+
element.scrollTop = Math.min(element.scrollHeight - element.clientHeight, element.scrollTop + scrollAmount);
|
|
332
|
+
}
|
|
333
|
+
}, 50);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const stopScrolling = () => {
|
|
337
|
+
isScrolling.value = false;
|
|
338
|
+
if (scrollInterval.value) {
|
|
339
|
+
clearInterval(scrollInterval.value);
|
|
340
|
+
scrollInterval.value = null;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// Legacy function for compatibility
|
|
345
|
+
const scrollTo = (dir) => {
|
|
346
|
+
performScroll(dir, true);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// Simple autoscroll to bottom
|
|
350
|
+
const autoScrollToBottom = () => {
|
|
351
|
+
if (!$autoscroll.value?.el || !autoscrollToggled.value) return;
|
|
352
|
+
|
|
353
|
+
// Only scroll if user hasn't manually scrolled up
|
|
354
|
+
if (!userScrolledUp.value) {
|
|
355
|
+
const element = $autoscroll.value.el;
|
|
356
|
+
|
|
357
|
+
// Calculate the target scroll position to show the last log
|
|
358
|
+
const targetScrollTop = element.scrollHeight - element.clientHeight;
|
|
359
|
+
|
|
360
|
+
// Use smooth scrolling to ensure new logs are visible
|
|
361
|
+
if (element.scrollTo && Math.abs(element.scrollTop - targetScrollTop) > 5) {
|
|
362
|
+
element.scrollTo({
|
|
363
|
+
top: targetScrollTop,
|
|
364
|
+
behavior: 'smooth'
|
|
365
|
+
});
|
|
366
|
+
} else {
|
|
367
|
+
// For small differences or fallback, use instant scroll
|
|
368
|
+
element.scrollTop = targetScrollTop;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// Handle autoscroll toggle
|
|
374
|
+
const onAutoscrollToggle = () => {
|
|
375
|
+
if (autoscrollToggled.value) {
|
|
376
|
+
userScrolledUp.value = false;
|
|
377
|
+
nextTick().then(autoScrollToBottom);
|
|
206
378
|
}
|
|
207
379
|
};
|
|
208
380
|
|
|
209
381
|
const addAnsiToOutput = (a) => {
|
|
210
382
|
const html = ansii.toHtml(a?.log || a);
|
|
211
383
|
logLines.value.push(html);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
384
|
+
|
|
385
|
+
// Auto scroll after adding new content with proper timing
|
|
386
|
+
nextTick().then(() => {
|
|
387
|
+
// Use a small delay to ensure DOM is fully updated
|
|
388
|
+
setTimeout(autoScrollToBottom, 10);
|
|
389
|
+
});
|
|
216
390
|
};
|
|
217
391
|
|
|
218
392
|
const handleWebsocketMessages = (msg) => {
|
|
@@ -259,11 +433,19 @@ window.startDebugConsoleMessages = () => {
|
|
|
259
433
|
};
|
|
260
434
|
addAnsiToOutput(log);
|
|
261
435
|
makeTaskLogMapping([log]);
|
|
262
|
-
},
|
|
436
|
+
}, 100);
|
|
263
437
|
};
|
|
264
438
|
|
|
265
439
|
if (DEBUG) window.startDebugConsoleMessages();
|
|
266
440
|
|
|
441
|
+
// Watch for log filter changes and reset scroll state
|
|
442
|
+
watch([currentTaskLog, filteredLogs], () => {
|
|
443
|
+
userScrolledUp.value = false;
|
|
444
|
+
nextTick().then(() => {
|
|
445
|
+
setTimeout(autoScrollToBottom, 10);
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
267
449
|
// Listen for messages
|
|
268
450
|
onMounted(() => {
|
|
269
451
|
const socket = new WebsocketHeartbeatJs({ url, pingMsg: "ping" });
|
|
@@ -276,4 +458,9 @@ onMounted(() => {
|
|
|
276
458
|
});
|
|
277
459
|
};
|
|
278
460
|
});
|
|
461
|
+
|
|
462
|
+
// Cleanup on unmount
|
|
463
|
+
onUnmounted(() => {
|
|
464
|
+
stopScrolling();
|
|
465
|
+
});
|
|
279
466
|
</script>
|
package/src/views/Editor.vue
CHANGED
|
@@ -686,10 +686,6 @@ loadProxyLists();
|
|
|
686
686
|
overflow: auto;
|
|
687
687
|
}
|
|
688
688
|
|
|
689
|
-
/* Enhance the text selection color */
|
|
690
|
-
.code-editor::selection {
|
|
691
|
-
background: rgba(98, 114, 164, 0.4);
|
|
692
|
-
}
|
|
693
689
|
|
|
694
690
|
/* iOS keyboard focus fix and scrolling */
|
|
695
691
|
.code-editor:focus,
|
|
@@ -788,8 +784,8 @@ textarea.proxy-editor {
|
|
|
788
784
|
}
|
|
789
785
|
|
|
790
786
|
.proxy-editor-container:focus-within {
|
|
791
|
-
border-color: #
|
|
792
|
-
box-shadow: 0 0 0
|
|
787
|
+
border-color: #44454b;
|
|
788
|
+
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2);
|
|
793
789
|
}
|
|
794
790
|
|
|
795
791
|
/* Prism.js dark theme */
|
|
@@ -10,12 +10,10 @@
|
|
|
10
10
|
<input
|
|
11
11
|
class="h-10 text-white text-sm p-2 bg-dark-500 w-48 flex items-center rounded-l rounded-r-none relative border-2 border-dark-550"
|
|
12
12
|
placeholder="Event ID"
|
|
13
|
-
v-model="eventId"
|
|
14
|
-
/>
|
|
13
|
+
v-model="eventId" />
|
|
15
14
|
<button
|
|
16
15
|
class="h-10 text-white text-sm px-3 bg-dark-400 flex items-center rounded-r relative font-medium border-2 border-dark-550 smooth-hover"
|
|
17
|
-
@click="updateShownVenue"
|
|
18
|
-
>
|
|
16
|
+
@click="updateShownVenue">
|
|
19
17
|
Load
|
|
20
18
|
</button>
|
|
21
19
|
</div>
|
|
@@ -27,8 +25,7 @@
|
|
|
27
25
|
<div class="grid grid-cols-1 lg:grid-cols-5 gap-3 lg:gap-4 w-full h-full">
|
|
28
26
|
<!-- Map -->
|
|
29
27
|
<div
|
|
30
|
-
class="col-span-1 lg:col-span-3 w-full h-full rounded-lg relative flex flex-col lg:max-w-none lg:overflow-hidden"
|
|
31
|
-
>
|
|
28
|
+
class="col-span-1 lg:col-span-3 w-full h-full rounded-lg relative flex flex-col lg:max-w-none lg:overflow-hidden">
|
|
32
29
|
<div v-if="svg" class="flex items-center mb-1">
|
|
33
30
|
<div class="flex items-center justify-between w-20 px-2 text-white font-black text-sm">
|
|
34
31
|
<span class="cursor-pointer" @click="handleZoom(true)">+</span>
|
|
@@ -39,25 +36,21 @@
|
|
|
39
36
|
<div class="overflow-hidden selecto-wrapper flex-1">
|
|
40
37
|
<div
|
|
41
38
|
v-if="svg"
|
|
42
|
-
class="h-full overflow-auto hidden-scrollbars p-2 rounded shadow border-2 border-dark-550 svg-container"
|
|
43
|
-
>
|
|
39
|
+
class="h-full overflow-auto hidden-scrollbars p-2 rounded shadow border-2 border-dark-550 svg-container">
|
|
44
40
|
<div class="svg-wrapper" id="svg-wrapper" v-html="svg"></div>
|
|
45
41
|
</div>
|
|
46
42
|
<div
|
|
47
43
|
v-else
|
|
48
|
-
class="h-full flex items-center justify-center p-2 rounded shadow border-2 border-dark-550 svg-container"
|
|
49
|
-
>
|
|
44
|
+
class="h-full flex items-center justify-center p-2 rounded shadow border-2 border-dark-550 svg-container">
|
|
50
45
|
<div class="text-center">
|
|
51
46
|
<svg
|
|
52
47
|
class="w-12 h-12 mb-3 mx-auto opacity-50"
|
|
53
48
|
viewBox="0 0 19 19"
|
|
54
49
|
fill="none"
|
|
55
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
56
|
-
>
|
|
50
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
57
51
|
<path
|
|
58
52
|
d="M2.37499 5.54165V2.37498L5.54166 3.95831L2.37499 5.54165ZM14.25 5.54165V2.37498L17.4167 3.95831L14.25 5.54165ZM8.70833 4.74998V1.58331L11.875 3.16665L8.70833 4.74998ZM8.70833 17.4166C7.70555 17.3903 6.77218 17.3079 5.9082 17.1696C5.0437 17.0308 4.29162 16.8559 3.65195 16.6448C3.01176 16.4337 2.50694 16.1896 2.13749 15.9125C1.76805 15.6354 1.58333 15.3451 1.58333 15.0416V7.91665C1.58333 7.58678 1.79127 7.27988 2.20716 6.99594C2.62252 6.71252 3.18645 6.46183 3.89895 6.24385C4.61145 6.02641 5.4493 5.85488 6.4125 5.72927C7.37569 5.60419 8.40486 5.54165 9.49999 5.54165C10.5951 5.54165 11.6243 5.60419 12.5875 5.72927C13.5507 5.85488 14.3885 6.02641 15.101 6.24385C15.8135 6.46183 16.3775 6.71252 16.7928 6.99594C17.2087 7.27988 17.4167 7.58678 17.4167 7.91665V15.0416C17.4167 15.3451 17.2319 15.6354 16.8625 15.9125C16.493 16.1896 15.9885 16.4337 15.3488 16.6448C14.7086 16.8559 13.9565 17.0308 13.0926 17.1696C12.2281 17.3079 11.2944 17.3903 10.2917 17.4166V14.25H8.70833V17.4166ZM9.49999 8.70831C10.7799 8.70831 11.885 8.63231 12.8155 8.48031C13.7454 8.32884 14.4875 8.15415 15.0417 7.95623C15.0417 7.89026 14.5403 7.73509 13.5375 7.49073C12.5347 7.2469 11.1889 7.12498 9.49999 7.12498C7.81111 7.12498 6.46527 7.2469 5.46249 7.49073C4.45972 7.73509 3.95833 7.89026 3.95833 7.95623C4.51249 8.15415 5.25481 8.32884 6.18529 8.48031C7.11523 8.63231 8.22013 8.70831 9.49999 8.70831ZM7.12499 15.7146V12.6666H11.875V15.7146C12.9305 15.609 13.7948 15.4538 14.4677 15.2491C15.1406 15.0448 15.5958 14.8635 15.8333 14.7052V9.34165C15.1076 9.63192 14.1972 9.86283 13.1021 10.0344C12.0069 10.2059 10.8062 10.2916 9.49999 10.2916C8.19374 10.2916 6.99305 10.2059 5.89791 10.0344C4.80277 9.86283 3.89236 9.63192 3.16666 9.34165V14.7052C3.40416 14.8635 3.85937 15.0448 4.53229 15.2491C5.2052 15.4538 6.06944 15.609 7.12499 15.7146Z"
|
|
59
|
-
fill="#F5F5F5"
|
|
60
|
-
/>
|
|
53
|
+
fill="#F5F5F5" />
|
|
61
54
|
</svg>
|
|
62
55
|
<p class="text-light-400 text-sm">No Map</p>
|
|
63
56
|
<p class="text-light-500 text-xs">
|
|
@@ -69,13 +62,15 @@
|
|
|
69
62
|
</div>
|
|
70
63
|
<div class="col-span-1 lg:col-span-2 w-full flex flex-col h-full">
|
|
71
64
|
<div class="flex justify-between mb-2 items-center text-white">
|
|
72
|
-
<div class="rounded flex
|
|
65
|
+
<div class="rounded flex gap-2 items-center" v-if="hasLoaded">
|
|
73
66
|
<PriceSortToggle
|
|
74
67
|
:current="filterBuilder.globalFilter.priceSort"
|
|
75
68
|
class="smooth-hover"
|
|
76
|
-
@change="(e) => filterBuilder.updateGlobalFilter({ priceSort: e })"
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
@change="(e) => filterBuilder.updateGlobalFilter({ priceSort: e })" />
|
|
70
|
+
</div>
|
|
71
|
+
<div class="flex items-center gap-1" v-if="hasLoaded">
|
|
72
|
+
<SavingsIcon class="w-4 h-4 text-light-400" />
|
|
73
|
+
<label class="text-sm text-light-400">Max Price:</label>
|
|
79
74
|
<input
|
|
80
75
|
type="number"
|
|
81
76
|
:value="filterBuilder.globalFilter.maxPrice"
|
|
@@ -86,8 +81,7 @@
|
|
|
86
81
|
})
|
|
87
82
|
"
|
|
88
83
|
class="w-14 bg-dark-500 border-2 border-dark-550 px-1 pl-2 h-8 rounded"
|
|
89
|
-
placeholder="max"
|
|
90
|
-
/>
|
|
84
|
+
placeholder="max" />
|
|
91
85
|
</div>
|
|
92
86
|
</div>
|
|
93
87
|
<Table class="border-2 border-dark-550 shadow-xl flex-1 flex flex-col">
|
|
@@ -101,8 +95,7 @@
|
|
|
101
95
|
<PriceSortToggle
|
|
102
96
|
class="w-14 smooth-hover"
|
|
103
97
|
:options="['All', 'WL', 'BL']"
|
|
104
|
-
@change="(e) => (shownFilters = e)"
|
|
105
|
-
/>
|
|
98
|
+
@change="(e) => (shownFilters = e)" />
|
|
106
99
|
<button class="header-btn save-btn" @click="saveFilter">
|
|
107
100
|
<EditIcon class="w-4 h-4" />
|
|
108
101
|
<span class="lg:block hidden">Save</span>
|
|
@@ -131,23 +124,20 @@
|
|
|
131
124
|
filterBuilder.updateCss();
|
|
132
125
|
cssUpdateTrigger++;
|
|
133
126
|
}
|
|
134
|
-
"
|
|
135
|
-
>
|
|
127
|
+
">
|
|
136
128
|
<template #item="{ element: f, index: i }">
|
|
137
129
|
<Filter
|
|
138
130
|
v-if="doesFilterShow(f)"
|
|
139
131
|
:filter="f"
|
|
140
132
|
:index="i"
|
|
141
133
|
:filterBuilder="filterBuilder"
|
|
142
|
-
class="compact-filter"
|
|
143
|
-
/>
|
|
134
|
+
class="compact-filter" />
|
|
144
135
|
</template>
|
|
145
136
|
</draggable>
|
|
146
137
|
</div>
|
|
147
138
|
<div
|
|
148
139
|
v-else
|
|
149
|
-
class="empty-state flex flex-col items-center justify-center py-8 text-center"
|
|
150
|
-
>
|
|
140
|
+
class="empty-state flex flex-col items-center justify-center py-8 text-center">
|
|
151
141
|
<FilterIcon class="w-12 h-12 text-dark-400 mb-3 opacity-50" />
|
|
152
142
|
<p class="text-light-400 text-sm">No filters yet</p>
|
|
153
143
|
<p class="text-light-500 text-xs mt-1">Click on the map to create filters</p>
|
|
@@ -164,14 +154,12 @@
|
|
|
164
154
|
? 'text-gray opacity-50 cursor-not-allowed'
|
|
165
155
|
: 'text-gray smooth-hover hover:bg-dark-400 hover:border-light-300'
|
|
166
156
|
]"
|
|
167
|
-
:title="hasWildcardFilter ? 'Wildcard filter already exists' : 'Add wildcard filter'"
|
|
168
|
-
>
|
|
157
|
+
:title="hasWildcardFilter ? 'Wildcard filter already exists' : 'Add wildcard filter'">
|
|
169
158
|
* Wildcard
|
|
170
159
|
</button>
|
|
171
160
|
<button
|
|
172
161
|
@click="ui.toggleModal('preview-filter')"
|
|
173
|
-
class="border-2 gap-1 flex justify-between items-center rounded border-dark-550 px-2 h-7 text-gray text-xs bg-dark-500 overflow-hidden shadow smooth-hover"
|
|
174
|
-
>
|
|
162
|
+
class="border-2 gap-1 flex justify-between items-center rounded border-dark-550 px-2 h-7 text-gray text-xs bg-dark-500 overflow-hidden shadow smooth-hover">
|
|
175
163
|
<CameraIcon class="w-3 h-3" />
|
|
176
164
|
JSON
|
|
177
165
|
</button>
|
|
@@ -197,7 +185,7 @@ import { FilterIcon } from "@/components/icons";
|
|
|
197
185
|
import DragSelect from "dragselect";
|
|
198
186
|
import { DEBUG } from "@/utils/debug";
|
|
199
187
|
|
|
200
|
-
import { ReloadIcon, TrashIcon, EditIcon, CameraIcon, StadiumIcon } from "@/components/icons";
|
|
188
|
+
import { ReloadIcon, TrashIcon, EditIcon, CameraIcon, StadiumIcon, SavingsIcon } from "@/components/icons";
|
|
201
189
|
import { sendSaveFilter } from "@/stores/requests";
|
|
202
190
|
import PriceSortToggle from "@/components/Filter/PriceSortToggle.vue";
|
|
203
191
|
import { useUIStore } from "@/stores/ui";
|
|
@@ -372,12 +360,7 @@ const addWildcardFilter = () => {
|
|
|
372
360
|
};
|
|
373
361
|
|
|
374
362
|
// Initialize RendererFactory
|
|
375
|
-
let RendererFactory;
|
|
376
|
-
if (DEBUG) {
|
|
377
|
-
RendererFactory = class {
|
|
378
|
-
constructor() {}
|
|
379
|
-
};
|
|
380
|
-
} else RendererFactory = import("@necrolab/tm-renderer");
|
|
363
|
+
let RendererFactory = import("@necrolab/tm-renderer");
|
|
381
364
|
|
|
382
365
|
// Real-time CSS injection system
|
|
383
366
|
let styleElement = null;
|
|
@@ -490,13 +473,12 @@ const doesFilterShow = (filter) => {
|
|
|
490
473
|
};
|
|
491
474
|
|
|
492
475
|
let rendererFactory;
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
});
|
|
476
|
+
RendererFactory.then((r) => {
|
|
477
|
+
rendererFactory = new r.default();
|
|
478
|
+
rendererFactory.init();
|
|
479
|
+
}).catch((error) => {
|
|
480
|
+
console.error("Failed to initialize renderer:", error);
|
|
481
|
+
});
|
|
500
482
|
|
|
501
483
|
const updateShownVenue = async () => {
|
|
502
484
|
eventId.value = eventId.value.trim();
|