@necrolab/dashboard 0.4.61 → 0.4.208

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 (133) hide show
  1. package/.prettierrc +1 -27
  2. package/.vscode/extensions.json +1 -1
  3. package/README.md +79 -43
  4. package/backend/api.js +48 -40
  5. package/backend/auth.js +3 -3
  6. package/backend/batching.js +1 -1
  7. package/backend/endpoints.js +77 -13
  8. package/backend/index.js +2 -2
  9. package/backend/mock-data.js +38 -29
  10. package/backend/mock-src/classes/logger.js +8 -8
  11. package/backend/mock-src/classes/utils.js +3 -7
  12. package/backend/mock-src/database.js +0 -0
  13. package/backend/mock-src/ticketmaster.js +79 -79
  14. package/backend/validator.js +2 -2
  15. package/config/configs.json +3 -2
  16. package/config/filter.json +3 -2
  17. package/index.html +10 -81
  18. package/index.js +1 -1
  19. package/package.json +25 -40
  20. package/postcss.config.js +1 -1
  21. package/postinstall.js +17 -98
  22. package/public/android-chrome-192x192.png +0 -0
  23. package/public/android-chrome-512x512.png +0 -0
  24. package/public/apple-touch-icon.png +0 -0
  25. package/public/favicon-16x16.png +0 -0
  26. package/public/favicon-32x32.png +0 -0
  27. package/public/favicon.ico +0 -0
  28. package/public/manifest.json +7 -12
  29. package/public/sw.js +2 -0
  30. package/public/workbox-49fdaf31.js +2 -0
  31. package/public/workbox-49fdaf31.js.map +1 -0
  32. package/public/workbox-88575b92.js +2 -0
  33. package/public/workbox-88575b92.js.map +1 -0
  34. package/public/workbox-a67a7b11.js +2 -0
  35. package/public/workbox-a67a7b11.js.map +1 -0
  36. package/public/workbox-d4314735.js +2 -0
  37. package/public/workbox-d4314735.js.map +1 -0
  38. package/public/workbox-e0f89ef3.js +2 -0
  39. package/public/workbox-e0f89ef3.js.map +1 -0
  40. package/run +9 -176
  41. package/src/App.vue +85 -498
  42. package/src/assets/css/_input.scss +99 -144
  43. package/src/assets/css/main.scss +99 -450
  44. package/src/assets/img/background.svg +2 -2
  45. package/src/assets/img/logo_icon.png +0 -0
  46. package/src/components/Auth/LoginForm.vue +11 -62
  47. package/src/components/Editors/Account/Account.vue +40 -116
  48. package/src/components/Editors/Account/AccountCreator.vue +39 -88
  49. package/src/components/Editors/Account/AccountView.vue +34 -102
  50. package/src/components/Editors/Account/CreateAccount.vue +32 -80
  51. package/src/components/Editors/Profile/CreateProfile.vue +83 -269
  52. package/src/components/Editors/Profile/Profile.vue +47 -132
  53. package/src/components/Editors/Profile/ProfileCountryChooser.vue +20 -82
  54. package/src/components/Editors/Profile/ProfileView.vue +34 -91
  55. package/src/components/Editors/TagLabel.vue +6 -67
  56. package/src/components/Filter/Filter.vue +72 -289
  57. package/src/components/Filter/FilterPreview.vue +30 -171
  58. package/src/components/Filter/PriceSortToggle.vue +4 -74
  59. package/src/components/Table/Header.vue +1 -1
  60. package/src/components/Table/Row.vue +1 -1
  61. package/src/components/Table/Table.vue +2 -19
  62. package/src/components/Tasks/CheckStock.vue +13 -28
  63. package/src/components/Tasks/Controls/DesktopControls.vue +17 -17
  64. package/src/components/Tasks/Controls/MobileControls.vue +45 -8
  65. package/src/components/Tasks/CreateTaskAXS.vue +73 -79
  66. package/src/components/Tasks/CreateTaskTM.vue +142 -94
  67. package/src/components/Tasks/MassEdit.vue +7 -9
  68. package/src/components/Tasks/QuickSettings.vue +55 -169
  69. package/src/components/Tasks/ScrapeVenue.vue +4 -7
  70. package/src/components/Tasks/Stats.vue +23 -52
  71. package/src/components/Tasks/Task.vue +136 -378
  72. package/src/components/Tasks/TaskView.vue +47 -107
  73. package/src/components/Tasks/Utilities.vue +6 -5
  74. package/src/components/icons/Bag.vue +1 -1
  75. package/src/components/icons/Loyalty.vue +1 -1
  76. package/src/components/icons/Mail.vue +2 -2
  77. package/src/components/icons/Play.vue +2 -2
  78. package/src/components/icons/Reload.vue +5 -4
  79. package/src/components/icons/Sandclock.vue +2 -2
  80. package/src/components/icons/Stadium.vue +1 -1
  81. package/src/components/icons/index.js +1 -24
  82. package/src/components/ui/Modal.vue +13 -105
  83. package/src/components/ui/Navbar.vue +38 -171
  84. package/src/components/ui/ReconnectIndicator.vue +55 -351
  85. package/src/components/ui/Splash.vue +35 -5
  86. package/src/components/ui/controls/CountryChooser.vue +62 -200
  87. package/src/components/ui/controls/atomic/Checkbox.vue +10 -119
  88. package/src/components/ui/controls/atomic/Dropdown.vue +39 -208
  89. package/src/components/ui/controls/atomic/MultiDropdown.vue +37 -300
  90. package/src/libs/Filter.js +170 -200
  91. package/src/registerServiceWorker.js +1 -1
  92. package/src/stores/connection.js +53 -51
  93. package/src/stores/logger.js +3 -11
  94. package/src/stores/sampleData.js +235 -207
  95. package/src/stores/ui.js +44 -112
  96. package/src/stores/utils.js +6 -90
  97. package/src/views/Accounts.vue +35 -44
  98. package/src/views/Console.vue +90 -341
  99. package/src/views/Editor.vue +123 -1176
  100. package/src/views/FilterBuilder.vue +251 -607
  101. package/src/views/Login.vue +14 -76
  102. package/src/views/Profiles.vue +25 -44
  103. package/src/views/Tasks.vue +100 -187
  104. package/static/offline.html +50 -192
  105. package/tailwind.config.js +26 -104
  106. package/vite.config.js +16 -73
  107. package/vue.config.js +32 -0
  108. package/workbox-config.js +11 -0
  109. package/artwork/image.png +0 -0
  110. package/dev-server.js +0 -136
  111. package/exit +0 -209
  112. package/jsconfig.json +0 -16
  113. package/src/assets/css/_utilities.scss +0 -388
  114. package/src/assets/img/background.svg.backup +0 -11
  115. package/src/components/icons/Check.vue +0 -5
  116. package/src/components/icons/Close.vue +0 -21
  117. package/src/components/icons/CloseX.vue +0 -5
  118. package/src/components/icons/Key.vue +0 -21
  119. package/src/components/icons/Pencil.vue +0 -21
  120. package/src/components/icons/Profile.vue +0 -18
  121. package/src/components/icons/Sell.vue +0 -21
  122. package/src/components/icons/Spinner.vue +0 -42
  123. package/src/components/icons/SquareCheck.vue +0 -18
  124. package/src/components/icons/SquareUncheck.vue +0 -18
  125. package/src/components/icons/Wildcard.vue +0 -18
  126. package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
  127. package/src/composables/useClickOutside.js +0 -21
  128. package/src/composables/useDropdownPosition.js +0 -174
  129. package/src/types/index.js +0 -41
  130. package/src/utils/debug.js +0 -1
  131. package/switch-branch.sh +0 -41
  132. package/workbox-config.cjs +0 -63
  133. /package/src/assets/img/{logo_icon-old.png → logo_icon_2.png} +0 -0
