@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.
- package/package.json +1 -1
- package/src/App.vue +14 -480
- package/src/assets/css/components/buttons.scss +12 -68
- package/src/assets/css/components/headers.scss +1 -1
- package/src/assets/css/components/utilities.scss +91 -16
- package/src/assets/css/main.scss +22 -95
- package/src/components/Auth/LoginForm.vue +2 -2
- package/src/components/Console/ConsoleToolbar.vue +123 -0
- package/src/components/Editors/Account/Account.vue +4 -2
- package/src/components/Editors/Account/AccountView.vue +12 -37
- package/src/components/Editors/Account/CreateAccount.vue +3 -11
- package/src/components/Editors/AdminFileEditor.vue +421 -0
- package/src/components/Editors/Profile/CreateProfile.vue +4 -20
- package/src/components/Editors/Profile/Profile.vue +5 -4
- package/src/components/Editors/Profile/ProfileView.vue +13 -38
- package/src/components/Editors/ProxyFileEditor.vue +178 -0
- package/src/components/Filter/Filter.vue +6 -6
- package/src/components/Filter/FilterPreview.vue +4 -12
- package/src/components/Filter/PriceSortToggle.vue +1 -1
- package/src/components/Tasks/QuickSettings.vue +5 -5
- package/src/components/Tasks/Stats.vue +1 -1
- package/src/components/Tasks/Task.vue +5 -8
- package/src/components/Tasks/TaskView.vue +2 -1
- package/src/components/Tasks/ViewTask.vue +2 -2
- package/src/components/icons/index.js +0 -4
- package/src/components/ui/ActionButtonGroup.vue +2 -2
- package/src/components/ui/BalanceIndicator.vue +3 -3
- package/src/components/ui/EnableDisableToggle.vue +2 -2
- package/src/components/ui/FormField.vue +2 -2
- package/src/components/ui/IconLabel.vue +2 -2
- package/src/components/ui/InfoRow.vue +4 -4
- package/src/components/ui/Modal.vue +83 -9
- package/src/components/ui/Navbar.vue +3 -3
- package/src/components/ui/ReadonlyFieldsSection.vue +1 -1
- package/src/components/ui/StatusBadge.vue +1 -1
- package/src/components/ui/controls/CountryChooser.vue +5 -5
- package/src/components/ui/controls/atomic/MultiDropdown.vue +1 -1
- package/src/composables/useCodeEditor.js +117 -0
- package/src/composables/useDropdownPosition.js +0 -2
- 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/useTableRender.js +23 -0
- package/src/composables/useZoomPrevention.js +96 -0
- package/src/constants/tableLayout.js +14 -0
- package/src/libs/utils/array.js +1 -3
- package/src/libs/utils/dataGeneration.js +1 -1
- package/src/libs/utils/string.js +1 -26
- package/src/libs/utils/validation.js +2 -26
- package/src/stores/connection.js +0 -25
- package/src/stores/ui.js +21 -35
- package/src/utils/tableHelpers.js +1 -0
- package/src/views/Accounts.vue +9 -17
- package/src/views/Console.vue +15 -92
- package/src/views/Editor.vue +39 -938
- package/src/views/FilterBuilder.vue +9 -97
- package/src/views/Profiles.vue +9 -17
- package/src/views/Tasks.vue +4 -4
- package/src/assets/img/background.svg.backup +0 -11
- package/src/components/icons/SquareCheck.vue +0 -12
- package/src/components/icons/SquareUncheck.vue +0 -12
- package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
- /package/src/components/Editors/Account/{AccountCreator.vue → CreateAccountBatch.vue} +0 -0
package/package.json
CHANGED
package/src/App.vue
CHANGED
|
@@ -36,101 +36,31 @@
|
|
|
36
36
|
|
|
37
37
|
<script setup>
|
|
38
38
|
import { storeToRefs } from "pinia";
|
|
39
|
-
import { ref, computed
|
|
39
|
+
import { ref, computed } from "vue";
|
|
40
40
|
import { useRouter } from "vue-router";
|
|
41
41
|
import Navbar from "@/components/ui/Navbar.vue";
|
|
42
42
|
import { useUIStore } from "@/stores/ui";
|
|
43
43
|
import Splash from "@/components/ui/Splash.vue";
|
|
44
44
|
import ReconnectIndicator from "@/components/ui/ReconnectIndicator.vue";
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
45
|
+
import { useZoomPrevention } from "@/composables/useZoomPrevention";
|
|
46
|
+
import { useIOSViewportHandling } from "@/composables/useIOSViewportHandling";
|
|
47
|
+
import { useNotchHandling } from "@/composables/useNotchHandling";
|
|
47
48
|
|
|
48
49
|
const ui = useUIStore();
|
|
49
50
|
const { showSpinner: spinner } = storeToRefs(ui);
|
|
50
51
|
const router = useRouter();
|
|
51
52
|
const isLoading = ref(false);
|
|
52
|
-
const showLandscapeLock = ref(false);
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
// Initialize zoom prevention (all browsers)
|
|
55
|
+
const { KEY_CODES } = useZoomPrevention();
|
|
55
56
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
if (e.touches.length > 1) {
|
|
59
|
-
e.preventDefault();
|
|
60
|
-
}
|
|
61
|
-
}, { passive: false });
|
|
62
|
-
|
|
63
|
-
document.addEventListener('touchmove', (e) => {
|
|
64
|
-
if (e.touches.length > 1) {
|
|
65
|
-
e.preventDefault();
|
|
66
|
-
}
|
|
67
|
-
}, { passive: false });
|
|
68
|
-
|
|
69
|
-
document.addEventListener('gesturestart', (e) => {
|
|
70
|
-
e.preventDefault();
|
|
71
|
-
}, { passive: false });
|
|
72
|
-
|
|
73
|
-
document.addEventListener('gesturechange', (e) => {
|
|
74
|
-
e.preventDefault();
|
|
75
|
-
}, { passive: false });
|
|
76
|
-
|
|
77
|
-
document.addEventListener('gestureend', (e) => {
|
|
78
|
-
e.preventDefault();
|
|
79
|
-
}, { passive: false });
|
|
80
|
-
|
|
81
|
-
// Prevent double-tap zoom
|
|
82
|
-
let lastTouchEnd = 0;
|
|
83
|
-
document.addEventListener('touchend', (e) => {
|
|
84
|
-
const now = Date.now();
|
|
85
|
-
if (now - lastTouchEnd <= 300) {
|
|
86
|
-
e.preventDefault();
|
|
87
|
-
}
|
|
88
|
-
lastTouchEnd = now;
|
|
89
|
-
}, { passive: false });
|
|
90
|
-
|
|
91
|
-
if (!window.location.href.includes(":5173")) ui.startSpinner("Loading...");
|
|
92
|
-
|
|
93
|
-
// Handle iPhone landscape lock
|
|
94
|
-
function updateLandscapeLock() {
|
|
95
|
-
if (isIOS() && !isIpadOS()) {
|
|
96
|
-
showLandscapeLock.value = isLandscapeMode();
|
|
97
|
-
}
|
|
98
|
-
}
|
|
57
|
+
// Initialize iOS viewport handling
|
|
58
|
+
useIOSViewportHandling();
|
|
99
59
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
window.addEventListener("resize", updateLandscapeLock);
|
|
103
|
-
updateLandscapeLock();
|
|
104
|
-
|
|
105
|
-
// Ensure notch handling runs when Vue app mounts
|
|
106
|
-
onMounted(() => {
|
|
107
|
-
// Optimized: Single debounced handler instead of multiple setTimeouts
|
|
108
|
-
let attempts = 0;
|
|
109
|
-
const maxAttempts = 5;
|
|
110
|
-
const delays = [0, 50, 150, 300, 500];
|
|
111
|
-
|
|
112
|
-
const scheduleNextAttempt = (index) => {
|
|
113
|
-
if (index >= maxAttempts) return;
|
|
114
|
-
setTimeout(() => {
|
|
115
|
-
handleNotch();
|
|
116
|
-
scheduleNextAttempt(index + 1);
|
|
117
|
-
}, delays[index]);
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
scheduleNextAttempt(0);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// Keyboard key codes
|
|
124
|
-
const KEY_CODES = {
|
|
125
|
-
ESCAPE: 27,
|
|
126
|
-
EQUAL: 61,
|
|
127
|
-
NUMPAD_PLUS: 107,
|
|
128
|
-
FIREFOX_MINUS: 173,
|
|
129
|
-
NUMPAD_MINUS: 109,
|
|
130
|
-
CHROME_EQUAL: 187,
|
|
131
|
-
MINUS: 189
|
|
132
|
-
};
|
|
60
|
+
// Initialize notch handling (iPhone-specific)
|
|
61
|
+
const { showLandscapeLock } = useNotchHandling(ui.logger);
|
|
133
62
|
|
|
63
|
+
// ESC key handling for modals
|
|
134
64
|
document.onkeydown = function (evt) {
|
|
135
65
|
evt = evt || window.event;
|
|
136
66
|
let isEscape = false;
|
|
@@ -144,408 +74,12 @@ document.onkeydown = function (evt) {
|
|
|
144
74
|
}
|
|
145
75
|
};
|
|
146
76
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
// Prevent Ctrl/Cmd + Plus/Minus/0 (various keycodes for different browsers)
|
|
150
|
-
if (
|
|
151
|
-
(event.ctrlKey || event.metaKey) &&
|
|
152
|
-
(event.which === KEY_CODES.EQUAL ||
|
|
153
|
-
event.which === KEY_CODES.NUMPAD_PLUS ||
|
|
154
|
-
event.which === KEY_CODES.FIREFOX_MINUS ||
|
|
155
|
-
event.which === KEY_CODES.NUMPAD_MINUS ||
|
|
156
|
-
event.which === KEY_CODES.CHROME_EQUAL ||
|
|
157
|
-
event.which === KEY_CODES.MINUS ||
|
|
158
|
-
event.keyCode === KEY_CODES.EQUAL ||
|
|
159
|
-
event.keyCode === KEY_CODES.NUMPAD_PLUS ||
|
|
160
|
-
event.keyCode === KEY_CODES.FIREFOX_MINUS ||
|
|
161
|
-
event.keyCode === KEY_CODES.NUMPAD_MINUS ||
|
|
162
|
-
event.keyCode === KEY_CODES.CHROME_EQUAL ||
|
|
163
|
-
event.keyCode === KEY_CODES.MINUS ||
|
|
164
|
-
event.key === '+' ||
|
|
165
|
-
event.key === '-' ||
|
|
166
|
-
event.key === '=' ||
|
|
167
|
-
event.key === '0')
|
|
168
|
-
) {
|
|
169
|
-
event.preventDefault();
|
|
170
|
-
}
|
|
171
|
-
}, { passive: false });
|
|
172
|
-
|
|
173
|
-
// Prevent Ctrl/Cmd + Mouse wheel zoom (desktop)
|
|
174
|
-
document.addEventListener("wheel", function (event) {
|
|
175
|
-
if (event.ctrlKey || event.metaKey) {
|
|
176
|
-
event.preventDefault();
|
|
177
|
-
}
|
|
178
|
-
}, { passive: false });
|
|
179
|
-
|
|
180
|
-
// Also block mousewheel for older browsers
|
|
181
|
-
document.addEventListener("mousewheel", function (event) {
|
|
182
|
-
if (event.ctrlKey || event.metaKey) {
|
|
183
|
-
event.preventDefault();
|
|
184
|
-
}
|
|
185
|
-
}, { passive: false });
|
|
186
|
-
|
|
187
|
-
// Block DOMMouseScroll for Firefox
|
|
188
|
-
document.addEventListener("DOMMouseScroll", function (event) {
|
|
189
|
-
if (event.ctrlKey || event.metaKey) {
|
|
190
|
-
event.preventDefault();
|
|
191
|
-
}
|
|
192
|
-
}, { passive: false });
|
|
193
|
-
// Nuclear iOS keyboard prevention - lock everything down
|
|
194
|
-
let isIOSDevice =
|
|
195
|
-
/iPad|iPhone|iPod/.test(navigator.platform) ||
|
|
196
|
-
(navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform));
|
|
197
|
-
|
|
198
|
-
if (isIOSDevice) {
|
|
199
|
-
const handleViewportResize = () => {
|
|
200
|
-
if (isIpadOS()) {
|
|
201
|
-
// For iPad, allow natural viewport behavior
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// For iPhone, maintain viewport stability
|
|
206
|
-
const vh = window.innerHeight * 0.01;
|
|
207
|
-
document.documentElement.style.setProperty("--vh", `${vh}px`);
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
// Initial setup
|
|
211
|
-
handleViewportResize();
|
|
212
|
-
|
|
213
|
-
// Listen for viewport changes
|
|
214
|
-
window.addEventListener("resize", handleViewportResize);
|
|
215
|
-
if (window.visualViewport) {
|
|
216
|
-
window.visualViewport.addEventListener("resize", handleViewportResize);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Simplified scroll control - let scrollable elements handle their own scrolling
|
|
221
|
-
// Page scroll is already prevented by overflow: hidden on html/body
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
// Precise scroll control - only allow scrolling within specific scrollable elements
|
|
226
|
-
window.addEventListener(
|
|
227
|
-
"touchmove",
|
|
228
|
-
function (event) {
|
|
229
|
-
// Check if we're touching a scrollable element directly
|
|
230
|
-
const isScrollableTextarea =
|
|
231
|
-
event.target.tagName === "TEXTAREA" &&
|
|
232
|
-
(event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
|
|
233
|
-
|
|
234
|
-
// Check if we're in a scrollable container
|
|
235
|
-
const isInScrollableContainer =
|
|
236
|
-
event.target.closest(".stop-pan") ||
|
|
237
|
-
event.target.closest(".overflow-y-auto") ||
|
|
238
|
-
event.target.closest(".vue-recycle-scroller") ||
|
|
239
|
-
event.target.closest(".scroller") ||
|
|
240
|
-
event.target.closest(".scrollable");
|
|
241
|
-
|
|
242
|
-
// Check if we're in a table but only allow scrolling on actual scrollable content
|
|
243
|
-
const isInTable = event.target.closest(".table-component");
|
|
244
|
-
const isScrollableTableContent =
|
|
245
|
-
isInTable &&
|
|
246
|
-
(event.target.closest(".grid") || // Table rows
|
|
247
|
-
event.target.closest(".table-row") ||
|
|
248
|
-
isInScrollableContainer ||
|
|
249
|
-
(event.target.closest(".table-component") && !event.target.closest(".table-header")));
|
|
250
|
-
|
|
251
|
-
// Allow scrolling in navbar and modals (they handle their own boundaries)
|
|
252
|
-
const isInNavbar = event.target.closest(".navbar") || event.target.closest(".mobile-menu");
|
|
253
|
-
const isInModal = event.target.closest('[role="dialog"]');
|
|
254
|
-
|
|
255
|
-
// Only allow these specific cases
|
|
256
|
-
if (isScrollableTextarea || isScrollableTableContent || isInScrollableContainer || isInNavbar || isInModal) {
|
|
257
|
-
// For table content, ensure we stop propagation to prevent page scroll
|
|
258
|
-
if (isScrollableTableContent || isScrollableTextarea || isInScrollableContainer) {
|
|
259
|
-
event.stopPropagation();
|
|
260
|
-
}
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Prevent everything else
|
|
265
|
-
event.preventDefault();
|
|
266
|
-
},
|
|
267
|
-
{ passive: false }
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
let notchTimeout;
|
|
271
|
-
let isNotchBusy = false;
|
|
272
|
-
let lastNotchState = null;
|
|
273
|
-
|
|
274
|
-
function isLandscapeMode() {
|
|
275
|
-
// Check orientation first
|
|
276
|
-
let orientationLandscape = false;
|
|
277
|
-
|
|
278
|
-
if (typeof window.orientation !== "undefined") {
|
|
279
|
-
orientationLandscape = Math.abs(window.orientation) === 90;
|
|
280
|
-
} else if (screen.orientation && typeof screen.orientation.angle !== "undefined") {
|
|
281
|
-
orientationLandscape = Math.abs(screen.orientation.angle) === 90;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Always also check dimensions as backup
|
|
285
|
-
const dimensionLandscape = window.innerWidth > window.innerHeight;
|
|
286
|
-
const hasStrongLandscapeRatio = window.innerWidth / window.innerHeight > 1.5;
|
|
287
|
-
|
|
288
|
-
// For iPhones, if dimensions clearly indicate landscape, trust that
|
|
289
|
-
if (isIOS() && !isIpadOS() && dimensionLandscape && hasStrongLandscapeRatio) {
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Otherwise use orientation if available, fallback to dimensions
|
|
294
|
-
return orientationLandscape || (dimensionLandscape && hasStrongLandscapeRatio);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function hasDeviceNotch() {
|
|
298
|
-
// Only check for notch on actual devices that might have one
|
|
299
|
-
if (!isIOS() || isIpadOS()) return false;
|
|
300
|
-
|
|
301
|
-
return (
|
|
302
|
-
CSS.supports("padding-left: env(safe-area-inset-left)") &&
|
|
303
|
-
CSS.supports("padding-right: env(safe-area-inset-right)")
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function handleNotch(force = false) {
|
|
308
|
-
// Simple debouncing
|
|
309
|
-
if (isNotchBusy && !force) return;
|
|
310
|
-
|
|
311
|
-
try {
|
|
312
|
-
// Only for iPhone (not iPad)
|
|
313
|
-
if (!isIOS() || isIpadOS()) return;
|
|
314
|
-
|
|
315
|
-
const wrappers = document.querySelectorAll(".ios-wrapper");
|
|
316
|
-
if (wrappers.length === 0) {
|
|
317
|
-
setTimeout(() => handleNotch(force), 100);
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const isLandscape = isLandscapeMode();
|
|
322
|
-
const hasNotch = hasDeviceNotch();
|
|
323
|
-
|
|
324
|
-
// Get orientation
|
|
325
|
-
let orientation = window.orientation;
|
|
326
|
-
if (typeof orientation === "undefined" && screen.orientation) {
|
|
327
|
-
orientation = screen.orientation.angle;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Create state signature to prevent redundant updates
|
|
331
|
-
const currentState = `${isLandscape}-${hasNotch}-${orientation}-${window.innerWidth}x${window.innerHeight}`;
|
|
332
|
-
if (lastNotchState === currentState && !force) {
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
lastNotchState = currentState;
|
|
336
|
-
|
|
337
|
-
isNotchBusy = true;
|
|
338
|
-
|
|
339
|
-
if (DEBUG) {
|
|
340
|
-
ui.logger.Debug("🔥 Notch Debug:", {
|
|
341
|
-
isLandscape,
|
|
342
|
-
hasNotch,
|
|
343
|
-
orientation,
|
|
344
|
-
dimensions: `${window.innerWidth}x${window.innerHeight}`,
|
|
345
|
-
state: currentState
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (hasNotch && isLandscape) {
|
|
350
|
-
// Test actual safe area values to determine notch side
|
|
351
|
-
const testDiv = document.createElement("div");
|
|
352
|
-
testDiv.style.position = "fixed";
|
|
353
|
-
testDiv.style.top = "0";
|
|
354
|
-
testDiv.style.left = "0";
|
|
355
|
-
testDiv.style.visibility = "hidden";
|
|
356
|
-
testDiv.style.paddingLeft = "env(safe-area-inset-left)";
|
|
357
|
-
testDiv.style.paddingRight = "env(safe-area-inset-right)";
|
|
358
|
-
document.body.appendChild(testDiv);
|
|
359
|
-
|
|
360
|
-
const computedStyle = getComputedStyle(testDiv);
|
|
361
|
-
const leftInset = parseFloat(computedStyle.paddingLeft) || 0;
|
|
362
|
-
const rightInset = parseFloat(computedStyle.paddingRight) || 0;
|
|
363
|
-
|
|
364
|
-
document.body.removeChild(testDiv);
|
|
365
|
-
|
|
366
|
-
if (DEBUG) ui.logger.Debug("🔍 Safe area insets:", { leftInset, rightInset });
|
|
367
|
-
|
|
368
|
-
// Apply styles instantly - NO ANIMATION
|
|
369
|
-
wrappers.forEach((wrapper) => {
|
|
370
|
-
if (leftInset > 0) {
|
|
371
|
-
// Notch on left
|
|
372
|
-
wrapper.style.paddingLeft = "env(safe-area-inset-left)";
|
|
373
|
-
wrapper.style.paddingRight = "0.5rem";
|
|
374
|
-
} else if (rightInset > 0) {
|
|
375
|
-
// Notch on right
|
|
376
|
-
wrapper.style.paddingLeft = "0.5rem";
|
|
377
|
-
wrapper.style.paddingRight = "env(safe-area-inset-right)";
|
|
378
|
-
} else {
|
|
379
|
-
// Fallback - no detectable notch
|
|
380
|
-
wrapper.style.paddingLeft = "0.5rem";
|
|
381
|
-
wrapper.style.paddingRight = "0.5rem";
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
} else {
|
|
385
|
-
// Portrait or no notch - Responsive padding constants
|
|
386
|
-
const RESPONSIVE_PADDING = {
|
|
387
|
-
XL: { minWidth: 1280, padding: "2.5rem" },
|
|
388
|
-
LG: { minWidth: 1030, padding: "1.5rem" },
|
|
389
|
-
MD: { minWidth: 768, padding: "0.5rem" },
|
|
390
|
-
DEFAULT: { padding: "0.5rem" }
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
const padding =
|
|
394
|
-
window.innerWidth > RESPONSIVE_PADDING.XL.minWidth
|
|
395
|
-
? RESPONSIVE_PADDING.XL.padding
|
|
396
|
-
: window.innerWidth > RESPONSIVE_PADDING.LG.minWidth
|
|
397
|
-
? RESPONSIVE_PADDING.LG.padding
|
|
398
|
-
: window.innerWidth > RESPONSIVE_PADDING.MD.minWidth
|
|
399
|
-
? RESPONSIVE_PADDING.MD.padding
|
|
400
|
-
: RESPONSIVE_PADDING.DEFAULT.padding;
|
|
401
|
-
|
|
402
|
-
// Apply styles instantly - NO ANIMATION
|
|
403
|
-
wrappers.forEach((wrapper) => {
|
|
404
|
-
wrapper.style.paddingLeft = padding;
|
|
405
|
-
wrapper.style.paddingRight = padding;
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
isNotchBusy = false;
|
|
410
|
-
} catch (error) {
|
|
411
|
-
if (DEBUG) ui.logger.Yellow("Notch error:", error);
|
|
412
|
-
isNotchBusy = false;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function triggerNotch() {
|
|
417
|
-
clearTimeout(notchTimeout);
|
|
418
|
-
notchTimeout = setTimeout(handleNotch, 50);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Event listeners
|
|
422
|
-
window.addEventListener("orientationchange", triggerNotch);
|
|
423
|
-
window.addEventListener("resize", triggerNotch);
|
|
424
|
-
if (screen.orientation) {
|
|
425
|
-
screen.orientation.addEventListener("change", triggerNotch);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Aggressive initial setup to handle all scenarios
|
|
429
|
-
function initNotchHandling() {
|
|
430
|
-
handleNotch(true); // Force first execution
|
|
431
|
-
setTimeout(() => handleNotch(true), 50);
|
|
432
|
-
setTimeout(() => handleNotch(true), 150);
|
|
433
|
-
setTimeout(() => handleNotch(true), 300);
|
|
434
|
-
setTimeout(() => handleNotch(true), 500);
|
|
435
|
-
setTimeout(() => handleNotch(true), 1000);
|
|
436
|
-
setTimeout(() => handleNotch(true), 2000); // Extra long delay for slow loads
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Multiple initialization points
|
|
440
|
-
if (document.readyState === "loading") {
|
|
441
|
-
document.addEventListener("DOMContentLoaded", initNotchHandling);
|
|
442
|
-
} else {
|
|
443
|
-
initNotchHandling();
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
window.addEventListener("load", initNotchHandling);
|
|
447
|
-
|
|
448
|
-
// Also run when page becomes visible (handles app switching)
|
|
449
|
-
document.addEventListener("visibilitychange", () => {
|
|
450
|
-
if (!document.hidden) {
|
|
451
|
-
setTimeout(handleNotch, 100);
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
// Run on focus (when returning to app)
|
|
456
|
-
window.addEventListener("focus", () => {
|
|
457
|
-
setTimeout(handleNotch, 100);
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
// Watch for ios-wrapper elements appearing in DOM
|
|
461
|
-
const observer = new MutationObserver((mutations) => {
|
|
462
|
-
let shouldTrigger = false;
|
|
463
|
-
|
|
464
|
-
mutations.forEach((mutation) => {
|
|
465
|
-
if (mutation.type === "childList") {
|
|
466
|
-
mutation.addedNodes.forEach((node) => {
|
|
467
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
468
|
-
if (node.classList?.contains("ios-wrapper") || node.querySelector?.(".ios-wrapper")) {
|
|
469
|
-
shouldTrigger = true;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
if (shouldTrigger) {
|
|
477
|
-
setTimeout(() => handleNotch(true), 10);
|
|
478
|
-
setTimeout(() => handleNotch(true), 100);
|
|
479
|
-
}
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
// Start observing
|
|
483
|
-
if (document.body) {
|
|
484
|
-
observer.observe(document.body, {
|
|
485
|
-
childList: true,
|
|
486
|
-
subtree: true
|
|
487
|
-
});
|
|
488
|
-
} else {
|
|
489
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
490
|
-
observer.observe(document.body, {
|
|
491
|
-
childList: true,
|
|
492
|
-
subtree: true
|
|
493
|
-
});
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// Navigation handling
|
|
498
|
-
const originalPushState = history.pushState;
|
|
499
|
-
const originalReplaceState = history.replaceState;
|
|
500
|
-
|
|
501
|
-
history.pushState = function (...args) {
|
|
502
|
-
originalPushState.apply(this, args);
|
|
503
|
-
triggerNotch();
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
history.replaceState = function (...args) {
|
|
507
|
-
originalReplaceState.apply(this, args);
|
|
508
|
-
triggerNotch();
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
window.addEventListener("popstate", triggerNotch);
|
|
512
|
-
|
|
513
|
-
// Expose for manual triggering
|
|
514
|
-
window.simulateRotate = handleNotch;
|
|
515
|
-
|
|
516
|
-
// Pull-to-refresh removed per user request
|
|
517
|
-
// Vue router integration - aggressive triggering on route changes
|
|
518
|
-
watch(
|
|
519
|
-
() => router.currentRoute.value.name,
|
|
520
|
-
() => {
|
|
521
|
-
// Immediate triggers
|
|
522
|
-
handleNotch();
|
|
523
|
-
triggerNotch();
|
|
524
|
-
|
|
525
|
-
// Staggered attempts
|
|
526
|
-
setTimeout(handleNotch, 10);
|
|
527
|
-
setTimeout(handleNotch, 50);
|
|
528
|
-
setTimeout(handleNotch, 150);
|
|
529
|
-
setTimeout(handleNotch, 300);
|
|
530
|
-
setTimeout(handleNotch, 500);
|
|
531
|
-
}
|
|
532
|
-
);
|
|
533
|
-
|
|
534
|
-
watch(
|
|
535
|
-
() => router.currentRoute.value.path,
|
|
536
|
-
() => {
|
|
537
|
-
// Immediate triggers
|
|
538
|
-
handleNotch();
|
|
539
|
-
triggerNotch();
|
|
77
|
+
// Start spinner unless in development
|
|
78
|
+
if (!window.location.href.includes(":5173")) ui.startSpinner("Loading...");
|
|
540
79
|
|
|
541
|
-
// Staggered attempts
|
|
542
|
-
setTimeout(handleNotch, 25);
|
|
543
|
-
setTimeout(handleNotch, 100);
|
|
544
|
-
setTimeout(handleNotch, 250);
|
|
545
|
-
}
|
|
546
|
-
);
|
|
547
80
|
const layout = computed(() => router.currentRoute.value.meta.layout);
|
|
548
81
|
</script>
|
|
82
|
+
|
|
549
83
|
<style lang="scss">
|
|
550
84
|
/* iPhone Landscape Lock Overlay */
|
|
551
85
|
.iphone-landscape-lock {
|
|
@@ -5,10 +5,8 @@
|
|
|
5
5
|
@use "../base/mixins" as *;
|
|
6
6
|
|
|
7
7
|
.btn-primary {
|
|
8
|
-
@apply font-medium
|
|
9
|
-
@
|
|
10
|
-
@apply px-4 py-2 rounded-md;
|
|
11
|
-
@apply shadow-sm border-2 border-accent-green;
|
|
8
|
+
@apply font-medium bg-accent-green text-white px-4 py-2 rounded-md shadow-sm border-2 border-accent-green;
|
|
9
|
+
@include transition-standard;
|
|
12
10
|
|
|
13
11
|
&:hover {
|
|
14
12
|
@apply bg-green-400 border-green-400;
|
|
@@ -20,58 +18,10 @@
|
|
|
20
18
|
}
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
.btn-secondary {
|
|
24
|
-
@apply font-medium transition-all duration-150;
|
|
25
|
-
@apply bg-transparent text-accent-green;
|
|
26
|
-
@apply px-4 py-2 rounded-md;
|
|
27
|
-
@apply border-2 border-accent-green;
|
|
28
|
-
|
|
29
|
-
&:hover {
|
|
30
|
-
@apply bg-accent-green text-white border-accent-green;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
&:active {
|
|
34
|
-
@apply bg-green-400 border-accent-green;
|
|
35
|
-
@include focus-ring-accent;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.btn-ghost {
|
|
40
|
-
@apply font-medium transition-all duration-150;
|
|
41
|
-
@apply bg-transparent text-light-400;
|
|
42
|
-
@apply px-4 py-2 rounded-md;
|
|
43
|
-
@apply border-none;
|
|
44
|
-
|
|
45
|
-
&:hover {
|
|
46
|
-
@apply bg-dark-450 text-light-300;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
&:active {
|
|
50
|
-
@apply bg-dark-350;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.btn-danger,
|
|
55
|
-
.btn-destructive {
|
|
56
|
-
@apply font-medium transition-all duration-150;
|
|
57
|
-
@apply bg-error-500 text-white;
|
|
58
|
-
@apply px-4 py-2 rounded-md;
|
|
59
|
-
@apply shadow-sm border-none;
|
|
60
|
-
|
|
61
|
-
&:hover {
|
|
62
|
-
@apply bg-error-400;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
&:active {
|
|
66
|
-
@apply bg-red-500;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
21
|
|
|
70
22
|
.btn-icon {
|
|
71
|
-
@apply flex items-center justify-center
|
|
72
|
-
@
|
|
73
|
-
@apply bg-transparent text-light-400;
|
|
74
|
-
@apply border-none;
|
|
23
|
+
@apply flex items-center justify-center p-1 rounded-md bg-transparent text-light-400 border-none;
|
|
24
|
+
@include transition-standard;
|
|
75
25
|
|
|
76
26
|
&:hover {
|
|
77
27
|
@apply bg-dark-450 text-white;
|
|
@@ -83,11 +33,8 @@
|
|
|
83
33
|
}
|
|
84
34
|
|
|
85
35
|
.btn-action {
|
|
86
|
-
@apply font-medium flex items-center justify-center gap-x-2
|
|
87
|
-
@
|
|
88
|
-
@apply px-4 h-10 rounded-md;
|
|
89
|
-
@apply border-2 border-accent-green;
|
|
90
|
-
@apply text-xs;
|
|
36
|
+
@apply font-medium flex items-center justify-center gap-x-2 bg-transparent text-accent-green px-4 h-10 rounded-md border-2 border-accent-green text-xs;
|
|
37
|
+
@include transition-standard;
|
|
91
38
|
|
|
92
39
|
&:hover {
|
|
93
40
|
@apply bg-accent-green text-white border-accent-green;
|
|
@@ -100,8 +47,8 @@
|
|
|
100
47
|
}
|
|
101
48
|
|
|
102
49
|
.button-default {
|
|
103
|
-
@apply font-medium
|
|
104
|
-
@
|
|
50
|
+
@apply font-medium flex items-center justify-center gap-x-2 h-14 rounded-lg text-white border-2 border-dark-550 text-sm px-6 w-48;
|
|
51
|
+
@include transition-standard;
|
|
105
52
|
|
|
106
53
|
svg {
|
|
107
54
|
@apply w-4 h-4 flex-shrink-0;
|
|
@@ -146,10 +93,8 @@
|
|
|
146
93
|
}
|
|
147
94
|
|
|
148
95
|
.login-btn {
|
|
149
|
-
@apply flex items-center justify-center gap-2;
|
|
150
|
-
@
|
|
151
|
-
@apply bg-accent-green border-2 border-accent-green text-white;
|
|
152
|
-
@apply transition-all duration-150;
|
|
96
|
+
@apply flex items-center justify-center gap-2 h-12 rounded-lg font-semibold tracking-wider uppercase bg-accent-green border-2 border-accent-green text-white;
|
|
97
|
+
@include transition-standard;
|
|
153
98
|
font-size: theme('fontSize.base-');
|
|
154
99
|
|
|
155
100
|
&:hover:enabled {
|
|
@@ -198,9 +143,8 @@
|
|
|
198
143
|
========================================================================== */
|
|
199
144
|
|
|
200
145
|
.btn-modal {
|
|
201
|
-
@apply h-10 rounded-md text-xs flex items-center justify-center;
|
|
202
|
-
@
|
|
203
|
-
@apply hover:border-dark-700 transition-all duration-150 btn-focus-ring;
|
|
146
|
+
@apply h-10 rounded-md text-xs flex items-center justify-center font-medium px-4 hover:border-dark-700 btn-focus-ring;
|
|
147
|
+
@include transition-standard;
|
|
204
148
|
@include dark-button-base;
|
|
205
149
|
@include scale-hover;
|
|
206
150
|
}
|