@necrolab/dashboard 0.4.220 → 0.5.1
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/.prettierrc +27 -1
- package/.vscode/extensions.json +1 -1
- package/README.md +64 -2
- package/artwork/image.png +0 -0
- package/backend/api.js +26 -24
- package/backend/auth.js +2 -2
- package/backend/batching.js +1 -1
- package/backend/endpoints.js +8 -11
- package/backend/index.js +2 -2
- package/backend/mock-data.js +27 -36
- package/backend/mock-src/classes/logger.js +5 -7
- package/backend/mock-src/classes/utils.js +3 -2
- package/backend/mock-src/ticketmaster.js +4 -4
- package/backend/validator.js +2 -2
- package/config/configs.json +0 -1
- package/dev-server.js +134 -0
- package/exit +209 -0
- package/index.html +78 -8
- package/index.js +1 -1
- package/jsconfig.json +16 -0
- package/package.json +39 -25
- package/postcss.config.js +1 -1
- package/postinstall.js +124 -20
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/img/logo_trans.png +0 -0
- package/public/img/necro_logo.png +0 -0
- package/public/manifest.json +16 -10
- package/run +176 -9
- package/src/App.vue +498 -85
- package/src/assets/css/base/reset.scss +43 -0
- package/src/assets/css/base/scroll.scss +114 -0
- package/src/assets/css/base/typography.scss +37 -0
- package/src/assets/css/components/buttons.scss +216 -0
- package/src/assets/css/components/forms.scss +221 -0
- package/src/assets/css/components/modals.scss +13 -0
- package/src/assets/css/components/tables.scss +27 -0
- package/src/assets/css/components/toasts.scss +100 -0
- package/src/assets/css/main.scss +201 -122
- package/src/assets/img/background.svg +2 -2
- package/src/assets/img/background.svg.backup +11 -0
- package/src/assets/img/logo_trans.png +0 -0
- package/src/components/Auth/LoginForm.vue +62 -11
- package/src/components/Editors/Account/Account.vue +116 -40
- package/src/components/Editors/Account/AccountCreator.vue +88 -39
- package/src/components/Editors/Account/AccountView.vue +102 -34
- package/src/components/Editors/Account/CreateAccount.vue +80 -32
- package/src/components/Editors/Profile/CreateProfile.vue +269 -83
- package/src/components/Editors/Profile/Profile.vue +132 -47
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +82 -20
- package/src/components/Editors/Profile/ProfileView.vue +89 -32
- package/src/components/Editors/TagLabel.vue +67 -6
- package/src/components/Editors/TagToggle.vue +7 -2
- package/src/components/Filter/Filter.vue +288 -71
- package/src/components/Filter/FilterPreview.vue +202 -31
- package/src/components/Filter/PriceSortToggle.vue +76 -6
- package/src/components/Table/Header.vue +1 -1
- package/src/components/Table/Row.vue +1 -1
- package/src/components/Table/Table.vue +19 -2
- package/src/components/Tasks/CheckStock.vue +6 -8
- package/src/components/Tasks/Controls/DesktopControls.vue +27 -17
- package/src/components/Tasks/Controls/MobileControls.vue +8 -45
- package/src/components/Tasks/CreateTaskAXS.vue +80 -72
- package/src/components/Tasks/CreateTaskTM.vue +95 -141
- package/src/components/Tasks/MassEdit.vue +4 -6
- package/src/components/Tasks/QuickSettings.vue +199 -30
- package/src/components/Tasks/ScrapeVenue.vue +5 -6
- package/src/components/Tasks/Stats.vue +50 -24
- package/src/components/Tasks/Task.vue +384 -179
- package/src/components/Tasks/TaskLabel.vue +2 -2
- package/src/components/Tasks/TaskView.vue +136 -48
- package/src/components/Tasks/Utilities.vue +25 -10
- package/src/components/Tasks/ViewTask.vue +321 -0
- package/src/components/icons/Bag.vue +1 -1
- package/src/components/icons/Check.vue +5 -0
- package/src/components/icons/Close.vue +21 -0
- package/src/components/icons/CloseX.vue +5 -0
- package/src/components/icons/Eye.vue +6 -0
- package/src/components/icons/Key.vue +21 -0
- package/src/components/icons/Loyalty.vue +1 -1
- package/src/components/icons/Mail.vue +2 -2
- package/src/components/icons/Pencil.vue +21 -0
- package/src/components/icons/Play.vue +2 -2
- package/src/components/icons/Profile.vue +18 -0
- package/src/components/icons/Reload.vue +4 -5
- package/src/components/icons/Sandclock.vue +2 -2
- package/src/components/icons/Sell.vue +21 -0
- package/src/components/icons/Spinner.vue +42 -0
- package/src/components/icons/SquareCheck.vue +18 -0
- package/src/components/icons/SquareUncheck.vue +18 -0
- package/src/components/icons/Stadium.vue +1 -1
- package/src/components/icons/Wildcard.vue +18 -0
- package/src/components/icons/index.js +26 -1
- package/src/components/ui/Modal.vue +107 -13
- package/src/components/ui/Navbar.vue +175 -40
- package/src/components/ui/ReconnectIndicator.vue +351 -55
- package/src/components/ui/Splash.vue +5 -35
- package/src/components/ui/controls/CountryChooser.vue +200 -62
- package/src/components/ui/controls/atomic/Checkbox.vue +119 -10
- package/src/components/ui/controls/atomic/Dropdown.vue +216 -39
- package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
- package/src/components/ui/controls/atomic/MultiDropdown.vue +300 -37
- package/src/components/ui/controls/atomic/Switch.vue +53 -25
- package/src/composables/useClickOutside.js +21 -0
- package/src/composables/useDropdownPosition.js +174 -0
- package/src/libs/Filter.js +60 -24
- package/src/registerServiceWorker.js +1 -1
- package/src/stores/connection.js +4 -4
- package/src/stores/sampleData.js +172 -199
- package/src/stores/ui.js +55 -20
- package/src/stores/utils.js +30 -4
- package/src/types/index.js +41 -0
- package/src/utils/debug.js +1 -0
- package/src/views/Accounts.vue +116 -50
- package/src/views/Console.vue +394 -79
- package/src/views/Editor.vue +1176 -123
- package/src/views/FilterBuilder.vue +528 -250
- package/src/views/Login.vue +76 -14
- package/src/views/Profiles.vue +119 -34
- package/src/views/Tasks.vue +266 -98
- package/static/offline.html +192 -50
- package/switch-branch.sh +41 -0
- package/tailwind.config.js +119 -27
- package/vite.config.js +73 -16
- package/workbox-config.cjs +63 -0
- package/ICONS.md +0 -21
- package/public/img/background.svg +0 -14
- package/public/img/logo.png +0 -0
- package/public/img/logo_icon.png +0 -0
- package/public/img/logo_icon_2.png +0 -0
- package/src/assets/css/_input.scss +0 -143
- package/src/assets/img/logo.png +0 -0
- package/src/assets/img/logo_icon.png +0 -0
- package/src/assets/img/logo_icon_2.png +0 -0
- package/vue.config.js +0 -32
- package/workbox-config.js +0 -7
package/src/App.vue
CHANGED
|
@@ -9,27 +9,23 @@
|
|
|
9
9
|
:style="{
|
|
10
10
|
'margin-top': maxPull() + 'px',
|
|
11
11
|
transform: `rotate(${ui.pullChange}deg)`
|
|
12
|
-
}"
|
|
13
|
-
>
|
|
12
|
+
}">
|
|
14
13
|
<div
|
|
15
14
|
class="refresh-icon p-2 rounded-full mx-auto duration-200"
|
|
16
15
|
:class="{
|
|
17
16
|
'opacity-100': ui.pullChange > 250,
|
|
18
17
|
'opacity-0': ui.pullChange < 250
|
|
19
|
-
}"
|
|
20
|
-
>
|
|
18
|
+
}">
|
|
21
19
|
<svg
|
|
22
20
|
xmlns="http://www.w3.org/2000/svg"
|
|
23
21
|
fill="none"
|
|
24
22
|
viewBox="0 0 24 24"
|
|
25
23
|
class="stroke-2 w-4 stroke-dark-400"
|
|
26
|
-
:class="{ 'opacity-0': ui.pullChange == 0 }"
|
|
27
|
-
>
|
|
24
|
+
:class="{ 'opacity-0': ui.pullChange == 0 }">
|
|
28
25
|
<path
|
|
29
26
|
strokeLinecap="round"
|
|
30
27
|
strokeLinejoin="round"
|
|
31
|
-
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
|
|
32
|
-
/>
|
|
28
|
+
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
|
|
33
29
|
</svg>
|
|
34
30
|
</div>
|
|
35
31
|
</div>
|
|
@@ -38,8 +34,7 @@
|
|
|
38
34
|
:class="{
|
|
39
35
|
'opacity-100': ui.pullChange > 250,
|
|
40
36
|
'opacity-0': ui.pullChange < 250
|
|
41
|
-
}"
|
|
42
|
-
>
|
|
37
|
+
}">
|
|
43
38
|
Release to refresh
|
|
44
39
|
</h2>
|
|
45
40
|
</div>
|
|
@@ -49,12 +44,17 @@
|
|
|
49
44
|
</div>
|
|
50
45
|
<div v-else key="main-components" class="flex">
|
|
51
46
|
<Navbar v-if="layout == 'dashboard'" class="fixed" />
|
|
52
|
-
<div :class="
|
|
53
|
-
<router-view v-slot="{ Component }">
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
<div :class="['router-wrapper', { '-mt-[60px]': ui.pullChange > 1 }]"></div>
|
|
48
|
+
<router-view v-slot="{ Component, route }">
|
|
49
|
+
<transition name="page-transition" mode="out-in">
|
|
50
|
+
<component
|
|
51
|
+
:is="Component"
|
|
52
|
+
:key="route.path"
|
|
53
|
+
:class="[
|
|
54
|
+
'component-container pb-2 mt-0 lg:mt-4 ios-wrapper',
|
|
55
|
+
{ 'w-full': landscapeIos }
|
|
56
|
+
]" />
|
|
57
|
+
</transition>
|
|
58
58
|
</router-view>
|
|
59
59
|
</div>
|
|
60
60
|
</transition>
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
|
|
64
64
|
<script setup>
|
|
65
65
|
import { storeToRefs } from "pinia";
|
|
66
|
-
import { ref, computed, watch } from "vue";
|
|
66
|
+
import { ref, computed, watch, onMounted } from "vue";
|
|
67
67
|
import { useRouter } from "vue-router";
|
|
68
68
|
import Navbar from "@/components/ui/Navbar.vue";
|
|
69
69
|
import { useUIStore } from "@/stores/ui";
|
|
@@ -88,13 +88,50 @@ function isIpadOS() {
|
|
|
88
88
|
return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
// Handle orientation changes for iOS devices
|
|
92
|
+
const handleOrientationChange = () => {
|
|
93
|
+
if (isIOS()) {
|
|
94
|
+
if (isIpadOS()) {
|
|
95
|
+
// For iPad, we want to allow proper viewport resizing
|
|
96
|
+
landscapeIos.value = window.matchMedia("(orientation: landscape)").matches;
|
|
97
|
+
document.documentElement.style.setProperty("width", "100%", "important");
|
|
98
|
+
document.body.style.setProperty("width", "100%", "important");
|
|
99
|
+
} else {
|
|
100
|
+
// For iPhone, maintain the current behavior
|
|
101
|
+
landscapeIos.value = window.matchMedia("(orientation: landscape)").matches;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Initial orientation check
|
|
107
|
+
handleOrientationChange();
|
|
108
|
+
|
|
109
|
+
// Listen for orientation changes
|
|
110
|
+
window.matchMedia("(orientation: portrait)").addEventListener("change", handleOrientationChange);
|
|
111
|
+
window.matchMedia("(orientation: landscape)").addEventListener("change", handleOrientationChange);
|
|
95
112
|
|
|
96
113
|
if (!window.location.href.includes(":5173")) ui.startSpinner("Loading...");
|
|
97
114
|
|
|
115
|
+
// Ensure notch handling runs when Vue app mounts
|
|
116
|
+
onMounted(() => {
|
|
117
|
+
// Multiple aggressive attempts on Vue mount
|
|
118
|
+
handleNotch();
|
|
119
|
+
setTimeout(handleNotch, 10);
|
|
120
|
+
setTimeout(handleNotch, 50);
|
|
121
|
+
setTimeout(handleNotch, 150);
|
|
122
|
+
setTimeout(handleNotch, 300);
|
|
123
|
+
|
|
124
|
+
// Set up a recurring check for the first few seconds
|
|
125
|
+
let attempts = 0;
|
|
126
|
+
const recurringCheck = setInterval(() => {
|
|
127
|
+
if (attempts++ > 10) {
|
|
128
|
+
clearInterval(recurringCheck);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
handleNotch();
|
|
132
|
+
}, 200);
|
|
133
|
+
});
|
|
134
|
+
|
|
98
135
|
// close modals on esc
|
|
99
136
|
document.onkeydown = function (evt) {
|
|
100
137
|
evt = evt || window.event;
|
|
@@ -122,13 +159,64 @@ document.addEventListener("keydown", function (event) {
|
|
|
122
159
|
event.preventDefault();
|
|
123
160
|
}
|
|
124
161
|
});
|
|
125
|
-
//
|
|
162
|
+
// Nuclear iOS keyboard prevention - lock everything down
|
|
163
|
+
let isIOSDevice =
|
|
164
|
+
/iPad|iPhone|iPod/.test(navigator.platform) ||
|
|
165
|
+
(navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform));
|
|
166
|
+
|
|
167
|
+
if (isIOSDevice) {
|
|
168
|
+
const handleViewportResize = () => {
|
|
169
|
+
if (isIpadOS()) {
|
|
170
|
+
// For iPad, allow natural viewport behavior
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// For iPhone, maintain viewport stability
|
|
175
|
+
const vh = window.innerHeight * 0.01;
|
|
176
|
+
document.documentElement.style.setProperty("--vh", `${vh}px`);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Initial setup
|
|
180
|
+
handleViewportResize();
|
|
181
|
+
|
|
182
|
+
// Listen for viewport changes
|
|
183
|
+
window.addEventListener("resize", handleViewportResize);
|
|
184
|
+
if (window.visualViewport) {
|
|
185
|
+
window.visualViewport.addEventListener("resize", handleViewportResize);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Precise mouse wheel control - only allow scrolling within specific scrollable elements
|
|
126
190
|
window.addEventListener(
|
|
127
191
|
"mousewheel",
|
|
128
192
|
function (event) {
|
|
129
|
-
if
|
|
130
|
-
|
|
193
|
+
// Check if we're on a scrollable textarea
|
|
194
|
+
const isScrollableTextarea =
|
|
195
|
+
event.target.tagName === "TEXTAREA" &&
|
|
196
|
+
(event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
|
|
197
|
+
|
|
198
|
+
// Check if we're in a table but only allow scrolling on actual scrollable content
|
|
199
|
+
const isInTable = event.target.closest(".table-component");
|
|
200
|
+
const isScrollableTableContent =
|
|
201
|
+
isInTable &&
|
|
202
|
+
(event.target.closest(".grid") || // Table rows
|
|
203
|
+
event.target.closest(".table-row") ||
|
|
204
|
+
(event.target.closest(".table-component") && !event.target.closest(".table-header")));
|
|
205
|
+
|
|
206
|
+
// Allow scrolling in navbar and modals
|
|
207
|
+
const isInNavbar = event.target.closest(".navbar") || event.target.closest(".mobile-menu");
|
|
208
|
+
const isInModal = event.target.closest('[role="dialog"]');
|
|
209
|
+
|
|
210
|
+
// Only allow these specific cases
|
|
211
|
+
if (isScrollableTextarea || isScrollableTableContent || isInNavbar || isInModal) {
|
|
212
|
+
// Stop propagation to prevent page scroll
|
|
213
|
+
if (isScrollableTableContent || isScrollableTextarea) {
|
|
214
|
+
event.stopPropagation();
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
131
217
|
}
|
|
218
|
+
|
|
219
|
+
event.preventDefault();
|
|
132
220
|
},
|
|
133
221
|
{ passive: false }
|
|
134
222
|
);
|
|
@@ -136,69 +224,349 @@ window.addEventListener(
|
|
|
136
224
|
window.addEventListener(
|
|
137
225
|
"DOMMouseScroll",
|
|
138
226
|
function (event) {
|
|
139
|
-
|
|
140
|
-
|
|
227
|
+
// Use same logic as mousewheel
|
|
228
|
+
const isScrollableTextarea =
|
|
229
|
+
event.target.tagName === "TEXTAREA" &&
|
|
230
|
+
(event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
|
|
231
|
+
|
|
232
|
+
const isInTable = event.target.closest(".table-component");
|
|
233
|
+
const isScrollableTableContent =
|
|
234
|
+
isInTable &&
|
|
235
|
+
(event.target.closest(".grid") ||
|
|
236
|
+
event.target.closest(".table-row") ||
|
|
237
|
+
(event.target.closest(".table-component") && !event.target.closest(".table-header")));
|
|
238
|
+
|
|
239
|
+
const isInNavbar = event.target.closest(".navbar") || event.target.closest(".mobile-menu");
|
|
240
|
+
const isInModal = event.target.closest('[role="dialog"]');
|
|
241
|
+
|
|
242
|
+
if (isScrollableTextarea || isScrollableTableContent || isInNavbar || isInModal) {
|
|
243
|
+
if (isScrollableTableContent || isScrollableTextarea) {
|
|
244
|
+
event.stopPropagation();
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
event.preventDefault();
|
|
250
|
+
},
|
|
251
|
+
{ passive: false }
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
window.addEventListener(
|
|
255
|
+
"wheel",
|
|
256
|
+
function (event) {
|
|
257
|
+
// Use same logic as mousewheel
|
|
258
|
+
const isScrollableTextarea =
|
|
259
|
+
event.target.tagName === "TEXTAREA" &&
|
|
260
|
+
(event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
|
|
261
|
+
|
|
262
|
+
const isInTable = event.target.closest(".table-component");
|
|
263
|
+
const isScrollableTableContent =
|
|
264
|
+
isInTable &&
|
|
265
|
+
(event.target.closest(".grid") ||
|
|
266
|
+
event.target.closest(".table-row") ||
|
|
267
|
+
(event.target.closest(".table-component") && !event.target.closest(".table-header")));
|
|
268
|
+
|
|
269
|
+
const isInNavbar = event.target.closest(".navbar") || event.target.closest(".mobile-menu");
|
|
270
|
+
const isInModal = event.target.closest('[role="dialog"]');
|
|
271
|
+
|
|
272
|
+
if (isScrollableTextarea || isScrollableTableContent || isInNavbar || isInModal) {
|
|
273
|
+
if (isScrollableTableContent || isScrollableTextarea) {
|
|
274
|
+
event.stopPropagation();
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
},
|
|
281
|
+
{ passive: false }
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
window.addEventListener(
|
|
285
|
+
"scroll",
|
|
286
|
+
function (event) {
|
|
287
|
+
event.preventDefault();
|
|
288
|
+
},
|
|
289
|
+
{ passive: false }
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
// Precise scroll control - only allow scrolling within specific scrollable elements
|
|
293
|
+
window.addEventListener(
|
|
294
|
+
"touchmove",
|
|
295
|
+
function (event) {
|
|
296
|
+
// Check if we're touching a scrollable element directly
|
|
297
|
+
const isScrollableTextarea =
|
|
298
|
+
event.target.tagName === "TEXTAREA" &&
|
|
299
|
+
(event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
|
|
300
|
+
|
|
301
|
+
// Check if we're in a table but only allow scrolling on actual scrollable content
|
|
302
|
+
const isInTable = event.target.closest(".table-component");
|
|
303
|
+
const isScrollableTableContent =
|
|
304
|
+
isInTable &&
|
|
305
|
+
(event.target.closest(".grid") || // Table rows
|
|
306
|
+
event.target.closest(".table-row") ||
|
|
307
|
+
(event.target.closest(".table-component") && !event.target.closest(".table-header")));
|
|
308
|
+
|
|
309
|
+
// Allow scrolling in navbar and modals (they handle their own boundaries)
|
|
310
|
+
const isInNavbar = event.target.closest(".navbar") || event.target.closest(".mobile-menu");
|
|
311
|
+
const isInModal = event.target.closest('[role="dialog"]');
|
|
312
|
+
|
|
313
|
+
// Only allow these specific cases
|
|
314
|
+
if (isScrollableTextarea || isScrollableTableContent || isInNavbar || isInModal) {
|
|
315
|
+
// For table content, ensure we stop propagation to prevent page scroll
|
|
316
|
+
if (isScrollableTableContent || isScrollableTextarea) {
|
|
317
|
+
event.stopPropagation();
|
|
318
|
+
}
|
|
319
|
+
return;
|
|
141
320
|
}
|
|
321
|
+
|
|
322
|
+
// Prevent everything else
|
|
323
|
+
event.preventDefault();
|
|
142
324
|
},
|
|
143
325
|
{ passive: false }
|
|
144
326
|
);
|
|
145
327
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
328
|
+
// PERFECT notch handling - simple and bulletproof
|
|
329
|
+
let notchTimeout;
|
|
330
|
+
let isNotchBusy = false;
|
|
331
|
+
let lastNotchState = null;
|
|
332
|
+
let animationTimeout;
|
|
333
|
+
let pendingUpdate = false;
|
|
334
|
+
|
|
335
|
+
function isLandscapeMode() {
|
|
336
|
+
// Check orientation first
|
|
337
|
+
let orientationLandscape = false;
|
|
338
|
+
|
|
339
|
+
if (typeof window.orientation !== "undefined") {
|
|
340
|
+
orientationLandscape = Math.abs(window.orientation) === 90;
|
|
341
|
+
} else if (screen.orientation && typeof screen.orientation.angle !== "undefined") {
|
|
342
|
+
orientationLandscape = Math.abs(screen.orientation.angle) === 90;
|
|
152
343
|
}
|
|
153
|
-
handleNotch();
|
|
154
|
-
};
|
|
155
344
|
|
|
156
|
-
|
|
157
|
-
window.
|
|
345
|
+
// Always also check dimensions as backup
|
|
346
|
+
const dimensionLandscape = window.innerWidth > window.innerHeight;
|
|
347
|
+
const hasStrongLandscapeRatio = window.innerWidth / window.innerHeight > 1.5;
|
|
158
348
|
|
|
159
|
-
|
|
160
|
-
|
|
349
|
+
// For iPhones, if dimensions clearly indicate landscape, trust that
|
|
350
|
+
if (isIOS() && !isIpadOS() && dimensionLandscape && hasStrongLandscapeRatio) {
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
161
353
|
|
|
162
|
-
//
|
|
163
|
-
|
|
354
|
+
// Otherwise use orientation if available, fallback to dimensions
|
|
355
|
+
return orientationLandscape || (dimensionLandscape && hasStrongLandscapeRatio);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function hasDeviceNotch() {
|
|
359
|
+
// Only check for notch on actual devices that might have one
|
|
360
|
+
if (!isIOS() || isIpadOS()) return false;
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
CSS.supports("padding-left: env(safe-area-inset-left)") &&
|
|
364
|
+
CSS.supports("padding-right: env(safe-area-inset-right)")
|
|
365
|
+
);
|
|
366
|
+
}
|
|
164
367
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
368
|
+
function handleNotch(force = false) {
|
|
369
|
+
// Simple debouncing
|
|
370
|
+
if (isNotchBusy && !force) return;
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
// Only for iPhone (not iPad)
|
|
374
|
+
if (!isIOS() || isIpadOS()) return;
|
|
375
|
+
|
|
376
|
+
const wrappers = document.querySelectorAll(".ios-wrapper");
|
|
377
|
+
if (wrappers.length === 0) {
|
|
378
|
+
setTimeout(() => handleNotch(force), 100);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const isLandscape = isLandscapeMode();
|
|
383
|
+
const hasNotch = hasDeviceNotch();
|
|
384
|
+
|
|
385
|
+
// Get orientation
|
|
386
|
+
let orientation = window.orientation;
|
|
387
|
+
if (typeof orientation === "undefined" && screen.orientation) {
|
|
388
|
+
orientation = screen.orientation.angle;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Create state signature to prevent redundant updates
|
|
392
|
+
const currentState = `${isLandscape}-${hasNotch}-${orientation}-${window.innerWidth}x${window.innerHeight}`;
|
|
393
|
+
if (lastNotchState === currentState && !force) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
lastNotchState = currentState;
|
|
397
|
+
|
|
398
|
+
isNotchBusy = true;
|
|
399
|
+
|
|
400
|
+
// Debug info
|
|
401
|
+
if (window.location.href.includes("localhost") || window.location.href.includes("5173")) {
|
|
402
|
+
console.log("🔥 Notch Debug:", {
|
|
403
|
+
isLandscape,
|
|
404
|
+
hasNotch,
|
|
405
|
+
orientation,
|
|
406
|
+
dimensions: `${window.innerWidth}x${window.innerHeight}`,
|
|
407
|
+
state: currentState
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (hasNotch && isLandscape) {
|
|
412
|
+
// Test actual safe area values to determine notch side
|
|
413
|
+
const testDiv = document.createElement("div");
|
|
414
|
+
testDiv.style.position = "fixed";
|
|
415
|
+
testDiv.style.top = "0";
|
|
416
|
+
testDiv.style.left = "0";
|
|
417
|
+
testDiv.style.visibility = "hidden";
|
|
418
|
+
testDiv.style.paddingLeft = "env(safe-area-inset-left)";
|
|
419
|
+
testDiv.style.paddingRight = "env(safe-area-inset-right)";
|
|
420
|
+
document.body.appendChild(testDiv);
|
|
421
|
+
|
|
422
|
+
const computedStyle = getComputedStyle(testDiv);
|
|
423
|
+
const leftInset = parseFloat(computedStyle.paddingLeft) || 0;
|
|
424
|
+
const rightInset = parseFloat(computedStyle.paddingRight) || 0;
|
|
425
|
+
|
|
426
|
+
document.body.removeChild(testDiv);
|
|
427
|
+
|
|
428
|
+
console.log("🔍 Safe area insets:", { leftInset, rightInset });
|
|
429
|
+
|
|
430
|
+
// Apply styles instantly - NO ANIMATION
|
|
431
|
+
wrappers.forEach((wrapper) => {
|
|
432
|
+
if (leftInset > 0) {
|
|
433
|
+
// Notch on left
|
|
172
434
|
wrapper.style.paddingLeft = "env(safe-area-inset-left)";
|
|
173
435
|
wrapper.style.paddingRight = "0.5rem";
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
wrappers.forEach((wrapper) => {
|
|
436
|
+
} else if (rightInset > 0) {
|
|
437
|
+
// Notch on right
|
|
438
|
+
wrapper.style.paddingLeft = "0.5rem";
|
|
178
439
|
wrapper.style.paddingRight = "env(safe-area-inset-right)";
|
|
440
|
+
} else {
|
|
441
|
+
// Fallback - no detectable notch
|
|
179
442
|
wrapper.style.paddingLeft = "0.5rem";
|
|
180
|
-
|
|
181
|
-
|
|
443
|
+
wrapper.style.paddingRight = "0.5rem";
|
|
444
|
+
}
|
|
445
|
+
});
|
|
182
446
|
} else {
|
|
447
|
+
// Portrait or no notch
|
|
448
|
+
const padding =
|
|
449
|
+
window.innerWidth > 1280
|
|
450
|
+
? "2.5rem"
|
|
451
|
+
: window.innerWidth > 1030
|
|
452
|
+
? "1.5rem"
|
|
453
|
+
: window.innerWidth > 768
|
|
454
|
+
? "0.5rem"
|
|
455
|
+
: "0.5rem";
|
|
456
|
+
|
|
457
|
+
// Apply styles instantly - NO ANIMATION
|
|
183
458
|
wrappers.forEach((wrapper) => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
window.innerWidth > 1280
|
|
187
|
-
? "2.5rem"
|
|
188
|
-
: window.innerWidth > 1030
|
|
189
|
-
? "1.5rem"
|
|
190
|
-
: window.innerWidth > 768
|
|
191
|
-
? "0.5rem"
|
|
192
|
-
: "0.5rem";
|
|
193
|
-
wrapper.style.paddingLeft = amount;
|
|
194
|
-
wrapper.style.paddingRight = amount;
|
|
459
|
+
wrapper.style.paddingLeft = padding;
|
|
460
|
+
wrapper.style.paddingRight = padding;
|
|
195
461
|
});
|
|
196
462
|
}
|
|
463
|
+
|
|
464
|
+
isNotchBusy = false;
|
|
465
|
+
} catch (error) {
|
|
466
|
+
console.warn("Notch error:", error);
|
|
467
|
+
isNotchBusy = false;
|
|
197
468
|
}
|
|
198
469
|
}
|
|
199
470
|
|
|
200
|
-
|
|
201
|
-
|
|
471
|
+
function triggerNotch() {
|
|
472
|
+
clearTimeout(notchTimeout);
|
|
473
|
+
notchTimeout = setTimeout(handleNotch, 50);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Event listeners
|
|
477
|
+
window.addEventListener("orientationchange", triggerNotch);
|
|
478
|
+
window.addEventListener("resize", triggerNotch);
|
|
479
|
+
if (screen.orientation) {
|
|
480
|
+
screen.orientation.addEventListener("change", triggerNotch);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Aggressive initial setup to handle all scenarios
|
|
484
|
+
function initNotchHandling() {
|
|
485
|
+
handleNotch(true); // Force first execution
|
|
486
|
+
setTimeout(() => handleNotch(true), 50);
|
|
487
|
+
setTimeout(() => handleNotch(true), 150);
|
|
488
|
+
setTimeout(() => handleNotch(true), 300);
|
|
489
|
+
setTimeout(() => handleNotch(true), 500);
|
|
490
|
+
setTimeout(() => handleNotch(true), 1000);
|
|
491
|
+
setTimeout(() => handleNotch(true), 2000); // Extra long delay for slow loads
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Multiple initialization points
|
|
495
|
+
if (document.readyState === "loading") {
|
|
496
|
+
document.addEventListener("DOMContentLoaded", initNotchHandling);
|
|
497
|
+
} else {
|
|
498
|
+
initNotchHandling();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
window.addEventListener("load", initNotchHandling);
|
|
502
|
+
|
|
503
|
+
// Also run when page becomes visible (handles app switching)
|
|
504
|
+
document.addEventListener("visibilitychange", () => {
|
|
505
|
+
if (!document.hidden) {
|
|
506
|
+
setTimeout(handleNotch, 100);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// Run on focus (when returning to app)
|
|
511
|
+
window.addEventListener("focus", () => {
|
|
512
|
+
setTimeout(handleNotch, 100);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// Watch for ios-wrapper elements appearing in DOM
|
|
516
|
+
const observer = new MutationObserver((mutations) => {
|
|
517
|
+
let shouldTrigger = false;
|
|
518
|
+
|
|
519
|
+
mutations.forEach((mutation) => {
|
|
520
|
+
if (mutation.type === "childList") {
|
|
521
|
+
mutation.addedNodes.forEach((node) => {
|
|
522
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
523
|
+
if (node.classList?.contains("ios-wrapper") || node.querySelector?.(".ios-wrapper")) {
|
|
524
|
+
shouldTrigger = true;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
if (shouldTrigger) {
|
|
532
|
+
setTimeout(() => handleNotch(true), 10);
|
|
533
|
+
setTimeout(() => handleNotch(true), 100);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Start observing
|
|
538
|
+
if (document.body) {
|
|
539
|
+
observer.observe(document.body, {
|
|
540
|
+
childList: true,
|
|
541
|
+
subtree: true
|
|
542
|
+
});
|
|
543
|
+
} else {
|
|
544
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
545
|
+
observer.observe(document.body, {
|
|
546
|
+
childList: true,
|
|
547
|
+
subtree: true
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Navigation handling
|
|
553
|
+
const originalPushState = history.pushState;
|
|
554
|
+
const originalReplaceState = history.replaceState;
|
|
555
|
+
|
|
556
|
+
history.pushState = function (...args) {
|
|
557
|
+
originalPushState.apply(this, args);
|
|
558
|
+
triggerNotch();
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
history.replaceState = function (...args) {
|
|
562
|
+
originalReplaceState.apply(this, args);
|
|
563
|
+
triggerNotch();
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
window.addEventListener("popstate", triggerNotch);
|
|
567
|
+
|
|
568
|
+
// Expose for manual triggering
|
|
569
|
+
window.simulateRotate = handleNotch;
|
|
202
570
|
|
|
203
571
|
const pullStart = (e) => {
|
|
204
572
|
const { screenY } = e.targetTouches[0];
|
|
@@ -252,33 +620,57 @@ function maxPull() {
|
|
|
252
620
|
return 250;
|
|
253
621
|
}
|
|
254
622
|
}
|
|
623
|
+
// Vue router integration - aggressive triggering on route changes
|
|
255
624
|
watch(
|
|
256
625
|
() => router.currentRoute.value.name,
|
|
257
626
|
() => {
|
|
258
|
-
|
|
627
|
+
// Immediate triggers
|
|
628
|
+
handleNotch();
|
|
629
|
+
triggerNotch();
|
|
630
|
+
|
|
631
|
+
// Staggered attempts
|
|
632
|
+
setTimeout(handleNotch, 10);
|
|
633
|
+
setTimeout(handleNotch, 50);
|
|
634
|
+
setTimeout(handleNotch, 150);
|
|
635
|
+
setTimeout(handleNotch, 300);
|
|
636
|
+
setTimeout(handleNotch, 500);
|
|
637
|
+
}
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
watch(
|
|
641
|
+
() => router.currentRoute.value.path,
|
|
642
|
+
() => {
|
|
643
|
+
// Immediate triggers
|
|
644
|
+
handleNotch();
|
|
645
|
+
triggerNotch();
|
|
646
|
+
|
|
647
|
+
// Staggered attempts
|
|
648
|
+
setTimeout(handleNotch, 25);
|
|
649
|
+
setTimeout(handleNotch, 100);
|
|
650
|
+
setTimeout(handleNotch, 250);
|
|
259
651
|
}
|
|
260
652
|
);
|
|
261
653
|
const layout = computed(() => router.currentRoute.value.meta.layout);
|
|
262
654
|
</script>
|
|
263
655
|
<style lang="scss">
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
656
|
+
// Ultra bulletproof scroll prevention
|
|
657
|
+
html,
|
|
658
|
+
body {
|
|
659
|
+
overflow: hidden !important;
|
|
660
|
+
overscroll-behavior: none !important;
|
|
661
|
+
width: 100% !important;
|
|
662
|
+
height: 100% !important;
|
|
663
|
+
touch-action: none !important;
|
|
270
664
|
}
|
|
271
665
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
666
|
+
#app {
|
|
667
|
+
overflow: hidden !important;
|
|
668
|
+
overscroll-behavior: none !important;
|
|
669
|
+
width: 100% !important;
|
|
670
|
+
height: 100vh !important;
|
|
671
|
+
touch-action: none !important;
|
|
275
672
|
}
|
|
276
673
|
|
|
277
|
-
/* Hide scrollbar for IE, Edge and Firefox */
|
|
278
|
-
.hidden-scrollbars {
|
|
279
|
-
-ms-overflow-style: none; /* IE and Edge */
|
|
280
|
-
scrollbar-width: none; /* Firefox */
|
|
281
|
-
}
|
|
282
674
|
.dropdown {
|
|
283
675
|
position: relative;
|
|
284
676
|
display: inline-block;
|
|
@@ -292,19 +684,19 @@ const layout = computed(() => router.currentRoute.value.meta.layout);
|
|
|
292
684
|
z-index: 1;
|
|
293
685
|
}
|
|
294
686
|
|
|
295
|
-
.smooth-hover {
|
|
296
|
-
@apply hover:opacity-70 active:opacity-50 duration-150;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
687
|
.layout {
|
|
300
688
|
@apply flex flex-col;
|
|
301
689
|
min-height: 90vh;
|
|
690
|
+
overflow: hidden !important;
|
|
691
|
+
height: 100vh !important;
|
|
302
692
|
}
|
|
693
|
+
|
|
303
694
|
.router-wrapper {
|
|
304
695
|
@apply pt-5;
|
|
305
696
|
transition: margin 0.25s;
|
|
306
697
|
z-index: 0;
|
|
307
698
|
}
|
|
699
|
+
|
|
308
700
|
.refresh-container {
|
|
309
701
|
transition: margin 0.25s;
|
|
310
702
|
}
|
|
@@ -312,4 +704,25 @@ const layout = computed(() => router.currentRoute.value.meta.layout);
|
|
|
312
704
|
.component-container {
|
|
313
705
|
@apply w-full mx-auto px-4 xs:px-4 md:px-2 lg:px-6 xl:px-10;
|
|
314
706
|
}
|
|
707
|
+
|
|
708
|
+
// Page navigation transitions
|
|
709
|
+
.page-transition-enter-active {
|
|
710
|
+
transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.page-transition-leave-active {
|
|
714
|
+
transition: all 0.15s cubic-bezier(0.55, 0.085, 0.68, 0.53);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.page-transition-enter-from {
|
|
718
|
+
opacity: 0;
|
|
719
|
+
transform: translateX(15px) scale(0.98);
|
|
720
|
+
filter: blur(1px);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
.page-transition-leave-to {
|
|
724
|
+
opacity: 0;
|
|
725
|
+
transform: translateX(-10px) scale(1.02);
|
|
726
|
+
filter: blur(0.5px);
|
|
727
|
+
}
|
|
315
728
|
</style>
|