@@ -1,147 +1,104 @@
1
1
  <template>
2
- <div
3
- class="filter-card group text-white text-sm transition-all duration-200 hover:bg-dark-400 relative"
4
- :class="{
5
- 'expanded-filter': filterBuilder.expandedFilter === filter.id,
6
- 'excluded-filter': filter.exclude,
7
- 'normal-filter': !filterBuilder.expandedFilter && !filter.exclude
8
- }"
9
- >
10
- <div class="grid grid-cols-12 items-center py-3 px-2 sm:px-4 gap-2 sm:gap-3">
11
- <div class="col-span-9 sm:col-span-10">
12
- <div class="flex items-center gap-2 sm:gap-3 cursor-pointer flex-1" @click="handleFilterClick(filter.id)">
13
- <div class="filter-type-badge flex-shrink-0">
14
- <component :is="getFilterIcon()" class="w-3 h-3 sm:w-4 sm:h-4" />
15
- </div>
16
- <div class="flex-1 min-w-0">
17
- <h3 class="font-medium text-white group-hover:text-light-400 transition-colors text-sm sm:text-base truncate">{{ filterTitle }}</h3>
18
- <p class="text-xs mt-1 truncate text-light-400" v-if="getFilterSubtitle()">{{ getFilterSubtitle() }}</p>
19
- </div>
2
+ <div class="text-white text-sm border-t-2 border-t-dark-550">
3
+ <div class="grid grid-cols-7 items-center">
4
+ <div class="col-span-6 m-4 sm:m-1 mr-4">
5
+ <div class="flex items-center h-7 gap-2">
6
+ <h3 @click="handleFilterClick(filter.id)">{{ filterTitle }}</h3>
7
+ <Checkbox
8
+ :class="`${filterBuilder.selectedFilters.includes(filter.id) ? 'border-green-400' : null}`"
9
+ :toggled="filterBuilder.isForCurrentEvent(props.filter)"
10
+ @click="
11
+ filterBuilder.selectedFilters.includes(filter.id)
12
+ ? filterBuilder.unselectFilter(filter.id)
13
+ : filterBuilder.selectFilter(filter.id)
14
+ "
15
+ />
20
16
  </div>
