@necrolab/dashboard 0.5.15 → 0.5.17
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/backend/api.js +2 -3
- package/eslint.config.js +46 -0
- package/index.html +2 -1
- package/package.json +5 -2
- package/src/App.vue +70 -566
- package/src/assets/css/base/mixins.scss +72 -0
- package/src/assets/css/base/reset.scss +0 -2
- package/src/assets/css/base/scroll.scss +43 -36
- package/src/assets/css/base/typography.scss +9 -10
- package/src/assets/css/base/variables.scss +43 -0
- package/src/assets/css/components/accessibility.scss +37 -0
- package/src/assets/css/components/buttons.scss +61 -74
- package/src/assets/css/components/forms.scss +31 -32
- package/src/assets/css/components/headers.scss +13 -21
- package/src/assets/css/components/modals.scss +2 -2
- package/src/assets/css/components/search-groups.scss +28 -22
- package/src/assets/css/components/tables.scss +5 -7
- package/src/assets/css/components/toasts.scss +7 -7
- package/src/assets/css/components/utilities.scss +295 -0
- package/src/assets/css/main.scss +55 -139
- package/src/components/Auth/LoginForm.vue +7 -86
- package/src/components/Console/ConsoleToolbar.vue +123 -0
- package/src/components/Editors/Account/Account.vue +12 -12
- package/src/components/Editors/Account/AccountView.vue +38 -111
- package/src/components/Editors/Account/CreateAccount.vue +11 -61
- package/src/components/Editors/Account/{AccountCreator.vue → CreateAccountBatch.vue} +28 -59
- package/src/components/Editors/AdminFileEditor.vue +179 -0
- package/src/components/Editors/Profile/CreateProfile.vue +77 -150
- package/src/components/Editors/Profile/Profile.vue +20 -21
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
- package/src/components/Editors/Profile/ProfileView.vue +41 -116
- package/src/components/Editors/ProxyFileEditor.vue +86 -0
- package/src/components/Editors/TagLabel.vue +16 -55
- package/src/components/Editors/TagToggle.vue +20 -8
- package/src/components/Filter/Filter.vue +66 -79
- package/src/components/Filter/FilterPreview.vue +153 -135
- package/src/components/Filter/PriceSortToggle.vue +36 -43
- package/src/components/Table/Header.vue +1 -1
- package/src/components/Table/Table.vue +45 -51
- package/src/components/Tasks/CheckStock.vue +7 -16
- package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
- package/src/components/Tasks/Controls/MobileControls.vue +5 -20
- package/src/components/Tasks/CreateTaskAXS.vue +20 -118
- package/src/components/Tasks/CreateTaskTM.vue +33 -189
- package/src/components/Tasks/EventDetailRow.vue +21 -0
- package/src/components/Tasks/MassEdit.vue +6 -16
- package/src/components/Tasks/QuickSettings.vue +140 -216
- package/src/components/Tasks/ScrapeVenue.vue +4 -13
- package/src/components/Tasks/Stats.vue +20 -39
- package/src/components/Tasks/Task.vue +64 -270
- package/src/components/Tasks/TaskLabel.vue +9 -3
- package/src/components/Tasks/TaskView.vue +45 -64
- package/src/components/Tasks/Utilities.vue +10 -44
- package/src/components/Tasks/ViewTask.vue +23 -107
- package/src/components/icons/Close.vue +2 -8
- package/src/components/icons/Gear.vue +8 -8
- package/src/components/icons/Hash.vue +5 -0
- package/src/components/icons/Key.vue +2 -8
- package/src/components/icons/Pencil.vue +2 -8
- package/src/components/icons/Profile.vue +2 -8
- package/src/components/icons/Sell.vue +2 -8
- package/src/components/icons/Spinner.vue +4 -7
- package/src/components/icons/Wildcard.vue +2 -8
- package/src/components/icons/index.js +3 -5
- package/src/components/ui/ActionButtonGroup.vue +113 -52
- package/src/components/ui/BalanceIndicator.vue +60 -0
- package/src/components/ui/EmptyState.vue +24 -0
- package/src/components/ui/EnableDisableToggle.vue +23 -0
- package/src/components/ui/FormField.vue +49 -49
- package/src/components/ui/IconLabel.vue +23 -0
- package/src/components/ui/InfoRow.vue +21 -54
- package/src/components/ui/Modal.vue +161 -54
- package/src/components/ui/Navbar.vue +63 -44
- package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
- package/src/components/ui/ReconnectIndicator.vue +111 -124
- package/src/components/ui/SectionCard.vue +6 -14
- package/src/components/ui/Splash.vue +2 -10
- package/src/components/ui/StatusBadge.vue +26 -28
- package/src/components/ui/TaskToggle.vue +54 -0
- package/src/components/ui/controls/CountryChooser.vue +29 -66
- package/src/components/ui/controls/EyeToggle.vue +1 -1
- package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
- package/src/components/ui/controls/atomic/Dropdown.vue +103 -139
- package/src/components/ui/controls/atomic/MultiDropdown.vue +72 -120
- package/src/components/ui/controls/atomic/Switch.vue +21 -84
- package/src/composables/useCodeEditor.js +117 -0
- package/src/composables/useColorMapping.js +15 -0
- package/src/composables/useCopyToClipboard.js +1 -1
- package/src/composables/useDateFormatting.js +21 -0
- package/src/composables/useDeviceDetection.js +14 -0
- package/src/composables/useDropdownPosition.js +1 -4
- package/src/composables/useDynamicTableHeight.js +31 -0
- package/src/composables/useEnableDisable.js +6 -0
- package/src/composables/useFilterCSS.js +71 -0
- package/src/composables/useFormValidation.js +92 -0
- package/src/composables/useGetAllTags.js +9 -0
- package/src/composables/useIOSViewportHandling.js +76 -0
- package/src/composables/useNotchHandling.js +306 -0
- package/src/composables/useRowSelection.js +0 -3
- package/src/composables/useTableRender.js +23 -0
- package/src/composables/useTicketPricing.js +16 -0
- package/src/composables/useWindowDimensions.js +21 -0
- package/src/composables/useZoomPrevention.js +96 -0
- package/src/constants/tableLayout.js +14 -0
- package/src/libs/Filter.js +14 -20
- package/src/libs/panzoom.js +1 -5
- package/src/libs/utils/array.js +58 -0
- package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
- package/src/libs/utils/eventUrl.js +40 -0
- package/src/libs/utils/string.js +3 -0
- package/src/libs/utils/time.js +20 -0
- package/src/libs/utils/validation.js +64 -0
- package/src/main.js +0 -2
- package/src/stores/connection.js +1 -29
- package/src/stores/logger.js +6 -12
- package/src/stores/sampleData.js +1 -2
- package/src/stores/ui.js +80 -71
- package/src/utils/tableHelpers.js +1 -0
- package/src/views/Accounts.vue +19 -38
- package/src/views/Console.vue +74 -253
- package/src/views/Editor.vue +47 -1114
- package/src/views/FilterBuilder.vue +190 -461
- package/src/views/Login.vue +3 -28
- package/src/views/Profiles.vue +17 -32
- package/src/views/Tasks.vue +51 -38
- package/tailwind.config.js +82 -71
- package/workbox-config.cjs +47 -5
- package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2438
- package/exit +0 -209
- package/run +0 -177
- package/src/assets/css/base/color-fallbacks.scss +0 -10
- package/src/assets/img/background.svg.backup +0 -11
- package/src/components/icons/SquareCheck.vue +0 -18
- package/src/components/icons/SquareUncheck.vue +0 -18
- package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
- package/switch-branch.sh +0 -41
- /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
3
|
class="filter-card group text-white text-sm transition-all duration-200 hover:bg-dark-400 relative"
|
|
4
4
|
:class="{
|
|
5
5
|
'expanded-filter': filterBuilder.expandedFilter === filter.id,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
>
|
|
10
10
|
<div class="grid grid-cols-12 items-center py-3 px-2 sm:px-4 gap-2 sm:gap-3">
|
|
11
11
|
<div class="col-span-9 sm:col-span-10">
|
|
12
|
-
<div class="flex items-center
|
|
12
|
+
<div class="flex-gap-2 items-center sm:gap-3 cursor-pointer flex-1" @click="handleFilterClick(filter.id)">
|
|
13
13
|
<div class="filter-type-badge flex-shrink-0">
|
|
14
14
|
<component :is="getFilterIcon()" class="w-3 h-3 sm:w-4 sm:h-4" />
|
|
15
15
|
</div>
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
21
21
|
<div class="expanded-content mt-4 mx-2 sm:mx-0" v-if="filterBuilder.expandedFilter === filter.id">
|
|
22
|
-
<!-- NORMAL -->
|
|
23
22
|
<div v-if="filterType === 2" class="space-y-2">
|
|
24
23
|
<div class="info-row">
|
|
25
24
|
<span class="label">Section:</span>
|
|
@@ -34,7 +33,6 @@
|
|
|
34
33
|
<span class="value">{{ filter?.event || filter?.eventId || "-" }}</span>
|
|
35
34
|
</div>
|
|
36
35
|
</div>
|
|
37
|
-
<!-- NORMAL_FIRSTROW -->
|
|
38
36
|
<div v-if="filterType === 3" class="space-y-2">
|
|
39
37
|
<div class="info-row">
|
|
40
38
|
<span class="label">Section:</span>
|
|
@@ -49,7 +47,6 @@
|
|
|
49
47
|
<span class="value">{{ filter?.event || "-" }}</span>
|
|
50
48
|
</div>
|
|
51
49
|
</div>
|
|
52
|
-
<!-- CATCH_ALL_GA -->
|
|
53
50
|
<div v-if="filterType === 1">
|
|
54
51
|
<button
|
|
55
52
|
@click.stop="update({ buyAny: true, floor: false, generalAdmission: false })"
|
|
@@ -59,7 +56,6 @@
|
|
|
59
56
|
Convert to wildcard
|
|
60
57
|
</button>
|
|
61
58
|
</div>
|
|
62
|
-
<!-- CATCH_ALL -->
|
|
63
59
|
<div v-if="filterType === 0">
|
|
64
60
|
<button
|
|
65
61
|
@click.stop="update({ floor: true, buyAny: false, generalAdmission: false })"
|
|
@@ -69,7 +65,6 @@
|
|
|
69
65
|
Convert to Floor Wildcard
|
|
70
66
|
</button>
|
|
71
67
|
</div>
|
|
72
|
-
<!-- CATCH_ALL_FLOOR -->
|
|
73
68
|
<div v-if="filterType === 5">
|
|
74
69
|
<button
|
|
75
70
|
@click.stop="update({ generalAdmission: true, floor: false, buyAny: false })"
|
|
@@ -82,11 +77,11 @@
|
|
|
82
77
|
</div>
|
|
83
78
|
<div v-if="filterBuilder.expandedFilter === filter.id" class="controls-section mt-4 mx-2 sm:mx-0">
|
|
84
79
|
<div class="flex flex-col sm:flex-row flex-wrap gap-3 sm:items-center">
|
|
85
|
-
<div class="flex items-center
|
|
80
|
+
<div class="flex-gap-2 items-center control-group">
|
|
86
81
|
<Checkbox :toggled="filter.exclude || false" @valueUpdate="handleExcludeClick" />
|
|
87
82
|
<label class="text-sm font-medium">Excluded</label>
|
|
88
83
|
</div>
|
|
89
|
-
<div class="flex items-center
|
|
84
|
+
<div class="flex-gap-2 items-center control-group">
|
|
90
85
|
<label class="text-xs whitespace-nowrap text-light-400">Min:</label>
|
|
91
86
|
<input
|
|
92
87
|
type="number"
|
|
@@ -104,7 +99,7 @@
|
|
|
104
99
|
class="filter-input w-16 sm:w-20"
|
|
105
100
|
/>
|
|
106
101
|
</div>
|
|
107
|
-
<div class="flex items-center
|
|
102
|
+
<div class="flex-gap-2 items-center control-group">
|
|
108
103
|
<label class="text-xs whitespace-nowrap text-light-400">Max:</label>
|
|
109
104
|
<input
|
|
110
105
|
type="number"
|
|
@@ -131,16 +126,15 @@
|
|
|
131
126
|
</div>
|
|
132
127
|
</div>
|
|
133
128
|
</div>
|
|
134
|
-
<div class="col-span-3 sm:col-span-2 flex justify-end items-center gap-
|
|
135
|
-
<div class="drag-handle handle
|
|
136
|
-
<MenuIcon class="
|
|
129
|
+
<div class="col-span-3 sm:col-span-2 flex justify-end items-center gap-2">
|
|
130
|
+
<div class="drag-handle handle drag-btn cursor-grab active:cursor-grabbing" title="Drag to reorder">
|
|
131
|
+
<MenuIcon class="icon-md" />
|
|
137
132
|
</div>
|
|
138
|
-
<button
|
|
139
|
-
@click="filterBuilder.deleteFilterById(filter.id)"
|
|
140
|
-
class="delete-btn
|
|
141
|
-
title="Delete filter"
|
|
142
|
-
|
|
143
|
-
<TrashIcon class="w-3 h-3 sm:w-4 sm:h-4" />
|
|
133
|
+
<button
|
|
134
|
+
@click="filterBuilder.deleteFilterById(filter.id)"
|
|
135
|
+
class="delete-btn"
|
|
136
|
+
title="Delete filter">
|
|
137
|
+
<TrashIcon class="icon-md" />
|
|
144
138
|
</button>
|
|
145
139
|
</div>
|
|
146
140
|
</div>
|
|
@@ -155,16 +149,27 @@ import { ref } from "vue";
|
|
|
155
149
|
import { useUIStore } from "@/stores/ui";
|
|
156
150
|
const ui = useUIStore();
|
|
157
151
|
|
|
158
|
-
|
|
159
|
-
import { UpIcon, DownIcon, ReloadIcon, TrashIcon, MenuIcon, WildcardIcon, BoxIcon, GroupIcon, FilterIcon, StadiumIcon } from "@/components/icons";
|
|
152
|
+
import { TrashIcon, MenuIcon, WildcardIcon, BoxIcon, GroupIcon, FilterIcon, StadiumIcon } from "@/components/icons";
|
|
160
153
|
|
|
161
154
|
let isAllRows = ref(false);
|
|
162
155
|
|
|
163
156
|
const props = defineProps({
|
|
164
|
-
filter:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
157
|
+
filter: {
|
|
158
|
+
type: Object,
|
|
159
|
+
required: true
|
|
160
|
+
},
|
|
161
|
+
index: {
|
|
162
|
+
type: Number,
|
|
163
|
+
required: true
|
|
164
|
+
},
|
|
165
|
+
expandedFilter: {
|
|
166
|
+
type: String,
|
|
167
|
+
default: null
|
|
168
|
+
},
|
|
169
|
+
filterBuilder: {
|
|
170
|
+
type: Object,
|
|
171
|
+
required: true
|
|
172
|
+
}
|
|
168
173
|
});
|
|
169
174
|
|
|
170
175
|
const handleFilterClick = (id) => {
|
|
@@ -282,17 +287,20 @@ props.filterBuilder.onUpdate(() => {
|
|
|
282
287
|
else {
|
|
283
288
|
filterTitle.value = getFilterTitle();
|
|
284
289
|
}
|
|
285
|
-
// else if (newData.id === filter.value.id) update(newData);
|
|
286
290
|
});
|
|
287
291
|
</script>
|
|
288
292
|
|
|
289
293
|
<style scoped>
|
|
290
294
|
.filter-card {
|
|
291
|
-
@apply bg-dark-500 border-dark-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
295
|
+
@apply bg-dark-500 border border-dark-625/30 relative mb-2 transition-all duration-200;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.filter-card:hover {
|
|
299
|
+
transform: scale(1.03);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.filter-card:active {
|
|
303
|
+
transform: scale(0.98);
|
|
296
304
|
}
|
|
297
305
|
|
|
298
306
|
.filter-card:hover:not(.expanded-filter) {
|
|
@@ -332,59 +340,39 @@ props.filterBuilder.onUpdate(() => {
|
|
|
332
340
|
}
|
|
333
341
|
|
|
334
342
|
.filter-input {
|
|
335
|
-
@apply border border-dark-550 rounded px-2 py-1.5 text-sm text-white focus:outline-none transition-
|
|
336
|
-
@apply bg-dark-450/90;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
.filter-input:focus {
|
|
340
|
-
border-color: oklch(0.28 0 0);
|
|
341
|
-
@apply bg-dark-475/90;
|
|
343
|
+
@apply border border-dark-550 rounded px-2 py-1.5 text-sm text-white bg-dark-450/90 focus:bg-dark-475/90 focus:outline-none transition-all duration-200 placeholder:text-light-400;
|
|
342
344
|
}
|
|
343
345
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
+
/* Filter action buttons - drag and delete */
|
|
347
|
+
.drag-btn,
|
|
348
|
+
.delete-btn {
|
|
349
|
+
@apply flex items-center justify-center rounded-full transition-all duration-200;
|
|
350
|
+
@apply w-8 h-8 flex-shrink-0 p-0;
|
|
351
|
+
@apply border border-dark-500 bg-dark-450;
|
|
346
352
|
}
|
|
347
353
|
|
|
348
|
-
.
|
|
349
|
-
@apply
|
|
350
|
-
width: 28px;
|
|
351
|
-
height: 28px;
|
|
352
|
-
@apply text-light-400;
|
|
353
|
-
background-color: rgba(35, 36, 41, 0.8);
|
|
354
|
-
backdrop-filter: blur(4px);
|
|
355
|
-
}
|
|
354
|
+
.drag-btn {
|
|
355
|
+
@apply text-light-500;
|
|
356
356
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
width: 32px;
|
|
360
|
-
height: 32px;
|
|
357
|
+
&:hover {
|
|
358
|
+
@apply bg-dark-300 border-dark-550 text-yellow-400 shadow-md;
|
|
361
359
|
}
|
|
362
360
|
}
|
|
363
361
|
|
|
364
|
-
.
|
|
365
|
-
|
|
366
|
-
transform: scale(1.15);
|
|
367
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
.drag-btn:hover {
|
|
371
|
-
@apply bg-dark-675/80;
|
|
372
|
-
border-color: oklch(0.28 0 0);
|
|
373
|
-
}
|
|
362
|
+
.delete-btn {
|
|
363
|
+
@apply bg-error-500/30 border-error-400/50 text-error-300;
|
|
374
364
|
|
|
375
|
-
|
|
376
|
-
|
|
365
|
+
&:hover {
|
|
366
|
+
@apply bg-error-400/80 border-error-500 text-white shadow-md;
|
|
367
|
+
}
|
|
377
368
|
}
|
|
378
369
|
|
|
379
370
|
.drag-handle.sortable-chosen {
|
|
380
|
-
border
|
|
381
|
-
@apply bg-dark-675/20;
|
|
371
|
+
@apply border border-dark-550 bg-dark-675/20;
|
|
382
372
|
}
|
|
383
373
|
|
|
384
374
|
.filter-card.sortable-ghost {
|
|
385
|
-
@apply opacity-50;
|
|
386
|
-
border: 1px solid oklch(0.28 0 0);
|
|
387
|
-
@apply bg-dark-675/10;
|
|
375
|
+
@apply opacity-50 border border-dark-550 bg-dark-675/10;
|
|
388
376
|
}
|
|
389
377
|
|
|
390
378
|
.filter-card.sortable-drag {
|
|
@@ -392,27 +380,26 @@ props.filterBuilder.onUpdate(() => {
|
|
|
392
380
|
}
|
|
393
381
|
|
|
394
382
|
.expanded-filter {
|
|
395
|
-
border-
|
|
396
|
-
background-color: rgba(26, 27, 30, 0.95);
|
|
383
|
+
@apply border-l-4 border-l-dark-550 bg-dark-300/95;
|
|
397
384
|
}
|
|
398
385
|
|
|
399
386
|
.expanded-content {
|
|
400
|
-
|
|
401
|
-
border-radius: 6px;
|
|
402
|
-
padding: 12px;
|
|
387
|
+
@apply rounded-md p-3 bg-dark-300/95;
|
|
403
388
|
}
|
|
404
389
|
|
|
405
390
|
.controls-section {
|
|
406
|
-
|
|
407
|
-
border-radius: 6px;
|
|
408
|
-
padding: 12px;
|
|
391
|
+
@apply rounded-md p-3 bg-dark-350/[0.98];
|
|
409
392
|
}
|
|
410
393
|
|
|
411
394
|
.excluded-filter {
|
|
412
|
-
@apply border-l-4 border-l-error-
|
|
395
|
+
@apply border-l-4 border-l-error-400 bg-error-500/30;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.excluded-filter .delete-btn {
|
|
399
|
+
@apply bg-error-400/50 border-error-500 text-error-300;
|
|
413
400
|
}
|
|
414
401
|
|
|
415
402
|
.normal-filter {
|
|
416
|
-
border-
|
|
403
|
+
@apply border-l-4 border-l-transparent;
|
|
417
404
|
}
|
|
418
405
|
</style>
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
</template>
|
|
7
7
|
|
|
8
8
|
<div class="my-3">
|
|
9
|
-
<div class="
|
|
10
|
-
<div class="
|
|
11
|
-
<pre ref="codeDisplay" class="language-json
|
|
9
|
+
<div class="relative min-h-75 max-h-125 rounded-lg overflow-hidden shadow-card bg-dark-350">
|
|
10
|
+
<div class="relative w-full h-full min-h-75 max-h-125">
|
|
11
|
+
<pre ref="codeDisplay" class="code-highlight language-json"></pre>
|
|
12
12
|
<textarea
|
|
13
13
|
ref="codeEditor"
|
|
14
14
|
v-model="text"
|
|
@@ -19,173 +19,91 @@
|
|
|
19
19
|
@keydown.tab.prevent="handleTab"></textarea>
|
|
20
20
|
</div>
|
|
21
21
|
</div>
|
|
22
|
-
<p class="text-red-400
|
|
22
|
+
<p class="text-red-400 font-bold mt-2">{{ errorMessage }}</p>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
25
|
<div class="ml-auto flex gap-3 mt-3">
|
|
26
|
-
<button class="modal
|
|
27
|
-
<button class="modal-
|
|
26
|
+
<button class="btn-modal px-6" @click="save()">Apply</button>
|
|
27
|
+
<button class="btn-modal px-6" @click="done()">Close</button>
|
|
28
28
|
</div>
|
|
29
29
|
</Modal>
|
|
30
30
|
</template>
|
|
31
|
-
<style lang="scss" scoped>
|
|
32
|
-
.modal-btn {
|
|
33
|
-
@apply rounded transition-all duration-150 flex items-center justify-center;
|
|
34
|
-
background: oklch(0.2046 0 0);
|
|
35
|
-
border: 2px solid oklch(0.2809 0 0);
|
|
36
|
-
color: oklch(0.90 0 0);
|
|
37
|
-
height: 2.5rem;
|
|
38
|
-
width: 6.5rem;
|
|
39
|
-
font-size: 0.75rem;
|
|
40
|
-
font-weight: 500;
|
|
41
|
-
|
|
42
|
-
&:hover {
|
|
43
|
-
border-color: oklch(0.72 0.15 145);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
&:active, &:focus {
|
|
47
|
-
border-color: oklch(0.72 0.15 145);
|
|
48
|
-
outline: 1px solid oklch(0.72 0.15 145);
|
|
49
|
-
outline-offset: 0;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
&.save-btn {
|
|
53
|
-
background-color: oklch(0.72 0.15 145 / 0.15);
|
|
54
|
-
border-color: oklch(0.72 0.15 145 / 0.5);
|
|
55
|
-
|
|
56
|
-
&:hover {
|
|
57
|
-
background-color: oklch(0.72 0.15 145 / 0.25);
|
|
58
|
-
border-color: oklch(0.72 0.15 145);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/* Prism.js syntax highlighting styles */
|
|
64
|
-
.editor-container {
|
|
65
|
-
position: relative;
|
|
66
|
-
min-height: 300px;
|
|
67
|
-
max-height: 500px;
|
|
68
|
-
border-radius: 8px;
|
|
69
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
70
|
-
overflow: hidden;
|
|
71
|
-
background-color: oklch(0.19 0 0);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.editor-wrapper {
|
|
75
|
-
position: relative;
|
|
76
|
-
width: 100%;
|
|
77
|
-
height: 100%;
|
|
78
|
-
min-height: 300px;
|
|
79
|
-
max-height: 500px;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.code-editor {
|
|
83
|
-
width: 100%;
|
|
84
|
-
height: 100%;
|
|
85
|
-
min-height: 300px;
|
|
86
|
-
max-height: 500px;
|
|
87
|
-
background-color: transparent;
|
|
88
|
-
/* Make text completely transparent */
|
|
89
|
-
color: rgba(0, 0, 0, 0);
|
|
90
|
-
caret-color: #e2e8f0;
|
|
91
|
-
font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
|
|
92
|
-
padding: 12px;
|
|
93
|
-
border: none;
|
|
94
|
-
resize: none;
|
|
95
|
-
font-size: 14px;
|
|
96
|
-
line-height: 1.6;
|
|
97
|
-
tab-size: 4;
|
|
98
|
-
outline: none;
|
|
99
|
-
border: 1px solid oklch(0.26 0 0);
|
|
100
|
-
border-radius: 8px;
|
|
101
|
-
z-index: 10;
|
|
102
|
-
position: absolute;
|
|
103
|
-
top: 0;
|
|
104
|
-
left: 0;
|
|
105
|
-
right: 0;
|
|
106
|
-
bottom: 0;
|
|
107
|
-
white-space: pre;
|
|
108
|
-
overflow: auto;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.code-highlight {
|
|
112
|
-
width: 100%;
|
|
113
|
-
height: 100%;
|
|
114
|
-
min-height: 300px;
|
|
115
|
-
max-height: 500px;
|
|
116
|
-
overflow: auto;
|
|
117
|
-
white-space: pre;
|
|
118
|
-
font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
|
|
119
|
-
font-size: 14px;
|
|
120
|
-
line-height: 1.6;
|
|
121
|
-
background-color: transparent !important;
|
|
122
|
-
pointer-events: none;
|
|
123
|
-
z-index: 5;
|
|
124
|
-
position: absolute;
|
|
125
|
-
top: 0;
|
|
126
|
-
left: 0;
|
|
127
|
-
right: 0;
|
|
128
|
-
bottom: 0;
|
|
129
|
-
padding: 12px;
|
|
130
|
-
margin: 0;
|
|
131
|
-
}
|
|
132
|
-
</style>
|
|
133
31
|
<script setup>
|
|
134
32
|
import Modal from "@/components/ui/Modal.vue";
|
|
135
33
|
import { FilterIcon } from "@/components/icons";
|
|
136
34
|
import { useUIStore } from "@/stores/ui";
|
|
137
35
|
import { ref, computed, onMounted, nextTick } from "vue";
|
|
36
|
+
import { DEBUG } from "@/utils/debug";
|
|
138
37
|
|
|
139
38
|
const props = defineProps({
|
|
140
|
-
filter:
|
|
39
|
+
filter: {
|
|
40
|
+
type: Object,
|
|
41
|
+
required: true
|
|
42
|
+
}
|
|
141
43
|
});
|
|
142
44
|
|
|
143
45
|
const ui = useUIStore();
|
|
144
46
|
const text = ref(JSON.stringify(props.filter.out(), null, 4));
|
|
145
47
|
const codeEditor = ref(null);
|
|
146
48
|
const codeDisplay = ref(null);
|
|
49
|
+
let highlightTimer = null;
|
|
147
50
|
|
|
148
51
|
// Function to highlight code using Prism
|
|
149
52
|
const highlightCode = () => {
|
|
150
53
|
if (!codeDisplay.value || !codeEditor.value) return;
|
|
151
54
|
|
|
152
|
-
//
|
|
153
|
-
if (
|
|
154
|
-
console.error("Prism is not loaded");
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
55
|
+
// Debounce to avoid excessive highlighting
|
|
56
|
+
if (highlightTimer) clearTimeout(highlightTimer);
|
|
157
57
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
58
|
+
highlightTimer = setTimeout(() => {
|
|
59
|
+
requestAnimationFrame(() => {
|
|
60
|
+
try {
|
|
61
|
+
// Check if Prism is available
|
|
62
|
+
if (typeof window.Prism === "undefined" || !window.Prism.languages?.json) {
|
|
63
|
+
// Fallback to plain text
|
|
64
|
+
if (codeDisplay.value) {
|
|
65
|
+
codeDisplay.value.textContent = text.value || "";
|
|
66
|
+
codeDisplay.value.className = "language-json";
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
170
70
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
71
|
+
// Highlight the code (Prism.highlight returns sanitized HTML)
|
|
72
|
+
const highlighted = window.Prism.highlight(
|
|
73
|
+
text.value || "",
|
|
74
|
+
window.Prism.languages.json,
|
|
75
|
+
"json"
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (codeDisplay.value) {
|
|
79
|
+
codeDisplay.value.innerHTML = highlighted;
|
|
80
|
+
codeDisplay.value.className = "language-json code-highlight";
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
if (DEBUG) ui.logger.Error("Highlight error:", e);
|
|
84
|
+
// Fallback to plain text
|
|
85
|
+
if (codeDisplay.value) {
|
|
86
|
+
codeDisplay.value.textContent = text.value || "";
|
|
87
|
+
}
|
|
88
|
+
} finally {
|
|
89
|
+
// Always sync scroll
|
|
90
|
+
syncScroll();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}, 50);
|
|
174
94
|
};
|
|
175
95
|
|
|
176
96
|
// Function to sync scrolling between textarea and highlighted code
|
|
177
97
|
const syncScroll = () => {
|
|
178
98
|
if (!codeDisplay.value || !codeEditor.value) return;
|
|
179
99
|
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
codeDisplay.value.scrollLeft = codeEditor.value.scrollLeft;
|
|
184
|
-
});
|
|
100
|
+
// Direct synchronization without RAF for immediate response
|
|
101
|
+
codeDisplay.value.scrollTop = codeEditor.value.scrollTop;
|
|
102
|
+
codeDisplay.value.scrollLeft = codeEditor.value.scrollLeft;
|
|
185
103
|
};
|
|
186
104
|
|
|
187
105
|
// Function to handle tab key press in the editor
|
|
188
|
-
const handleTab = (
|
|
106
|
+
const handleTab = () => {
|
|
189
107
|
const textarea = codeEditor.value;
|
|
190
108
|
const start = textarea.selectionStart;
|
|
191
109
|
const end = textarea.selectionEnd;
|
|
@@ -225,10 +143,12 @@ function done() {
|
|
|
225
143
|
|
|
226
144
|
function save() {
|
|
227
145
|
const out = JSON.parse(text.value);
|
|
146
|
+
/* eslint-disable vue/no-mutating-props -- intentional sync from JSON to filter */
|
|
228
147
|
props.filter.reset();
|
|
229
148
|
props.filter.globalFilter = out.globalFilter;
|
|
230
149
|
props.filter.filters = out.filters;
|
|
231
150
|
props.filter.updateCss();
|
|
151
|
+
/* eslint-enable vue/no-mutating-props */
|
|
232
152
|
done();
|
|
233
153
|
}
|
|
234
154
|
|
|
@@ -241,3 +161,101 @@ const errorMessage = computed(() => {
|
|
|
241
161
|
}
|
|
242
162
|
});
|
|
243
163
|
</script>
|
|
164
|
+
|
|
165
|
+
<style scoped>
|
|
166
|
+
/* Shared base styles for perfect alignment */
|
|
167
|
+
.code-editor,
|
|
168
|
+
.code-highlight {
|
|
169
|
+
position: absolute;
|
|
170
|
+
inset: 0;
|
|
171
|
+
width: 100%;
|
|
172
|
+
height: 100%;
|
|
173
|
+
min-height: 300px;
|
|
174
|
+
max-height: 500px;
|
|
175
|
+
padding: 12px;
|
|
176
|
+
margin: 0;
|
|
177
|
+
overflow: auto;
|
|
178
|
+
white-space: pre;
|
|
179
|
+
font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
|
|
180
|
+
font-size: 14px;
|
|
181
|
+
line-height: 1.6;
|
|
182
|
+
tab-size: 4;
|
|
183
|
+
-moz-tab-size: 4;
|
|
184
|
+
word-wrap: normal;
|
|
185
|
+
word-break: normal;
|
|
186
|
+
border: 0;
|
|
187
|
+
outline: none;
|
|
188
|
+
background: transparent;
|
|
189
|
+
box-sizing: border-box;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Code editor layer - interactive */
|
|
193
|
+
.code-editor {
|
|
194
|
+
z-index: 2;
|
|
195
|
+
color: transparent !important;
|
|
196
|
+
caret-color: #ffffff;
|
|
197
|
+
resize: none;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.code-editor::selection {
|
|
201
|
+
background-color: oklch(0.60 0.18 250 / 0.6);
|
|
202
|
+
color: transparent;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.code-editor::-moz-selection {
|
|
206
|
+
background-color: oklch(0.60 0.18 250 / 0.6);
|
|
207
|
+
color: transparent;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Highlight layer - visual only */
|
|
211
|
+
.code-highlight {
|
|
212
|
+
z-index: 1;
|
|
213
|
+
pointer-events: none;
|
|
214
|
+
user-select: none;
|
|
215
|
+
-webkit-user-select: none;
|
|
216
|
+
-moz-user-select: none;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
:deep(.token.property),
|
|
220
|
+
:deep(.token.tag),
|
|
221
|
+
:deep(.token.boolean),
|
|
222
|
+
:deep(.token.number),
|
|
223
|
+
:deep(.token.constant),
|
|
224
|
+
:deep(.token.symbol) {
|
|
225
|
+
color: #b5cea8;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
:deep(.token.selector),
|
|
229
|
+
:deep(.token.attr-name),
|
|
230
|
+
:deep(.token.string),
|
|
231
|
+
:deep(.token.char),
|
|
232
|
+
:deep(.token.builtin) {
|
|
233
|
+
color: #ce9178;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
:deep(.token.punctuation) {
|
|
237
|
+
color: #d4d4d4;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
:deep(.token.operator),
|
|
241
|
+
:deep(.token.entity),
|
|
242
|
+
:deep(.token.url) {
|
|
243
|
+
color: #d4d4d4;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
:deep(.token.atrule),
|
|
247
|
+
:deep(.token.attr-value),
|
|
248
|
+
:deep(.token.keyword) {
|
|
249
|
+
color: #c586c0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
:deep(.token.function),
|
|
253
|
+
:deep(.token.class-name) {
|
|
254
|
+
color: #dcdcaa;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
:deep(.token.comment) {
|
|
258
|
+
color: #6a9955;
|
|
259
|
+
font-style: italic;
|
|
260
|
+
}
|
|
261
|
+
</style>
|