@necrolab/dashboard 0.5.16 → 0.5.18

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 (66) hide show
  1. package/package.json +1 -1
  2. package/src/App.vue +14 -480
  3. package/src/assets/css/components/buttons.scss +12 -68
  4. package/src/assets/css/components/headers.scss +1 -1
  5. package/src/assets/css/components/utilities.scss +91 -16
  6. package/src/assets/css/main.scss +22 -95
  7. package/src/components/Auth/LoginForm.vue +2 -2
  8. package/src/components/Console/ConsoleToolbar.vue +123 -0
  9. package/src/components/Editors/Account/Account.vue +4 -2
  10. package/src/components/Editors/Account/AccountView.vue +12 -37
  11. package/src/components/Editors/Account/CreateAccount.vue +3 -11
  12. package/src/components/Editors/AdminFileEditor.vue +421 -0
  13. package/src/components/Editors/Profile/CreateProfile.vue +4 -20
  14. package/src/components/Editors/Profile/Profile.vue +5 -4
  15. package/src/components/Editors/Profile/ProfileView.vue +13 -38
  16. package/src/components/Editors/ProxyFileEditor.vue +178 -0
  17. package/src/components/Filter/Filter.vue +6 -6
  18. package/src/components/Filter/FilterPreview.vue +4 -12
  19. package/src/components/Filter/PriceSortToggle.vue +1 -1
  20. package/src/components/Tasks/QuickSettings.vue +5 -5
  21. package/src/components/Tasks/Stats.vue +1 -1
  22. package/src/components/Tasks/Task.vue +5 -8
  23. package/src/components/Tasks/TaskView.vue +2 -1
  24. package/src/components/Tasks/ViewTask.vue +2 -2
  25. package/src/components/icons/index.js +0 -4
  26. package/src/components/ui/ActionButtonGroup.vue +2 -2
  27. package/src/components/ui/BalanceIndicator.vue +3 -3
  28. package/src/components/ui/EnableDisableToggle.vue +2 -2
  29. package/src/components/ui/FormField.vue +2 -2
  30. package/src/components/ui/IconLabel.vue +2 -2
  31. package/src/components/ui/InfoRow.vue +4 -4
  32. package/src/components/ui/Modal.vue +83 -9
  33. package/src/components/ui/Navbar.vue +3 -3
  34. package/src/components/ui/ReadonlyFieldsSection.vue +1 -1
  35. package/src/components/ui/StatusBadge.vue +1 -1
  36. package/src/components/ui/controls/CountryChooser.vue +5 -5
  37. package/src/components/ui/controls/atomic/MultiDropdown.vue +1 -1
  38. package/src/composables/useCodeEditor.js +117 -0
  39. package/src/composables/useDropdownPosition.js +0 -2
  40. package/src/composables/useEnableDisable.js +6 -0
  41. package/src/composables/useFilterCSS.js +71 -0
  42. package/src/composables/useFormValidation.js +92 -0
  43. package/src/composables/useGetAllTags.js +9 -0
  44. package/src/composables/useIOSViewportHandling.js +76 -0
  45. package/src/composables/useNotchHandling.js +306 -0
  46. package/src/composables/useTableRender.js +23 -0
  47. package/src/composables/useZoomPrevention.js +96 -0
  48. package/src/constants/tableLayout.js +14 -0
  49. package/src/libs/utils/array.js +1 -3
  50. package/src/libs/utils/dataGeneration.js +1 -1
  51. package/src/libs/utils/string.js +1 -26
  52. package/src/libs/utils/validation.js +2 -26
  53. package/src/stores/connection.js +0 -25
  54. package/src/stores/ui.js +21 -35
  55. package/src/utils/tableHelpers.js +1 -0
  56. package/src/views/Accounts.vue +9 -17
  57. package/src/views/Console.vue +15 -92
  58. package/src/views/Editor.vue +39 -938
  59. package/src/views/FilterBuilder.vue +9 -97
  60. package/src/views/Profiles.vue +9 -17
  61. package/src/views/Tasks.vue +4 -4
  62. package/src/assets/img/background.svg.backup +0 -11
  63. package/src/components/icons/SquareCheck.vue +0 -12
  64. package/src/components/icons/SquareUncheck.vue +0 -12
  65. package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
  66. /package/src/components/Editors/Account/{AccountCreator.vue → CreateAccountBatch.vue} +0 -0
@@ -1,6 +1,5 @@
1
1
  <template>
2
2
  <div>
3
- <!-- Heading -->
4
3
  <div class="page-header" style="padding-bottom: 1.25rem;">
5
4
  <div class="page-header-card">
6
5
  <img src="@/assets/img/pencil.svg" />
@@ -9,200 +8,45 @@
9
8
  </div>
10
9
 
11
10
  <div class="bg-dark-400 border-2 rounded-lg shadow-sm p-2" style="border-color: var(--color-border);">