21
- <div class="expanded-content mt-4 mx-2 sm:mx-0" v-if="filterBuilder.expandedFilter === filter.id">
17
+ <div @click="handleFilterClick(filter.id)" v-if="filterBuilder.expandedFilter === filter.id">
22
18
  <!-- NORMAL -->
23
- <div v-if="filterType === 2" class="space-y-2">
24
- <div class="info-row">
25
- <span class="label">Section:</span>
26
- <span class="value">{{ filter?.section }}</span>
27
- </div>
28
- <div class="info-row" v-if="filter.rows?.length">
29
- <span class="label">Rows:</span>
30
- <span class="value">{{ filter?.rows?.join(", ") }}</span>
31
- </div>
32
- <div class="info-row">
33
- <span class="label">Event:</span>
34
- <span class="value">{{ filter?.event || filter?.eventId || "-" }}</span>
35
- </div>
19
+ <div v-if="filterType === 2">
20
+ <p>Section: {{ filter?.section }}</p>
21
+ <p v-if="filter.rows?.length">Rows: {{ filter?.rows?.join(", ") }}</p>
22
+ <p>Event: {{ filter?.event || filter?.eventId || "-" }}</p>
36
23
  </div>
37
24
  <!-- NORMAL_FIRSTROW -->
38
- <div v-if="filterType === 3" class="space-y-2">
39
- <div class="info-row">
40
- <span class="label">Section:</span>
41
- <span class="value">{{ filter?.section }}</span>
42
- </div>
43
- <div class="info-row">
44
- <span class="label">Row:</span>
45
- <span class="value">{{ filter?._row }}</span>
46
- </div>
47
- <div class="info-row">
48
- <span class="label">Event:</span>
49
- <span class="value">{{ filter?.event || "-" }}</span>
50
- </div>
25
+ <div v-if="filterType === 3">
26
+ <p>Section: {{ filter?.section }}</p>
27
+ <p>Row: {{ filter?._row }}</p>
28
+ <p>Event: {{ filter?.event || "-" }}</p>
51
29
  </div>
52
30
  <!-- CATCH_ALL_GA -->
53
31
  <div v-if="filterType === 1">
54
32
  <button
55
- @click.stop="update({ buyAny: true, floor: false, generalAdmission: false })"
56
- class="conversion-btn"
33
+ @click="update({ buyAny: true, floor: false, generalAdmission: false })"
34
+ class="border-2 rounded border-dark-550 px-1 h-8 my-2 text-gray bg-dark-550 overflow-hidden"
57
35
  >
58
- <component :is="'WildcardIcon'" class="w-4 h-4" />
59
36
  Convert to wildcard
60
37
  </button>
61
38
  </div>
62
39
  <!-- CATCH_ALL -->
63
40
  <div v-if="filterType === 0">
64
41
  <button
65
- @click.stop="update({ floor: true, buyAny: false, generalAdmission: false })"
66
- class="conversion-btn"
42
+ @click="update({ floor: true, buyAny: false, generalAdmission: false })"
43
+ class="border-2 rounded border-dark-550 px-1 h-8 text-gray my-2 bg-dark-550 overflow-hidden"
67
44
  >
68
- <component :is="'BoxIcon'" class="w-4 h-4" />
69
45
  Convert to Floor Wildcard
70
46
  </button>
71
47
  </div>
72
48
  <!-- CATCH_ALL_FLOOR -->
73
49
  <div v-if="filterType === 5">
74
50
  <button
75
- @click.stop="update({ generalAdmission: true, floor: false, buyAny: false })"
76
- class="conversion-btn"
51
+ @click="update({ generalAdmission: true, floor: false, buyAny: false })"
52
+ class="border-2 rounded border-dark-550 px-1 h-8 text-gray my-2 bg-dark-550 overflow-hidden"
77
53
  >
78
- <component :is="'GroupIcon'" class="w-4 h-4" />
79
54
  Convert to GA Wildcard
80
55
  </button>
81
56
  </div>
82
57
  </div>
83
- <div v-if="filterBuilder.expandedFilter === filter.id" class="controls-section mt-4 mx-2 sm:mx-0">
84
- <div class="flex flex-col sm:flex-row flex-wrap gap-3 sm:items-center">
85
- <div class="flex items-center gap-2 control-group">
86
- <Checkbox :toggled="filter.exclude || false" @valueUpdate="handleExcludeClick" />
87
- <label class="text-sm font-medium">Excluded</label>
88
- </div>
89
- <div class="flex items-center gap-2 control-group">
90
- <label class="text-xs whitespace-nowrap text-light-400">Min:</label>
91
- <input
92
- type="number"
93
- :value="filter.minPrice"
94
- @change="
95
- (e) =>
96
- update(
97
- {
98
- minPrice: e.target.value - 0
99
- },
100
- true
101
- )
102
- "
103
- placeholder="0"
104
- class="filter-input w-16 sm:w-20"
105
- />
106
- </div>
107
- <div class="flex items-center gap-2 control-group">
108
- <label class="text-xs whitespace-nowrap text-light-400">Max:</label>
109
- <input
110
- type="number"
111
- :value="filter.maxPrice"
112
- @change="
113
- (e) =>
114
- update(
115
- {
116
- maxPrice: e.target.value - 0
117
- },
118
- true
119
- )
120
- "
121
- placeholder="1000"
122
- class="filter-input w-16 sm:w-20"
123
- />
124
- </div>
125
- <div class="control-group">
126
- <PriceSortToggle
127
- :current="filter.priceSort"
128
- @change="(e) => props.filterBuilder.replaceById(filter.id, { priceSort: e })"
129
- />
130
- </div>
58
+ <div v-if="filterBuilder.expandedFilter === filter.id" class="flex gap-2 items-center">
59
+ <div class="flex items-center gap-2">
60
+ <Checkbox :toggled="filter.exclude || false" @valueUpdate="handleExcludeClick" />
61
+ <p>Excluded</p>
131
62
  </div>
63
+ <input
64
+ type="text"
65
+ :value="filter.minPrice"
66
+ @change="
67
+ (e) =>
68
+ update(
69
+ {
70
+ minPrice: e.target.value - 0
71
+ },
72
+ true
73
+ )
74
+ "
75
+ placeholder="min"
76
+ class="w-16 bg-dark-500 border-2 rounded border-dark-550 px-1 h-8"
77
+ />
78
+ <input
79
+ type="number"
80
+ :value="filter.maxPrice"
81
+ @change="
82
+ (e) =>
83
+ update(
84
+ {
85
+ maxPrice: e.target.value - 0
86
+ },
87
+ true
88
+ )
89
+ "
90
+ placeholder="max"
91
+ class="w-16 bg-dark-500 border-2 rounded border-dark-550 px-1 h-8"
92
+ />
93
+ <PriceSortToggle
94
+ :current="filter.priceSort"
95
+ @change="(e) => props.filterBuilder.replaceById(filter.id, { priceSort: e })"
96
+ />
132
97
  </div>