12
- <!-- Admin Editor Section - Hidden when proxy editor is open -->
13
- <div v-if="ui.profile.admin" class="admin-editor-section" :class="{ 'landscape-hidden': currentProxyList }">
14
- <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Admin Editor</h5>
15
- <div class="flex flex-wrap items-center gap-3 w-full">
16
- <div class="unified-search-group flex w-full flex-shrink-0 dropdown-container z-dropdown-high lg:w-auto lg:min-w-64">
17
- <Dropdown
18
- class="admin-file-dropdown flex-1"
19
- default="Select a file"
20
- :onClick="(f) => loadFile(f)"
21
- :options="availableFiles"
22
- :allowDefault="false"
23
- :includeAdjacentButtons="true"
24
- rightAmount="right-1" />
25
- <button
26
- class="refresh-button flex items-center justify-center w-9 h-10 flex-shrink-0 text-white bg-dark-400 transition-all duration-150 hover:bg-dark-450"
27
- @click="loadAvailableFiles()"
28
- title="Refresh file list">
29
- <ReloadIcon class="refresh-icon h-4 w-4" />
30
- </button>
31
- </div>
32
- <!-- Admin editor buttons moved here -->
33
- <div v-if="textEditorVisible" class="flex gap-1.5 ml-auto">
34
- <button
35
- class="button-default bg-dark-400"
36
- @click="format()"
37
- v-if="isJsonFile()"
38
- title="Format JSON">
39
- <svg
40
- xmlns="http://www.w3.org/2000/svg"
41
- width="16"
42
- height="16"
43
- viewBox="0 0 24 24"
44
- fill="none"
45
- stroke="currentColor"
46
- stroke-width="2"
47
- stroke-linecap="round"
48
- stroke-linejoin="round">
49
- <path d="M21 10H7" />
50
- <path d="M21 6H3" />
51
- <path d="M21 14H3" />
52
- <path d="M21 18H7" />
53
- </svg>
54
- </button>
55
- <button class="button-default bg-dark-400" @click="saveAll" title="Save File">
56
- <svg
57
- xmlns="http://www.w3.org/2000/svg"
58
- width="16"
59
- height="16"
60
- viewBox="0 0 24 24"
61
- fill="none"
62
- stroke="currentColor"
63
- stroke-width="2"
64
- stroke-linecap="round"
65
- stroke-linejoin="round">
66
- <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
67
- <polyline points="17 21 17 13 7 13 7 21" />
68
- <polyline points="7 3 7 8 15 8" />
69
- </svg>
70
- </button>
71
- <button class="button-default bg-dark-400" @click="closeFile" title="Close File">
72
- <svg
73
- xmlns="http://www.w3.org/2000/svg"
74
- width="16"
75
- height="16"
76
- viewBox="0 0 24 24"
77
- fill="none"
78
- stroke="currentColor"
79
- stroke-width="2"
80
- stroke-linecap="round"
81
- stroke-linejoin="round">
82
- <line x1="18" y1="6" x2="6" y2="18"></line>
83
- <line x1="6" y1="6" x2="18" y2="18"></line>
84
- </svg>
85
- </button>
86
- </div>
87
- </div>
11
+ <AdminFileEditor
12
+ v-if="ui.profile.admin"
13
+ :availableFiles="availableFiles"
14
+ :currentFile="currentFile"
15
+ v-model:content="currentContent"
16
+ :isHidden="!!currentProxyList"
17
+ :logger="ui.logger"
18
+ @load-file="loadFile"
19
+ @refresh-files="loadAvailableFiles"
20
+ @save="saveAll"
21
+ @close="closeFile"
22
+ @format="format" />
88
23
 
89
- <transition name="fade">
90
- <div v-if="textEditorVisible" class="my-3 relative">
91
- <div class="pb-4">
92
- <!-- Syntax Highlighting Editor -->
93
- <div class="editor-container overflow-hidden rounded-lg shadow-card bg-dark-350" :class="{ 'has-error': errorMessage }">
94
- <div class="relative w-full h-full">
95
- <pre ref="codeDisplay" class="code-highlight language-json"></pre>
96
- <textarea
97
- ref="codeEditor"
98
- v-model="currentContent"
99
- class="code-editor"
100
- spellcheck="false"
101
- @scroll="syncScroll"
102
- @input="highlightCode"
103
- @keydown.tab.prevent="handleTab"></textarea>
104
- </div>
105
- </div>
106
- </div>
107
-
108
- <div v-if="errorMessage" class="error-container">
109
- <div class="error-icon">
110
- <svg
111
- width="16"
112
- height="16"
113
- viewBox="0 0 24 24"
114
- fill="none"
115
- stroke="currentColor"
116
- stroke-width="2">
117
- <circle cx="12" cy="12" r="10" />
118
- <line x1="15" y1="9" x2="9" y2="15" />
119
- <line x1="9" y1="9" x2="15" y2="15" />
120
- </svg>
121
- </div>
122
- <div class="error-content">
123
- <div class="error-title">JSON Syntax Error</div>
124
- <div class="error-text">{{ errorMessage }}</div>
125
- </div>
126
- </div>
127
- </div>
128
- </transition>
129
- </div>
130
-
131
- <!-- Section Divider - Show when both editors are closed -->
132
24
  <div v-if="ui.profile.admin && !textEditorVisible && !currentProxyList" class="section-divider" />