133
98
  </div>
134
- <div class="col-span-3 sm:col-span-2 flex justify-end items-center gap-1 sm:gap-2">
135
- <div class="drag-handle handle filter-action-btn drag-btn cursor-grab active:cursor-grabbing" title="Drag to reorder">
136
- <MenuIcon class="w-3 h-3 sm:w-4 sm:h-4" />
137
- </div>
138
- <button
139
- @click="filterBuilder.deleteFilterById(filter.id)"
140
- class="delete-btn filter-action-btn"
141
- title="Delete filter"
142
- >
143
- <TrashIcon class="w-3 h-3 sm:w-4 sm:h-4" />
144
- </button>
99
+ <div class="col-span-1 flex justify-self-end m-1 items-center gap-2">
100
+ <MenuIcon class="scale-75 cursor-grab handle" />
101
+ <TrashIcon class="cursor-pointer" @click="filterBuilder.deleteFilterById(filter.id)" />
145
102
  </div>
146
103
  </div>
147
104
  </div>
@@ -156,7 +113,7 @@ import { useUIStore } from "@/stores/ui";
156
113
  const ui = useUIStore();
157
114
 
158
115
  // eslint-disable-next-line no-unused-vars
159
- import { UpIcon, DownIcon, ReloadIcon, TrashIcon, MenuIcon, WildcardIcon, BoxIcon, GroupIcon, FilterIcon, StadiumIcon } from "@/components/icons";
116
+ import { UpIcon, DownIcon, ReloadIcon, TrashIcon, MenuIcon } from "@/components/icons";
160
117
 
161
118
  let isAllRows = ref(false);
162
119
 
@@ -179,46 +136,6 @@ const handleFilterClick = (id) => {
179
136
  };
180
137
 
181
138
  const filterTypes = props.filterBuilder.filterTypes;
182
-
183
- const getFilterIcon = () => {
184
- switch (filterType.value) {
185
- case filterTypes.CATCH_ALL:
186
- return WildcardIcon;
187
- case filterTypes.CATCH_ALL_GA:
188
- return GroupIcon;
189
- case filterTypes.CATCH_ALL_FLOOR:
190
- return BoxIcon;
191
- case filterTypes.NORMAL:
192
- case filterTypes.NORMAL_FIRSTROW:
193
- return StadiumIcon;
194
- default:
195
- return FilterIcon;
196
- }
197
- };
198
-
199
- const getFilterSubtitle = () => {
200
- switch (filterType.value) {
201
- case filterTypes.NORMAL: {
202
- const selectedRowAmount = filter.value?.rows?.length;
203
- if (filter.value?.rows?.length && selectedRowAmount <= 5) {
204
- return `Rows: ${filter.value.rows.join(", ")}`;
205
- } else if (selectedRowAmount > 5) {
206
- return `${selectedRowAmount} rows selected`;
207
- }
208
- return selectedRowAmount === 0 ? "Full section" : null;
209
- }
210
- case filterTypes.NORMAL_FIRSTROW:
211
- return `First row in ${filter.value.section}`;
212
- case filterTypes.CATCH_ALL:
213
- return "Matches all tickets";
214
- case filterTypes.CATCH_ALL_GA:
215
- return "Matches all GA tickets";
216
- case filterTypes.CATCH_ALL_FLOOR:
217
- return "Matches all floor tickets";
218
- default:
219
- return null;
220
- }
221
- };
222
139
  const getFilterTitle = () => {
223
140
  const excluded = filter.value.exclude ? "[BL]" : "";
224
141
  switch (filterType.value) {
@@ -235,7 +152,7 @@ const getFilterTitle = () => {
235
152
  return `${filter.value.section} (first row) ${excluded}`;
236
153
  case filterTypes.NORMAL: {
237
154
  const selectedRowAmount = filter.value?.rows?.length;
238
- const totalRowAmount = document.querySelectorAll(`path[section='${filter.value.section}']`)?.length;
155
+ const totalRowAmount = document.querySelectorAll(`path[sectionname='${filter.value.section}']`)?.length;
239
156
  isAllRows.value = selectedRowAmount === totalRowAmount || !filter.value?.rows?.length;
240
157
 
241
158
  if (isAllRows.value) return `${filter.value.section} (full section) ${excluded}`;
@@ -285,137 +202,3 @@ props.filterBuilder.onUpdate(() => {
285
202
  // else if (newData.id === filter.value.id) update(newData);
286
203
  });
287
204
  </script>
288
-
289
- <style scoped>
290
- .filter-card {
291
- @apply bg-dark-500 border-dark-550 relative;
292
- border: 1px solid rgba(61, 62, 68, 0.3);
293
- margin-bottom: 8px;
294
- transition: all 0.15s ease-out;
295
- }
296
-
297
- .filter-card:hover:not(.expanded-filter) {
298
- border-color: rgba(61, 62, 68, 0.6);
299
- background-color: rgba(46, 47, 52, 0.8);
300
- }
301
-
302
- .expanded-filter:hover {
303
- border-color: rgba(61, 62, 68, 0.8);
304
- }
305
-
306
- .filter-card + .filter-card {
307
- border-top: 1px solid rgba(61, 62, 68, 0.2);
308
- }
309
-
310
- .filter-type-badge {
311
- @apply w-6 h-6 sm:w-8 sm:h-8 rounded-full bg-dark-400 flex items-center justify-center flex-shrink-0;
312
- }
313
-
314
- .info-row {
315
- @apply flex justify-between items-center;
316
- }
317
-
318
- .label {
319
- @apply text-xs font-medium text-light-400;
320
- }
321
-
322
- .value {
323
- @apply text-sm text-white;
324
- }
325
-
326
- .conversion-btn {
327
- @apply flex items-center gap-2 px-3 py-2 bg-dark-400 hover:bg-dark-300 border border-dark-550 rounded-md text-sm font-medium transition-colors;
328
- }
329
-
330
- .control-group {
331
- @apply flex items-center gap-2;
332
- }
333
-
334
- .filter-input {
335
- @apply border border-dark-550 rounded px-2 py-1.5 text-sm text-white focus:outline-none transition-colors;
336
- background-color: rgba(35, 36, 41, 0.9);
337
- color: white;
338
- }
339
-
340
- .filter-input:focus {
341
- border-color: #44454b;
342
- background-color: rgba(46, 47, 52, 0.9);
343
- }
344
-
345
- .filter-input::placeholder {
346
- color: #9CA3AF;
347
- }
348
-
349
- .filter-action-btn {
350
- @apply flex items-center justify-center rounded-full transition-all duration-200 border-2 border-transparent;
351
- width: 28px;
352
- height: 28px;
353
- color: #9CA3AF;
354
- background-color: rgba(35, 36, 41, 0.8);
355
- backdrop-filter: blur(4px);
356
- }
357
-
358
- @media (min-width: 640px) {
359
- .filter-action-btn {
360
- width: 32px;
361
- height: 32px;
362
- }
363
- }
364
-
365
- .filter-action-btn:hover {
366
- color: white;
367
- transform: scale(1.15);
368
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
369
- }
370
-
371
- .drag-btn:hover {
372
- background-color: rgba(68, 69, 75, 0.8);
373
- border-color: #44454b;
374
- }
375
-
376
- .delete-btn:hover {
377
- background-color: rgba(239, 68, 68, 0.8);
378
- border-color: #F87171;
379
- color: #F87171;
380
- }
381
-
382
- .drag-handle.sortable-chosen {
383
- border: 1px solid #44454b;
384
- background-color: rgba(68, 69, 75, 0.2);
385
- }
386
-
387
- .filter-card.sortable-ghost {
388
- @apply opacity-50;
389
- border: 1px solid #44454b;
390
- background-color: rgba(68, 69, 75, 0.1);
391
- }
392
-
393
- .filter-card.sortable-drag {
394
- @apply shadow-lg transform rotate-2 scale-105;
395
- }
396
-
397
- .expanded-filter {
398
- border-left: 4px solid #44454b;
399
- background-color: rgba(26, 27, 30, 0.95);
400
- }
401
-
402
- .expanded-content {
403
- background-color: rgba(26, 27, 30, 0.95);
404
- border-radius: 6px;
405
- padding: 12px;
406
- }
407
-
408
- .controls-section {
409
- background-color: rgba(26, 27, 30, 0.98);
410
- border-radius: 6px;
411
- padding: 12px;
412
- }
413
-
414
- .excluded-filter {
415
- border-left: 4px solid #EE8282;
416
- }
417
-
418
- .normal-filter {
419
- border-left: 4px solid transparent;
420
- }
421
- </style>
@@ -1,31 +1,35 @@
1
1
  <template>
2
2
  <Modal class="overflow-y-scroll max-w-screen">
3
- <template #header>
4
- Filter JSON data
5
- <FilterIcon class="ml-4" />
6
- </template>
7
-
8
- <div class="my-3">
9
- <div class="editor-container">
10
- <div class="editor-wrapper">
11
- <pre ref="codeDisplay" class="language-json code-highlight"></pre>
12
- <textarea
13
- ref="codeEditor"
14
- v-model="text"
15
- class="code-editor"
16
- spellcheck="false"
17
- @scroll="syncScroll"
18
- @input="highlightCode"
19
- @keydown.tab.prevent="handleTab"></textarea>
20
- </div>
21
- </div>
22
- <p class="text-red-400 text-bold mt-2">{{ errorMessage }}</p>
3
+ <template #header> Filter JSON data <FilterIcon class="ml-4" /> </template>
4
+
5
+ <div class="max-h-80 overflow-auto hidden-scrollbars z-0 my-3">
6
+ <CodeEditor
7
+ v-model="text"
8
+ width="100%"
9
+ spellcheck="false"
10
+ :hide_header="true"
11
+ :language_selector="false"
12
+ :languages="[['json', 'JSON']]"
13
+ theme="dark"
14
+ class=""
15
+ ></CodeEditor>
16
+ <p class="text-red-400 text-bold">{{ errorMessage }}</p>
23
17
  </div>
24
18
 
25
19
  <div class="ml-auto flex">
26
- <button class="btn-action" @click="save()">Apply</button>
27
-
28
- <button class="btn-action ml-2" @click="done()">Close</button>
20
+ <button
21
+ class="button-default hover:opacity-70 active:opacity-50 bg-dark-400 w-48 text-xs flex items-center justify-center gap-x-2"
22
+ @click="save()"
23
+ >
24
+ Apply
25
+ </button>
26
+
27
+ <button
28
+ class="button-default hover:opacity-70 active:opacity-50 bg-dark-400 w-48 text-xs flex items-center justify-center gap-x-2 ml-2"
29
+ @click="done()"
30
+ >
31
+ Close
32
+ </button>
29
33
  </div>
30
34
  </Modal>
31
35
  </template>
@@ -35,165 +39,20 @@
35
39
  @apply flex;
36
40
  }
37
41
  }
38
-
39
- /* Prism.js syntax highlighting styles */
40
- .editor-container {
41
- position: relative;
42
- min-height: 300px;
43
- max-height: 500px;
44
- border-radius: 8px;
45
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
46
- overflow: hidden;
47
- background-color: #2e2f34;
48
- }
49
-
50
- .editor-wrapper {
51
- position: relative;
52
- width: 100%;
53
- height: 100%;
54
- min-height: 300px;
55
- max-height: 500px;
56
- }
57
-
58
- .code-editor {
59
- width: 100%;
60
- height: 100%;
61
- min-height: 300px;
62
- max-height: 500px;
63
- background-color: transparent;
64
- /* Make text completely transparent */
65
- color: rgba(0, 0, 0, 0);
66
- caret-color: #e2e8f0;
67
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
68
- padding: 12px;
69
- border: none;
70
- resize: none;
71
- font-size: 14px;
72
- line-height: 1.6;
73
- tab-size: 4;
74
- outline: none;
75
- border: 1px solid #3d3e44;
76
- border-radius: 8px;
77
- z-index: 10;
78
- position: absolute;
79
- top: 0;
80
- left: 0;
81
- right: 0;
82
- bottom: 0;
83
- white-space: pre;
84
- overflow: auto;
85
- }
86
-
87
- .code-highlight {
88
- width: 100%;
89
- height: 100%;
90
- min-height: 300px;
91
- max-height: 500px;
92
- overflow: auto;
93
- white-space: pre;
94
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
95
- font-size: 14px;
96
- line-height: 1.6;
97
- background-color: transparent !important;
98
- pointer-events: none;
99
- z-index: 5;
100
- position: absolute;
101
- top: 0;
102
- left: 0;
103
- right: 0;
104
- bottom: 0;
105
- padding: 12px;
106
- margin: 0;
107
- }
108
42
  </style>
109
43
  <script setup>
110
44
  import Modal from "@/components/ui/Modal.vue";
111
45
  import { FilterIcon } from "@/components/icons";
112
46
  import { useUIStore } from "@/stores/ui";
113
- import { ref, computed, onMounted, nextTick } from "vue";
47
+ import { ref, computed } from "vue";
48
+ import CodeEditor from "simple-code-editor";
114
49
 
115
50
  const props = defineProps({
116
51
  filter: Object
117
52
  });
118
53
 
119
54
  const ui = useUIStore();
120
- const text = ref(JSON.stringify(props.filter.out(), null, 4));
121
- const codeEditor = ref(null);
122
- const codeDisplay = ref(null);
123
-
124
- // Function to highlight code using Prism
125
- const highlightCode = () => {
126
- if (!codeDisplay.value || !codeEditor.value) return;
127
-
128
- // Ensure Prism is available
129
- if (typeof Prism === "undefined") {
130
- console.error("Prism is not loaded");
131
- return;
132
- }
133
-
134
- // Use requestAnimationFrame for smoother updates
135
- requestAnimationFrame(() => {
136
- try {
137
- // Update the pre element with highlighted HTML
138
- const highlighted = Prism.highlight(text.value || "", Prism.languages.json, "json");
139
- codeDisplay.value.innerHTML = highlighted;
140
- codeDisplay.value.className = "language-json code-highlight";
141
- } catch (e) {
142
- console.error("Highlight error:", e);
143
- // Fallback to plain text if highlighting fails
144
- codeDisplay.value.textContent = text.value || "";
145
- }
146
-
147
- // Ensure scroll positions are synced after highlighting
148
- syncScroll();
149
- });
150
- };
151
-
152
- // Function to sync scrolling between textarea and highlighted code
153
- const syncScroll = () => {
154
- if (!codeDisplay.value || !codeEditor.value) return;
155
-
156
- // Synchronize scrolling between the textarea and the highlighted code
157
- requestAnimationFrame(() => {
158
- codeDisplay.value.scrollTop = codeEditor.value.scrollTop;
159
- codeDisplay.value.scrollLeft = codeEditor.value.scrollLeft;
160
- });
161
- };
162
-
163
- // Function to handle tab key press in the editor
164
- const handleTab = (e) => {
165
- const textarea = codeEditor.value;
166
- const start = textarea.selectionStart;
167
- const end = textarea.selectionEnd;
168
-
169
- // Insert 4 spaces at cursor position
170
- const spaces = " ";
171
- text.value = text.value.substring(0, start) + spaces + text.value.substring(end);
172
-
173
- // Move cursor position after the inserted tab
174
- nextTick(() => {
175
- textarea.selectionStart = textarea.selectionEnd = start + spaces.length;
176
- highlightCode();
177
- });
178
- };
179
-
180
- // Initialize syntax highlighting
181
- onMounted(() => {
182
- // Apply highlighting when the component is mounted
183
- nextTick(() => {
184
- highlightCode();
185
-
186
- // Ensure scroll synchronization on initial load
187
- if (codeEditor.value && codeDisplay.value) {
188
- syncScroll();
189
- }
190
-
191
- // Focus the editor
192
- if (codeEditor.value) {
193
- codeEditor.value.focus();
194
- }
195
- });
196
- });
55
+ const text = ref(JSON.stringify(props.filter.out(), null, "\t"));
197
56
 
198
57
  function done() {
199
58
  ui.toggleModal("");