133
25
 
134
- <!-- Proxy Editor Section - Hidden when admin editor is open -->
135
- <div class="proxy-editor-section" :class="{ 'landscape-hidden': textEditorVisible }">
136
- <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Proxy Editor</h5>
137
- <div class="flex flex-wrap items-center gap-3 w-full mb-1">
138
- <div class="w-full lg:w-64 flex flex-shrink-0 dropdown-container z-dropdown">
139
- <div class="relative flex-grow">
140
- <Dropdown
141
- class="bg-dark-500 border-2 border-dark-550 proxy-file-dropdown w-full"
142
- :default="ui.profile.proxyList?.checkout || proxyLists[0]"
143
- :onClick="loadProxies"
144
- :options="proxyLists"
145
- :allowDefault="false"
146
- :includeAdjacentButtons="true"
147
- rightAmount="right-1" />
148
- </div>
149
- </div>
150
- <!-- Proxy save button moved here -->
151
- <div v-if="currentProxyList" class="flex gap-1.5 ml-auto">
152
- <button class="button-default bg-dark-400" @click="saveProxies" title="Save Proxies">
153
- <svg
154
- xmlns="http://www.w3.org/2000/svg"
155
- width="16"
156
- height="16"
157
- viewBox="0 0 24 24"
158
- fill="none"
159
- stroke="currentColor"
160
- stroke-width="2"
161
- stroke-linecap="round"
162
- stroke-linejoin="round">
163
- <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
164
- <polyline points="17 21 17 13 7 13 7 21" />
165
- <polyline points="7 3 7 8 15 8" />
166
- </svg>
167
- </button>
168
- <button class="button-default bg-dark-400" @click="closeProxyFile" title="Close File">
169
- <svg
170
- xmlns="http://www.w3.org/2000/svg"
171
- width="16"
172
- height="16"
173
- viewBox="0 0 24 24"
174
- fill="none"
175
- stroke="currentColor"
176
- stroke-width="2"
177
- stroke-linecap="round"
178
- stroke-linejoin="round">
179
- <line x1="18" y1="6" x2="6" y2="18"></line>
180
- <line x1="6" y1="6" x2="18" y2="18"></line>
181
- </svg>
182
- </button>
183
- </div>
184
- </div>
185
- <!-- Textarea -->
186
- <transition name="fade">
187
- <div v-if="currentProxyList" class="relative my-3">
188
- <div class="pb-4">
189
- <div class="proxy-editor-container table-component">
190
- <textarea v-model="proxyContent" class="proxy-editor" spellcheck="false"></textarea>
191
- </div>
192
- </div>
193
- </div>
194
- </transition>
195
- </div>
26
+ <ProxyFileEditor
27
+ :proxyLists="proxyLists"
28
+ :currentProxyList="currentProxyList"
29
+ v-model:proxyContent="proxyContent"
30
+ :defaultProxyList="ui.profile.proxyList?.checkout || proxyLists[0]"
31
+ :isHidden="textEditorVisible"
32
+ @load-proxies="loadProxies"
33
+ @save-proxies="saveProxies"
34
+ @close-proxy="closeProxyFile" />
196
35
  </div>
197
36
  </div>
198
37
  </template>
38
+
199
39
  <script setup>
200
- import { ref, computed, watch, onMounted, nextTick } from "vue";
201
- import { ReloadIcon } from "@/components/icons";
40
+ import { ref } from "vue";
202
41
  import { useUIStore } from "@/stores/ui";
203
- import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
204
42
  import { getProxyLists, getProxyFile, getQuickConfig, sendQuickConfig } from "@/stores/requests";
205
43
  import { DEBUG } from "@/utils/debug";
44
+ import { useCodeEditor } from "@/composables/useCodeEditor";
45
+ import AdminFileEditor from "@/components/Editors/AdminFileEditor.vue";
46
+ import ProxyFileEditor from "@/components/Editors/ProxyFileEditor.vue";
47
+
48
+ const ui = useUIStore();
49
+ const { formatJSON } = useCodeEditor(ui.logger);
206
50
 
207
51
  const loadFromApi = async (path) => {
208
52
  const res = await fetch(path);
@@ -211,175 +55,29 @@ const loadFromApi = async (path) => {
211
55
 
212
56
  const availableFiles = ref([]);
213
57
  if (DEBUG) availableFiles.value.push("used-codes.json", "proxies.txt", "toMonitor.txt", "test.txt");
58
+
214
59
  const currentFile = ref("");
215
60
  const textEditorVisible = ref(false);
216
61
  const currentContent = ref("");
217
62
  const proxyContent = ref("");
218
63
  const proxyLists = ref(["loading..."]);
219
64
  const currentProxyList = ref("");
220
- const ui = useUIStore();
221
-
222
- // References to editor elements
223
- const codeEditor = ref(null);
224
- const codeDisplay = ref(null);
225
65
 
226
66
  let previous = {
227
67
  proxyContent: { list: ui.profile.proxyList, content: "" },
228
68
  currentContent: { file: "", content: "" }
229
69
  };
230
70
 
231
- const errorMessage = computed(() => {
232
- if (!currentFile.value.endsWith(".json")) return;
233
-
234
- let err;
235
- try {
236
- JSON.parse(currentContent.value);
237
- } catch (e) {
238
- err = e;
239
- }
240
- return err?.message;
241
- });
242
-
243
71
  const loadAvailableFiles = async () => (availableFiles.value = await loadFromApi("/api/json-files"));
244
72
 
245
73
  const isJsonFile = () => currentFile.value.endsWith(".json");
246
74
 
247
- // Debounce timer for highlighting
248
- let highlightTimer = null;
249
-
250
- // Function to highlight code using Prism
251
- const highlightCode = () => {
252
- if (!codeDisplay.value || !codeEditor.value) return;
253
-
254
- // Get the language based on file extension
255
- let language = "javascript";
256
-
257
- if (currentFile.value) {
258
- if (currentFile.value.endsWith(".json")) {
259
- language = "json";
260
- } else if (currentFile.value.endsWith(".js")) {
261
- language = "javascript";
262
- } else if (currentFile.value.endsWith(".txt") || currentFile.value.endsWith(".csv")) {
263
- language = "text";
264
- if (codeDisplay.value) {
265
- codeDisplay.value.textContent = currentContent.value || "";
266
- codeDisplay.value.className = "language-text code-highlight";
267
- }
268
- syncScroll();
269
- return;
270
- }
271
- }
272
-
273
- // Debounce to avoid excessive highlighting
274
- if (highlightTimer) clearTimeout(highlightTimer);
275
-
276
- highlightTimer = setTimeout(() => {
277
- requestAnimationFrame(() => {
278
- try {
279
- // Check if Prism and language are available
280
- if (typeof window.Prism === "undefined" || !window.Prism.languages?.[language]) {
281
- if (codeDisplay.value) {
282
- codeDisplay.value.textContent = currentContent.value || "";
283
- codeDisplay.value.className = `language-${language} code-highlight`;
284
- }
285
- syncScroll();
286
- return;
287
- }
288
-
289
- // Highlight the code (Prism.highlight returns sanitized HTML)
290
- const highlighted = window.Prism.highlight(
291
- currentContent.value || "",
292
- window.Prism.languages[language],
293
- language
294
- );
295
-
296
- if (codeDisplay.value) {
297
- codeDisplay.value.innerHTML = highlighted;
298
- codeDisplay.value.className = `language-${language} code-highlight`;
299
- }
300
- } catch (e) {
301
- ui.logger.Error("Highlight error:", e);
302
- if (codeDisplay.value) {
303
- codeDisplay.value.textContent = currentContent.value || "";
304
- }
305
- } finally {
306
- syncScroll();
307
- }
308
- });
309
- }, 50);
310
- };
311
-
312
- // Function to sync scrolling between textarea and highlighted code
313
- const syncScroll = () => {
314
- if (!codeDisplay.value || !codeEditor.value) return;
315
-
316
- // Direct synchronization without RAF for immediate response
317
- codeDisplay.value.scrollTop = codeEditor.value.scrollTop;
318
- codeDisplay.value.scrollLeft = codeEditor.value.scrollLeft;
319
- };
320
-
321
- // Function to handle tab key press in the editor
322
- const handleTab = () => {
323
- const textarea = codeEditor.value;
324
- const start = textarea.selectionStart;
325
- const end = textarea.selectionEnd;
326
-
327
- // Insert 4 spaces at cursor position
328
- const spaces = " ";
329
- currentContent.value = currentContent.value.substring(0, start) + spaces + currentContent.value.substring(end);
330
-
331
- // Move cursor position after the inserted tab
332
- nextTick(() => {
333
- textarea.selectionStart = textarea.selectionEnd = start + spaces.length;
334
- highlightCode();
335
- });
336
- };
337
-
338
- const format = () => {
339
- try {
340
- if (!isJsonFile()) return;
341
-
342
- const formatted = JSON.stringify(JSON.parse(currentContent.value), null, 4);
343
- currentContent.value = formatted;
344
-
345
- // Apply syntax highlighting after formatting
346
- nextTick(() => {
347
- highlightCode();
348
- });
349
- } catch (e) {
350
- ui.logger.Error("Could not format JSON", e);
351
- }
352
- };
353
-
354
- // Watch for content changes to apply syntax highlighting
355
- watch(currentContent, () => {
356
- nextTick(() => {
357
- highlightCode();
358
- });
359
- });
360
-
361
75
  const loadFile = async (f) => {
362
76
  currentFile.value = f;
363
77
  if (DEBUG) {
364
78
  textEditorVisible.value = true;
365
79
  currentProxyList.value = "";
366
80
  currentContent.value = new Array(100).fill(0, 0, 100).join("\n");
367
-
368
- // Update pre element class based on file type
369
- if (codeDisplay.value) {
370
- if (f.endsWith(".json")) {
371
- codeDisplay.value.className = "language-json";
372
- } else if (f.endsWith(".js")) {
373
- codeDisplay.value.className = "language-javascript";
374
- } else {
375
- codeDisplay.value.className = "language-text";
376
- }
377
- }
378
-
379
- // Apply syntax highlighting after content is set
380
- nextTick(() => {
381
- highlightCode();
382
- });
383
81
  return;
384
82
  }
385
83
 
@@ -389,22 +87,6 @@ const loadFile = async (f) => {
389
87
  currentProxyList.value = "";
390
88
  currentContent.value = text;
391
89
  previous.currentContent = { file: currentFile.value, content: text };
392
-
393
- // Update pre element class based on file type
394
- if (codeDisplay.value) {
395
- if (f.endsWith(".json")) {
396
- codeDisplay.value.className = "language-json";
397
- } else if (f.endsWith(".js")) {
398
- codeDisplay.value.className = "language-javascript";
399
- } else {
400
- codeDisplay.value.className = "language-text";
401
- }
402
- }
403
-
404
- // Apply syntax highlighting after content is loaded
405
- nextTick(() => {
406
- highlightCode();
407
- });
408
90
  };
409
91
 
410
92
  const saveDataToAPI = async (data, endpoint, msg, json = true) => {
@@ -460,6 +142,10 @@ const saveAll = async () => {
460
142
  await saveQuickConfig();
461
143
  };
462
144
 
145
+ const format = () => {
146
+ formatJSON(ref(currentContent), currentFile.value, () => {});
147
+ };
148
+
463
149
  const closeFile = () => {
464
150
  currentFile.value = "";
465
151
  currentContent.value = "";
@@ -513,603 +199,18 @@ const loadProxyLists = async () => {
513
199
  else ui.showError("Could not load proxylists");
514
200
  };
515
201
 
516
- // Initialize syntax highlighting
517
- onMounted(() => {
518
- // Apply highlighting when the component is mounted
519
- nextTick(() => {
520
- highlightCode();
521
-
522
- // Ensure scroll synchronization on initial load
523
- if (codeEditor.value && codeDisplay.value) {
524
- syncScroll();
525
- }
526
- });
527
-
528
- // iOS-specific editor fixes with cursor tracking
529
- if (
530
- /iPad|iPhone|iPod/.test(navigator.platform) ||
531
- (navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform))
532
- ) {
533
- const trackCursor = (editor) => {
534
- const scrollToCursor = () => {
535
- setTimeout(() => {
536
- const container = editor.closest(".editor-container") || editor.closest(".proxy-editor-container");
537
- if (container) {
538
- // Get cursor position and scroll to it
539
- const lines = editor.value.substring(0, editor.selectionStart).split("\n");
540
- const lineHeight = 24; // Approximate line height
541
- const cursorY = lines.length * lineHeight;
542
- const containerHeight = container.clientHeight;
543
-
544
- // Scroll to keep cursor in view
545
- if (cursorY > container.scrollTop + containerHeight - 60) {
546
- container.scrollTop = cursorY - containerHeight + 60;
547
- }
548
- }
549
- }, 100);
550
- };
551
-
552
- editor.addEventListener("focus", scrollToCursor);
553
- editor.addEventListener("input", scrollToCursor);
554
- editor.addEventListener("click", scrollToCursor);
555
- editor.addEventListener("keyup", scrollToCursor);
556
- };
557
-
558
- nextTick(() => {
559
- if (codeEditor.value) {
560
- trackCursor(codeEditor.value);
561
- }
562
- const proxyEditor = document.querySelector(".proxy-editor");
563
- if (proxyEditor) {
564
- trackCursor(proxyEditor);
565
- }
566
- });
567
- }
568
-
569
- // Watch for editor visibility changes
570
- watch(textEditorVisible, (newValue) => {
571
- if (newValue) {
572
- nextTick(() => {
573
- highlightCode();
574
-
575
- // Focus the editor when it becomes visible
576
- if (codeEditor.value) {
577
- codeEditor.value.focus();
578
- }
579
- });
580
- }
581
- });
582
- });
583
-
584
- // No cleanup needed - global iOS handling takes care of everything
585
-
586
202
  if (ui.profile.admin && !DEBUG) loadAvailableFiles();
587
203
  loadProxyLists();
588
204
  </script>
589
- <style lang="scss" scoped>
590
- /* ==========================================================================
591
- Z-INDEX MANAGEMENT - Required for dropdown layering
592
- ========================================================================== */
593
- .z-dropdown-high {
594
- position: relative;
595
- z-index: 200;
596
- }
597
-
598
- .z-dropdown-high .dropdown-menu,
599
- .z-dropdown-high .option-list {
600
- z-index: 250;
601
- }
602
-
603
- .z-dropdown {
604
- position: relative;
605
- z-index: 100;
606
- }
607
-
608
- .z-dropdown .dropdown-menu,
609
- .z-dropdown .option-list {
610
- z-index: 150;
611
- }
612
-
613
- /* ==========================================================================
614
- CONDITIONAL VISIBILITY - Required for editor switching logic
615
- ========================================================================== */
616
- .admin-editor-section.landscape-hidden,
617
- .proxy-editor-section.landscape-hidden {
618
- display: none !important;
619
- }
620
-
621
- /* ==========================================================================
622
- FILE DROPDOWN STYLING
623
- ========================================================================== */
624
- .admin-file-dropdown {
625
- height: 2.5rem !important;
626
- }
627
-
628
- .proxy-file-dropdown {
629
- @apply rounded-lg;
630
- height: 2.5rem !important;
631
- }
632
-
633
- .refresh-button {
634
- height: 2.5rem !important;
635
-
636
- &:hover {
637
- .refresh-icon {
638
- @apply rotate-180;
639
- }
640
- }
641
- }
642
-
643
- .refresh-icon {
644
- @apply transition-transform duration-500;
645
- }
646
-
647
- /* ==========================================================================
648
- EDITOR CONTAINERS
649
- ========================================================================== */
650
- .editor-container {
651
- position: relative;
652
- height: 400px;
653
- max-height: 60vh;
654
- }
655
205
 
656
- .proxy-editor-container {
657
- @apply relative overflow-hidden rounded-lg shadow-card bg-dark-350 flex flex-col;
658
- height: 400px;
659
- max-height: 60vh;
660
- transition: height 0.3s ease;
661
- }
662
-
663
- .proxy-editor-container .pb-4 {
664
- @apply flex-1 flex flex-col;
665
- padding-bottom: 0 !important;
666
- }
667
-
668
- .proxy-editor-container .table-component {
669
- @apply flex-1 flex flex-col;
670
- }
671
-
672
- /* ==========================================================================
673
- EDITOR TEXTAREAS - CRITICAL: Custom font, scrolling behavior
674
- ========================================================================== */
675
- .proxy-editor {
676
- @apply w-full h-full overflow-auto resize-none border-none outline-none bg-transparent p-3 m-0 box-border text-light-300 flex-1;
677
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
678
- font-size: 14px;
679
- line-height: 1.6;
680
- tab-size: 4;
681
- -webkit-overflow-scrolling: touch;
682
- min-height: 100%;
683
- }
684
-
685
- /* EXACT COPY from FilterPreview.vue - Shared base styles for perfect alignment */
686
- .code-editor,
687
- .code-highlight {
688
- position: absolute;
689
- inset: 0;
690
- width: 100%;
691
- height: 100%;
692
- min-height: 300px;
693
- max-height: 500px;
694
- padding: 12px;
695
- margin: 0;
696
- overflow: auto;
697
- white-space: pre;
698
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
699
- font-size: 14px;
700
- line-height: 1.6;
701
- tab-size: 4;
702
- -moz-tab-size: 4;
703
- word-wrap: normal;
704
- word-break: normal;
705
- border: 0;
706
- outline: none;
707
- background: transparent;
708
- box-sizing: border-box;
709
- }
710
-
711
- /* Code editor layer - interactive */
712
- .code-editor {
713
- z-index: 2;
714
- color: transparent !important;
715
- caret-color: #ffffff;
716
- resize: none;
717
- }
718
-
719
- .code-editor::selection {
720
- background-color: rgba(38, 79, 120, 0.6);
721
- color: transparent;
722
- }
723
-
724
- .code-editor::-moz-selection {
725
- background-color: rgba(38, 79, 120, 0.6);
726
- color: transparent;
727
- }
728
-
729
- /* Highlight layer - visual only */
730
- .code-highlight {
731
- z-index: 1;
732
- pointer-events: none;
733
- user-select: none;
734
- -webkit-user-select: none;
735
- -moz-user-select: none;
736
- }
737
-
738
- /* ==========================================================================
739
- PRISM.JS SYNTAX HIGHLIGHTING THEME - CRITICAL: Token colors
740
- ========================================================================== */
741
- code[class*="language-"],
742
- pre[class*="language-"] {
743
- @apply bg-transparent text-left;
744
- color: #f8f8f2;
745
- text-shadow: 0 1px oklch(0 0 0 / 0.3);
746
- font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
747
- white-space: pre;
748
- word-spacing: normal;
749
- word-break: normal;
750
- word-wrap: normal;
751
- line-height: 1.5;
752
- tab-size: 4;
753
- hyphens: none;
754
- }
755
-
756
- pre[class*="language-"]:not(.code-highlight) {
757
- @apply p-3 m-0 overflow-auto rounded;
758
- font-family: "Menlo", "Monaco", "Courier New", monospace;
759
- font-size: 14px;
760
- line-height: 1.5;
761
- tab-size: 4;
762
- }
763
-
764
- /* Override for code-highlight to match code-editor */
765
- .code-highlight.language-json,
766
- .code-highlight[class*="language-"] {
767
- line-height: 1.6 !important;
768
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace !important;
769
- }
770
-
771
- :not(pre) > code[class*="language-"],
772
- pre[class*="language-"] {
773
- @apply bg-dark-350 whitespace-pre;
774
- }
775
-
776
- :not(pre) > code[class*="language-"] {
777
- @apply whitespace-normal rounded-sm;
778
- padding: 0.1em;
779
- }
780
-
781
- /* CRITICAL: VSCode Dark+ theme for syntax highlighting */
782
- .token.comment,
783
- .token.prolog,
784
- .token.doctype,
785
- .token.cdata {
786
- font-style: italic;
787
- color: #6a9955;
788
- }
789
-
790
- .token.punctuation {
791
- color: #d4d4d4;
792
- }
793
-
794
- .namespace {
795
- @apply opacity-80;
796
- }
797
-
798
- .token.property,
799
- .token.tag,
800
- .token.constant,
801
- .token.symbol,
802
- .token.deleted {
803
- color: #b5cea8;
804
- }
805
-
806
- .token.boolean,
807
- .token.number {
808
- color: #b5cea8;
809
- }
810
-
811
- .token.selector,
812
- .token.attr-name,
813
- .token.string,
814
- .token.char,
815
- .token.builtin,
816
- .token.inserted {
817
- color: #ce9178;
818
- }
819
-
820
- .token.operator,
821
- .token.entity,
822
- .token.url {
823
- color: #d4d4d4;
824
- }
825
-
826
- .language-css .token.string,
827
- .style .token.string {
828
- color: #ce9178;
829
- }
830
-
831
- .token.atrule,
832
- .token.attr-value,
833
- .token.keyword {
834
- color: #c586c0;
835
- }
836
-
837
- .token.function,
838
- .token.class-name {
839
- color: #dcdcaa;
840
- }
841
-
842
- .token.regex,
843
- .token.important,
844
- .token.variable {
845
- color: #9cdcfe;
846
- }
847
-
848
- .token.important,
849
- .token.bold {
850
- font-weight: bold;
851
- }
852
-
853
- .token.italic {
854
- font-style: italic;
855
- }
856
-
857
- .token.entity {
858
- @apply cursor-help;
859
- }
860
-
861
- .editor-container:hover {
862
- @apply shadow-xl;
863
- transition: box-shadow 0.3s ease;
864
- }
865
-
866
- /* ==========================================================================
867
- ERROR MESSAGES
868
- ========================================================================== */
869
- .error-container {
870
- @apply flex items-start gap-3 p-4 bg-red-500/10 border border-red-500/20 rounded-lg mt-4 mb-4 max-w-full break-words;
871
- }
872
-
873
- .error-icon {
874
- @apply flex-shrink-0 text-red-400;
875
- }
876
-
877
- .error-content {
878
- @apply flex-1 min-w-0;
879
- }
880
-
881
- .error-title {
882
- @apply text-red-400 font-medium text-sm mb-1;
883
- }
884
-
885
- .error-text {
886
- @apply text-red-300 text-xs break-words;
887
- }
888
-
889
- /* ==========================================================================
890
- SECTION DIVIDER - CRITICAL: Gradient effect
891
- ========================================================================== */
206
+ <style lang="scss" scoped>
892
207
  .section-divider {
893
- @apply relative w-full border-none;
894
- margin: 24px 0;
895
- height: 1px;
896
- background: linear-gradient(90deg, transparent 0%, oklch(0.26 0 0) 20%, oklch(0.26 0 0) 80%, transparent 100%);
897
- box-shadow: 0 0 8px oklch(0.26 0 0 / 0.3);
898
- }
899
-
900
- /* ==========================================================================
901
- MOBILE RESPONSIVE STYLES (max-width: 767px)
902
- ========================================================================== */
903
- @media (max-width: 767px) {
904
- .editor-container,
905
- .proxy-editor-container {
906
- height: calc(100vh - 380px) !important;
907
- max-height: 60vh !important;
908
- min-height: 300px !important;
909
- }
910
-
911
- .editor-container.has-error {
912
- height: calc(100vh - 460px) !important;
913
- max-height: 50vh !important;
914
- min-height: 250px !important;
915
- }
916
-
917
- .error-container {
918
- @apply my-2 p-2;
919
- font-size: 11px !important;
920
- }
921
-
922
- .error-title {
923
- font-size: 11px !important;
924
- }
925
-
926
- .error-text {
927
- font-size: 10px !important;
928
- }
929
-
930
- .my-3 {
931
- @apply my-1.5;
932
- }
933
-
934
- .pb-4 {
935
- @apply pb-1;
936
- }
937
- }
938
-
939
- /* ==========================================================================
940
- DESKTOP RESPONSIVE STYLES
941
- ========================================================================== */
942
- @media (min-width: 1024px) {
943
- .editor-container,
944
- .proxy-editor-container {
945
- max-height: 60vh;
946
- }
208
+ @apply my-8 h-px w-full bg-dark-550;
947
209
  }
948
210
 
949
- @media (min-width: 1280px) {
950
- .editor-container,
951
- .proxy-editor-container {
952
- max-height: 70vh;
953
- }
954
- }
955
-
956
- /* ==========================================================================
957
- LANDSCAPE MODE - CRITICAL: Complex responsive behavior
958
- ========================================================================== */
959
- @media (max-height: 500px) and (orientation: landscape) and (min-width: 568px) {
960
- .admin-editor-section:not(.landscape-hidden) + .section-divider + .proxy-editor-section.landscape-hidden {
961
- @apply hidden;
962
- }
963
-
964
- h4 {
965
- @apply pt-4;
966
- font-size: 16px !important;
967
- margin-bottom: 6px !important;
968
- }
969
-
970
- h5 {
971
- font-size: 14px !important;
972
- margin-bottom: 4px !important;
973
- }
974
-
975
- .section-divider {
976
- @apply my-1.5;
977
- }
978
-
979
- .my-3 {
980
- @apply my-1;
981
- }
982
-
983
- .pb-4 {
984
- @apply pb-0.5;
985
- }
986
-
987
- .editor-container,
988
- .proxy-editor-container {
989
- height: 180px !important;
990
- max-height: 180px !important;
991
- min-height: 150px !important;
992
- }
993
-
994
- .editor-container.has-error {
995
- height: 120px !important;
996
- max-height: 120px !important;
997
- min-height: 100px !important;
998
- }
999
-
1000
- .proxy-editor {
1001
- @apply h-full;
1002
- min-height: 100% !important;
1003
- }
1004
-
1005
- .proxy-editor {
1006
- @apply p-1.5 text-xs;
1007
- line-height: 1.4 !important;
1008
- }
1009
-
1010
- .code-editor,
1011
- .code-highlight {
1012
- padding: 6px !important;
1013
- font-size: 12px !important;
1014
- line-height: 1.4 !important;
1015
- }
1016
-
1017
- .error-container {
1018
- @apply rounded gap-1.5 my-0.5 px-1.5 py-1;
1019
- font-size: 9px !important;
1020
- }
1021
-
1022
- .error-icon {
1023
- @apply w-3 h-3;
1024
- }
1025
-
1026
- .error-title {
1027
- @apply mb-0.5 font-medium;
1028
- font-size: 9px !important;
1029
- }
1030
-
1031
- .error-text {
1032
- font-size: 8px !important;
1033
- line-height: 1.2 !important;
1034
- }
1035
- }
1036
-
1037
- /* Very short landscape screens */
1038
- @media (max-height: 400px) and (orientation: landscape) {
1039
- .editor-container,
1040
- .proxy-editor-container {
1041
- height: 140px !important;
1042
- max-height: 140px !important;
1043
- min-height: 120px !important;
1044
- }
1045
-
1046
- .editor-container.has-error {
1047
- height: 90px !important;
1048
- max-height: 90px !important;
1049
- min-height: 80px !important;
1050
- }
1051
-
1052
- .proxy-editor {
1053
- @apply h-full p-1;
1054
- min-height: 100% !important;
1055
- font-size: 10px !important;
1056
- line-height: 1.3 !important;
1057
- }
1058
-
1059
- .code-editor,
1060
- .code-highlight {
1061
- padding: 4px !important;
1062
- font-size: 10px !important;
1063
- line-height: 1.3 !important;
1064
- }
1065
-
1066
- .error-container {
1067
- @apply rounded-sm gap-1 my-px px-1 py-0.5;
1068
- font-size: 8px !important;
1069
- }
1070
-
1071
- .error-icon {
1072
- @apply w-2.5 h-2.5;
1073
- }
1074
-
1075
- .error-title {
1076
- @apply mb-px font-medium;
1077
- font-size: 8px !important;
1078
- }
1079
-
1080
- .error-text {
1081
- font-size: 7px !important;
1082
- line-height: 1.1 !important;
1083
- }
1084
- }
1085
-
1086
- /* ==========================================================================
1087
- COMPACT EDITOR BUTTONS
1088
- ========================================================================== */
1089
- .button-default {
1090
- @apply h-7 w-7 p-0 rounded-md;
1091
- min-width: 28px !important;
1092
- max-width: 28px !important;
1093
- width: 28px !important;
1094
- height: 28px !important;
1095
- padding: 0 !important;
1096
-
1097
- svg {
1098
- @apply w-4 h-4;
1099
- }
1100
- }
1101
-
1102
- @media (max-width: 640px) {
1103
- .button-default {
1104
- @apply h-6 w-6;
1105
- min-width: 24px !important;
1106
- max-width: 24px !important;
1107
- width: 24px !important;
1108
- height: 24px !important;
1109
-
1110
- svg {
1111
- @apply w-3.5 h-3.5;
1112
- }
211
+ .landscape-hidden {
212
+ @media (max-width: 1023px) and (orientation: landscape) {
213
+ display: none !important;
1113
214
  }
1114
215
  }
1115
216
  </style>