@necrolab/dashboard 0.5.14 → 0.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/api.js +2 -3
- package/eslint.config.js +46 -0
- package/index.html +2 -1
- package/package.json +5 -2
- package/src/App.vue +140 -170
- package/src/assets/css/base/mixins.scss +72 -0
- package/src/assets/css/base/reset.scss +0 -2
- package/src/assets/css/base/scroll.scss +43 -36
- package/src/assets/css/base/typography.scss +9 -10
- package/src/assets/css/base/variables.scss +43 -0
- package/src/assets/css/components/accessibility.scss +37 -0
- package/src/assets/css/components/buttons.scss +58 -15
- package/src/assets/css/components/forms.scss +31 -32
- package/src/assets/css/components/headers.scss +119 -0
- package/src/assets/css/components/modals.scss +2 -2
- package/src/assets/css/components/search-groups.scss +28 -19
- package/src/assets/css/components/tables.scss +5 -7
- package/src/assets/css/components/toasts.scss +7 -7
- package/src/assets/css/components/utilities.scss +220 -0
- package/src/assets/css/main.scss +72 -75
- package/src/components/Auth/LoginForm.vue +5 -84
- package/src/components/Editors/Account/Account.vue +8 -10
- package/src/components/Editors/Account/AccountCreator.vue +28 -59
- package/src/components/Editors/Account/AccountView.vue +38 -86
- package/src/components/Editors/Account/CreateAccount.vue +8 -50
- package/src/components/Editors/Profile/CreateProfile.vue +74 -131
- package/src/components/Editors/Profile/Profile.vue +15 -17
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
- package/src/components/Editors/Profile/ProfileView.vue +46 -96
- package/src/components/Editors/TagLabel.vue +16 -55
- package/src/components/Editors/TagToggle.vue +20 -8
- package/src/components/Filter/Filter.vue +62 -75
- package/src/components/Filter/FilterPreview.vue +161 -135
- package/src/components/Filter/PriceSortToggle.vue +36 -43
- package/src/components/Table/Header.vue +1 -1
- package/src/components/Table/Table.vue +61 -12
- package/src/components/Tasks/CheckStock.vue +7 -16
- package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
- package/src/components/Tasks/Controls/MobileControls.vue +5 -20
- package/src/components/Tasks/CreateTaskAXS.vue +20 -118
- package/src/components/Tasks/CreateTaskTM.vue +33 -189
- package/src/components/Tasks/EventDetailRow.vue +21 -0
- package/src/components/Tasks/MassEdit.vue +6 -16
- package/src/components/Tasks/QuickSettings.vue +140 -216
- package/src/components/Tasks/ScrapeVenue.vue +4 -13
- package/src/components/Tasks/Stats.vue +19 -38
- package/src/components/Tasks/Task.vue +65 -268
- package/src/components/Tasks/TaskLabel.vue +9 -3
- package/src/components/Tasks/TaskView.vue +43 -63
- package/src/components/Tasks/Utilities.vue +10 -42
- package/src/components/Tasks/ViewTask.vue +23 -107
- package/src/components/icons/Close.vue +2 -8
- package/src/components/icons/Gear.vue +8 -8
- package/src/components/icons/Hash.vue +5 -0
- package/src/components/icons/Key.vue +2 -8
- package/src/components/icons/Pencil.vue +2 -8
- package/src/components/icons/Profile.vue +2 -8
- package/src/components/icons/Sell.vue +2 -8
- package/src/components/icons/Spinner.vue +4 -7
- package/src/components/icons/SquareCheck.vue +2 -8
- package/src/components/icons/SquareUncheck.vue +2 -8
- package/src/components/icons/Wildcard.vue +2 -8
- package/src/components/icons/index.js +3 -1
- package/src/components/ui/ActionButtonGroup.vue +113 -52
- package/src/components/ui/BalanceIndicator.vue +60 -0
- package/src/components/ui/EmptyState.vue +24 -0
- package/src/components/ui/EnableDisableToggle.vue +23 -0
- package/src/components/ui/FormField.vue +48 -48
- package/src/components/ui/IconLabel.vue +23 -0
- package/src/components/ui/InfoRow.vue +21 -54
- package/src/components/ui/Modal.vue +78 -37
- package/src/components/ui/Navbar.vue +60 -41
- package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
- package/src/components/ui/ReconnectIndicator.vue +111 -124
- package/src/components/ui/SectionCard.vue +6 -14
- package/src/components/ui/Splash.vue +2 -10
- package/src/components/ui/StatusBadge.vue +26 -28
- package/src/components/ui/TaskToggle.vue +54 -0
- package/src/components/ui/controls/CountryChooser.vue +27 -64
- package/src/components/ui/controls/EyeToggle.vue +1 -1
- package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
- package/src/components/ui/controls/atomic/Dropdown.vue +102 -95
- package/src/components/ui/controls/atomic/MultiDropdown.vue +72 -94
- package/src/components/ui/controls/atomic/Switch.vue +21 -84
- package/src/composables/useColorMapping.js +15 -0
- package/src/composables/useCopyToClipboard.js +1 -1
- package/src/composables/useDateFormatting.js +21 -0
- package/src/composables/useDeviceDetection.js +14 -0
- package/src/composables/useDropdownPosition.js +5 -6
- package/src/composables/useDynamicTableHeight.js +31 -0
- package/src/composables/useRowSelection.js +0 -3
- package/src/composables/useTicketPricing.js +16 -0
- package/src/composables/useWindowDimensions.js +21 -0
- package/src/libs/Filter.js +14 -20
- package/src/libs/panzoom.js +1 -5
- package/src/libs/utils/array.js +60 -0
- package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
- package/src/libs/utils/eventUrl.js +40 -0
- package/src/libs/utils/string.js +28 -0
- package/src/libs/utils/time.js +20 -0
- package/src/libs/utils/validation.js +88 -0
- package/src/main.js +0 -2
- package/src/stores/connection.js +1 -4
- package/src/stores/logger.js +6 -12
- package/src/stores/sampleData.js +1 -2
- package/src/stores/ui.js +59 -36
- package/src/views/Accounts.vue +17 -31
- package/src/views/Console.vue +76 -176
- package/src/views/Editor.vue +217 -383
- package/src/views/FilterBuilder.vue +190 -373
- package/src/views/Login.vue +3 -28
- package/src/views/Profiles.vue +12 -22
- package/src/views/Tasks.vue +51 -38
- package/tailwind.config.js +82 -71
- package/workbox-config.cjs +47 -5
- package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2416
- package/exit +0 -209
- package/run +0 -177
- package/switch-branch.sh +0 -41
- /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
package/backend/api.js
CHANGED
|
@@ -147,7 +147,7 @@ app.ws("/api/updates", async function (ws, req) {
|
|
|
147
147
|
{ event: "set-button-disabled", button: "add-accounts", value: false }
|
|
148
148
|
])
|
|
149
149
|
);
|
|
150
|
-
setTimeout(() => send({ event: "init-tasks", tasks: endpoints.getStrippedTasks(
|
|
150
|
+
setTimeout(() => send({ event: "init-tasks", tasks: endpoints.getStrippedTasks() }));
|
|
151
151
|
setTimeout(() => send({ event: "init-profiles", profiles: Bot.Profiles }));
|
|
152
152
|
setTimeout(() => send({ event: "init-tm-accounts", accounts: Bot.TM.Accounts }));
|
|
153
153
|
setTimeout(() => send({ event: "init-axs-accounts", accounts: Bot.AXS.Accounts }));
|
|
@@ -429,8 +429,7 @@ app.post("/api/proxies", async (req, res) => {
|
|
|
429
429
|
});
|
|
430
430
|
|
|
431
431
|
app.post("/api/userconfig/set", async (req, res) => {
|
|
432
|
-
|
|
433
|
-
const { field, value } = req.body;
|
|
432
|
+
const { value } = req.body;
|
|
434
433
|
|
|
435
434
|
users[0].proxyList = value;
|
|
436
435
|
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import vue from "eslint-plugin-vue";
|
|
4
|
+
import js from "@eslint/js";
|
|
5
|
+
import { FlatCompat } from "@eslint/eslintrc";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { dirname } from "node:path";
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const compat = new FlatCompat({
|
|
11
|
+
baseDirectory: __dirname,
|
|
12
|
+
recommendedConfig: js.configs.recommended,
|
|
13
|
+
allConfig: js.configs.all
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export default defineConfig([
|
|
17
|
+
...compat.extends("eslint:recommended", "plugin:vue/vue3-essential"),
|
|
18
|
+
{
|
|
19
|
+
languageOptions: {
|
|
20
|
+
globals: {
|
|
21
|
+
...globals.browser,
|
|
22
|
+
...globals.node,
|
|
23
|
+
Bot: true,
|
|
24
|
+
refreshTaskOnFrontEnd: true,
|
|
25
|
+
pushWSUpdate: true,
|
|
26
|
+
__APP_VERSION__: "readonly"
|
|
27
|
+
},
|
|
28
|
+
ecmaVersion: "latest",
|
|
29
|
+
sourceType: "module",
|
|
30
|
+
parserOptions: {}
|
|
31
|
+
},
|
|
32
|
+
plugins: {
|
|
33
|
+
vue
|
|
34
|
+
},
|
|
35
|
+
rules: {
|
|
36
|
+
"html.validate.scripts": 0,
|
|
37
|
+
"html.validate.styles": 0,
|
|
38
|
+
"vue/multi-word-component-names": "off",
|
|
39
|
+
"no-unused-vars": [
|
|
40
|
+
"warn",
|
|
41
|
+
{ vars: "all", args: "after-used", ignoreRestSiblings: true }
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
globalIgnores(["src/registerServiceWorker.js", "src/libs/panzoom.js"])
|
|
46
|
+
]);
|
package/index.html
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
/>
|
|
10
10
|
<!-- Lock iPhone to portrait orientation only -->
|
|
11
11
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
12
|
+
<meta name="mobile-web-app-capable" content="yes" />
|
|
12
13
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
13
14
|
<meta name="description" content="Necro Lab - dashboard" />
|
|
14
15
|
<meta name="darkreader-lock" />
|
|
@@ -36,8 +37,8 @@
|
|
|
36
37
|
/>
|
|
37
38
|
|
|
38
39
|
<!-- Preload critical assets -->
|
|
39
|
-
<link rel="preload" as="image" href="/img/background.svg" />
|
|
40
40
|
<link rel="preload" as="image" href="/img/logo_trans.png" />
|
|
41
|
+
<link rel="preload" as="image" href="/img/reconnect-logo.png" />
|
|
41
42
|
|
|
42
43
|
<!-- Prefetch core navigation icons -->
|
|
43
44
|
<link rel="prefetch" as="image" href="/img/close.svg" />
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@necrolab/dashboard",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"build": "rm -rf dist && npx workbox-cli generateSW workbox-config.cjs
|
|
6
|
+
"build": "rm -rf dist && vite build && npx workbox-cli generateSW workbox-config.cjs",
|
|
7
7
|
"dev": "node dev-server.js",
|
|
8
8
|
"bot": "node dev-server.js",
|
|
9
9
|
"expose": "node dev-server.js",
|
|
@@ -42,8 +42,11 @@
|
|
|
42
42
|
},
|
|
43
43
|
"main": "index.js",
|
|
44
44
|
"devDependencies": {
|
|
45
|
+
"@eslint/eslintrc": "^3.3.3",
|
|
46
|
+
"@eslint/js": "^9.39.2",
|
|
45
47
|
"eslint": "^9.17.0",
|
|
46
48
|
"eslint-plugin-vue": "^9.32.0",
|
|
49
|
+
"globals": "^17.3.0",
|
|
47
50
|
"workbox-cli": "^7.3.0"
|
|
48
51
|
},
|
|
49
52
|
"overrides": {
|
package/src/App.vue
CHANGED
|
@@ -1,58 +1,34 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="layout min-h-screen h-screen">
|
|
3
|
+
<!-- iPhone Landscape Lock Overlay -->
|
|
4
|
+
<div v-if="showLandscapeLock" class="iphone-landscape-lock">
|
|
5
|
+
<div class="rotate-message">
|
|
6
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="rotate-icon">
|
|
7
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
8
|
+
</svg>
|
|
9
|
+
<p class="rotate-text">Please rotate your device to portrait mode</p>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
3
13
|
<transition name="fade">
|
|
4
14
|
<Splash v-if="isLoading" />
|
|
5
15
|
</transition>
|
|
6
|
-
<div class="refresh-wrapper">
|
|
7
|
-
<div
|
|
8
|
-
class="refresh-container w-fit mx-auto"
|
|
9
|
-
:style="{
|
|
10
|
-
'margin-top': maxPull() + 'px',
|
|
11
|
-
transform: `rotate(${ui.pullChange}deg)`
|
|
12
|
-
}">
|
|
13
|
-
<div
|
|
14
|
-
class="refresh-icon p-2 rounded-full mx-auto duration-200"
|
|
15
|
-
:class="{
|
|
16
|
-
'opacity-100': ui.pullChange > 250,
|
|
17
|
-
'opacity-0': ui.pullChange < 250
|
|
18
|
-
}">
|
|
19
|
-
<svg
|
|
20
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
21
|
-
fill="none"
|
|
22
|
-
viewBox="0 0 24 24"
|
|
23
|
-
class="stroke-2 w-4 stroke-dark-400"
|
|
24
|
-
:class="{ 'opacity-0': ui.pullChange == 0 }">
|
|
25
|
-
<path
|
|
26
|
-
strokeLinecap="round"
|
|
27
|
-
strokeLinejoin="round"
|
|
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" />
|
|
29
|
-
</svg>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
<h2
|
|
33
|
-
class="text-dark-400 text-center duration-200"
|
|
34
|
-
:class="{
|
|
35
|
-
'opacity-100': ui.pullChange > 250,
|
|
36
|
-
'opacity-0': ui.pullChange < 250
|
|
37
|
-
}">
|
|
38
|
-
Release to refresh
|
|
39
|
-
</h2>
|
|
40
|
-
</div>
|
|
41
16
|
<transition name="out-in">
|
|
42
17
|
<div v-if="spinner" key="reconnect-indicator" class="h-full">
|
|
43
18
|
<ReconnectIndicator :message="ui.spinnerMessage" />
|
|
44
19
|
</div>
|
|
45
20
|
<div v-else key="main-components" class="flex">
|
|
46
21
|
<Navbar v-if="layout == 'dashboard'" class="fixed" />
|
|
47
|
-
<div
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
22
|
+
<div class="router-wrapper w-full">
|
|
23
|
+
<router-view v-slot="{ Component, route }">
|
|
24
|
+
<transition name="page-transition" mode="out-in">
|
|
25
|
+
<component
|
|
26
|
+
:is="Component"
|
|
27
|
+
:key="route.path"
|
|
28
|
+
class="component-container w-full mx-auto px-3 xs:px-3 md:px-2 lg:px-6 xl:px-10 pb-2 ios-wrapper" />
|
|
29
|
+
</transition>
|
|
30
|
+
</router-view>
|
|
31
|
+
</div>
|
|
56
32
|
</div>
|
|
57
33
|
</transition>
|
|
58
34
|
</div>
|
|
@@ -66,27 +42,18 @@ import Navbar from "@/components/ui/Navbar.vue";
|
|
|
66
42
|
import { useUIStore } from "@/stores/ui";
|
|
67
43
|
import Splash from "@/components/ui/Splash.vue";
|
|
68
44
|
import ReconnectIndicator from "@/components/ui/ReconnectIndicator.vue";
|
|
45
|
+
import { DEBUG } from "@/utils/debug";
|
|
46
|
+
import { useDeviceDetection } from "@/composables/useDeviceDetection";
|
|
69
47
|
|
|
70
48
|
const ui = useUIStore();
|
|
49
|
+
const { showSpinner: spinner } = storeToRefs(ui);
|
|
71
50
|
const router = useRouter();
|
|
72
51
|
const isLoading = ref(false);
|
|
73
|
-
const
|
|
52
|
+
const showLandscapeLock = ref(false);
|
|
74
53
|
|
|
75
|
-
|
|
76
|
-
if (/iPad|iPhone|iPod/.test(navigator.platform)) {
|
|
77
|
-
return true;
|
|
78
|
-
} else {
|
|
79
|
-
return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function isIpadOS() {
|
|
84
|
-
return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform);
|
|
85
|
-
}
|
|
54
|
+
const { isIOS, isIpadOS } = useDeviceDetection();
|
|
86
55
|
|
|
87
56
|
// Prevent pinch-to-zoom gestures
|
|
88
|
-
let lastTouchDistance = 0;
|
|
89
|
-
|
|
90
57
|
document.addEventListener('touchstart', (e) => {
|
|
91
58
|
if (e.touches.length > 1) {
|
|
92
59
|
e.preventDefault();
|
|
@@ -123,34 +90,54 @@ document.addEventListener('touchend', (e) => {
|
|
|
123
90
|
|
|
124
91
|
if (!window.location.href.includes(":5173")) ui.startSpinner("Loading...");
|
|
125
92
|
|
|
93
|
+
// Handle iPhone landscape lock
|
|
94
|
+
function updateLandscapeLock() {
|
|
95
|
+
if (isIOS() && !isIpadOS()) {
|
|
96
|
+
showLandscapeLock.value = isLandscapeMode();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Update on orientation/resize
|
|
101
|
+
window.addEventListener("orientationchange", updateLandscapeLock);
|
|
102
|
+
window.addEventListener("resize", updateLandscapeLock);
|
|
103
|
+
updateLandscapeLock();
|
|
104
|
+
|
|
126
105
|
// Ensure notch handling runs when Vue app mounts
|
|
127
106
|
onMounted(() => {
|
|
128
|
-
//
|
|
129
|
-
handleNotch();
|
|
130
|
-
setTimeout(handleNotch, 10);
|
|
131
|
-
setTimeout(handleNotch, 50);
|
|
132
|
-
setTimeout(handleNotch, 150);
|
|
133
|
-
setTimeout(handleNotch, 300);
|
|
134
|
-
|
|
135
|
-
// Set up a recurring check for the first few seconds
|
|
107
|
+
// Optimized: Single debounced handler instead of multiple setTimeouts
|
|
136
108
|
let attempts = 0;
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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);
|
|
144
121
|
});
|
|
145
122
|
|
|
146
|
-
//
|
|
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
|
+
};
|
|
133
|
+
|
|
147
134
|
document.onkeydown = function (evt) {
|
|
148
135
|
evt = evt || window.event;
|
|
149
|
-
|
|
136
|
+
let isEscape = false;
|
|
150
137
|
if ("key" in evt) {
|
|
151
138
|
isEscape = evt.key === "Escape" || evt.key === "Esc";
|
|
152
139
|
} else {
|
|
153
|
-
isEscape = evt.keyCode ===
|
|
140
|
+
isEscape = evt.keyCode === KEY_CODES.ESCAPE;
|
|
154
141
|
}
|
|
155
142
|
if (isEscape) {
|
|
156
143
|
ui.activeModal = "";
|
|
@@ -162,18 +149,18 @@ document.addEventListener("keydown", function (event) {
|
|
|
162
149
|
// Prevent Ctrl/Cmd + Plus/Minus/0 (various keycodes for different browsers)
|
|
163
150
|
if (
|
|
164
151
|
(event.ctrlKey || event.metaKey) &&
|
|
165
|
-
(event.which ===
|
|
166
|
-
event.which ===
|
|
167
|
-
event.which ===
|
|
168
|
-
event.which ===
|
|
169
|
-
event.which ===
|
|
170
|
-
event.which ===
|
|
171
|
-
event.keyCode ===
|
|
172
|
-
event.keyCode ===
|
|
173
|
-
event.keyCode ===
|
|
174
|
-
event.keyCode ===
|
|
175
|
-
event.keyCode ===
|
|
176
|
-
event.keyCode ===
|
|
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 ||
|
|
177
164
|
event.key === '+' ||
|
|
178
165
|
event.key === '-' ||
|
|
179
166
|
event.key === '=' ||
|
|
@@ -233,9 +220,7 @@ if (isIOSDevice) {
|
|
|
233
220
|
// Simplified scroll control - let scrollable elements handle their own scrolling
|
|
234
221
|
// Page scroll is already prevented by overflow: hidden on html/body
|
|
235
222
|
|
|
236
|
-
// DOMMouseScroll handler removed - CSS overflow handles scroll prevention
|
|
237
223
|
|
|
238
|
-
// Wheel and scroll handlers removed - CSS overflow: hidden on html/body prevents page scroll
|
|
239
224
|
|
|
240
225
|
// Precise scroll control - only allow scrolling within specific scrollable elements
|
|
241
226
|
window.addEventListener(
|
|
@@ -282,12 +267,9 @@ window.addEventListener(
|
|
|
282
267
|
{ passive: false }
|
|
283
268
|
);
|
|
284
269
|
|
|
285
|
-
// PERFECT notch handling - simple and bulletproof
|
|
286
270
|
let notchTimeout;
|
|
287
271
|
let isNotchBusy = false;
|
|
288
272
|
let lastNotchState = null;
|
|
289
|
-
let animationTimeout;
|
|
290
|
-
let pendingUpdate = false;
|
|
291
273
|
|
|
292
274
|
function isLandscapeMode() {
|
|
293
275
|
// Check orientation first
|
|
@@ -354,9 +336,8 @@ function handleNotch(force = false) {
|
|
|
354
336
|
|
|
355
337
|
isNotchBusy = true;
|
|
356
338
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
console.log("🔥 Notch Debug:", {
|
|
339
|
+
if (DEBUG) {
|
|
340
|
+
ui.logger.Debug("🔥 Notch Debug:", {
|
|
360
341
|
isLandscape,
|
|
361
342
|
hasNotch,
|
|
362
343
|
orientation,
|
|
@@ -382,7 +363,7 @@ function handleNotch(force = false) {
|
|
|
382
363
|
|
|
383
364
|
document.body.removeChild(testDiv);
|
|
384
365
|
|
|
385
|
-
|
|
366
|
+
if (DEBUG) ui.logger.Debug("🔍 Safe area insets:", { leftInset, rightInset });
|
|
386
367
|
|
|
387
368
|
// Apply styles instantly - NO ANIMATION
|
|
388
369
|
wrappers.forEach((wrapper) => {
|
|
@@ -401,15 +382,22 @@ function handleNotch(force = false) {
|
|
|
401
382
|
}
|
|
402
383
|
});
|
|
403
384
|
} else {
|
|
404
|
-
// Portrait or no notch
|
|
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
|
+
|
|
405
393
|
const padding =
|
|
406
|
-
window.innerWidth >
|
|
407
|
-
?
|
|
408
|
-
: window.innerWidth >
|
|
409
|
-
?
|
|
410
|
-
: window.innerWidth >
|
|
411
|
-
?
|
|
412
|
-
:
|
|
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;
|
|
413
401
|
|
|
414
402
|
// Apply styles instantly - NO ANIMATION
|
|
415
403
|
wrappers.forEach((wrapper) => {
|
|
@@ -420,7 +408,7 @@ function handleNotch(force = false) {
|
|
|
420
408
|
|
|
421
409
|
isNotchBusy = false;
|
|
422
410
|
} catch (error) {
|
|
423
|
-
|
|
411
|
+
if (DEBUG) ui.logger.Yellow("Notch error:", error);
|
|
424
412
|
isNotchBusy = false;
|
|
425
413
|
}
|
|
426
414
|
}
|
|
@@ -525,58 +513,7 @@ window.addEventListener("popstate", triggerNotch);
|
|
|
525
513
|
// Expose for manual triggering
|
|
526
514
|
window.simulateRotate = handleNotch;
|
|
527
515
|
|
|
528
|
-
|
|
529
|
-
const { screenY } = e.targetTouches[0];
|
|
530
|
-
ui.setStartPoint(screenY);
|
|
531
|
-
};
|
|
532
|
-
const initLoading = () => {
|
|
533
|
-
// refreshCont.current.classList.add("loading");
|
|
534
|
-
setTimeout(() => {
|
|
535
|
-
isLoading.value = true;
|
|
536
|
-
}, 500);
|
|
537
|
-
setTimeout(() => {
|
|
538
|
-
window.location.reload();
|
|
539
|
-
}, 1500);
|
|
540
|
-
};
|
|
541
|
-
const pull = (e) => {
|
|
542
|
-
/**
|
|
543
|
-
* get the current user touch event data
|
|
544
|
-
*/
|
|
545
|
-
const touch = e.targetTouches[0];
|
|
546
|
-
/**
|
|
547
|
-
* get the touch position on the screen's Y axis
|
|
548
|
-
*/
|
|
549
|
-
const { screenY } = touch;
|
|
550
|
-
/**
|
|
551
|
-
* The length of the pull
|
|
552
|
-
*
|
|
553
|
-
* if the start touch position is lesser than the current touch position, calculate the difference, which gives the `pullLength`
|
|
554
|
-
*
|
|
555
|
-
* This tells us how much the user has pulled
|
|
556
|
-
*/
|
|
557
|
-
let pullLength = ui.startPoint < screenY ? Math.abs(screenY - ui.startPoint) : 0;
|
|
558
|
-
ui.setPullChange(pullLength);
|
|
559
|
-
};
|
|
560
|
-
const endPull = (e) => {
|
|
561
|
-
if (ui.pullChange > 250) {
|
|
562
|
-
e.preventDefault();
|
|
563
|
-
}
|
|
564
|
-
if (ui.pullChange > 200) initLoading();
|
|
565
|
-
ui.setStartPoint(0);
|
|
566
|
-
ui.setPullChange(0);
|
|
567
|
-
};
|
|
568
|
-
window.addEventListener("touchstart", pullStart);
|
|
569
|
-
window.addEventListener("touchmove", pull);
|
|
570
|
-
window.addEventListener("touchend", endPull);
|
|
571
|
-
|
|
572
|
-
function maxPull() {
|
|
573
|
-
if (ui.pullChange < 250) {
|
|
574
|
-
return ui.pullChange;
|
|
575
|
-
} else {
|
|
576
|
-
ui.setPullChange(250);
|
|
577
|
-
return 250;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
516
|
+
// Pull-to-refresh removed per user request
|
|
580
517
|
// Vue router integration - aggressive triggering on route changes
|
|
581
518
|
watch(
|
|
582
519
|
() => router.currentRoute.value.name,
|
|
@@ -610,45 +547,78 @@ watch(
|
|
|
610
547
|
const layout = computed(() => router.currentRoute.value.meta.layout);
|
|
611
548
|
</script>
|
|
612
549
|
<style lang="scss">
|
|
613
|
-
|
|
550
|
+
/* iPhone Landscape Lock Overlay */
|
|
551
|
+
.iphone-landscape-lock {
|
|
552
|
+
@apply fixed inset-0 bg-dark-300 z-max hidden items-center justify-center;
|
|
553
|
+
|
|
554
|
+
@media (max-device-width: 430px) and (orientation: landscape) {
|
|
555
|
+
display: flex;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.rotate-message {
|
|
560
|
+
@apply text-center p-8;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.rotate-icon {
|
|
564
|
+
@apply w-16 h-16 mx-auto mb-4 text-accent-green;
|
|
565
|
+
animation: rotate-pulse 2s ease-in-out infinite;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.rotate-text {
|
|
569
|
+
@apply text-white text-lg font-medium;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
@keyframes rotate-pulse {
|
|
573
|
+
0%, 100% {
|
|
574
|
+
transform: rotate(0deg) scale(1);
|
|
575
|
+
opacity: 1;
|
|
576
|
+
}
|
|
577
|
+
50% {
|
|
578
|
+
transform: rotate(90deg) scale(1.1);
|
|
579
|
+
opacity: 0.8;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/* Touch scrolling overrides for mobile */
|
|
614
584
|
.table-component,
|
|
615
585
|
.stop-pan,
|
|
616
|
-
.scrollable,
|
|
617
586
|
.overflow-y-auto,
|
|
618
587
|
.vue-recycle-scroller,
|
|
619
588
|
.console,
|
|
620
|
-
[class*="scroll"] {
|
|
589
|
+
[class*="scroll"]:not(.dropdown-menu-portal) {
|
|
621
590
|
touch-action: pan-y !important;
|
|
622
591
|
}
|
|
623
592
|
|
|
593
|
+
/* Dropdown menus need both x/y panning */
|
|
594
|
+
.dropdown-menu-portal {
|
|
595
|
+
touch-action: pan-x pan-y !important;
|
|
596
|
+
}
|
|
597
|
+
|
|
624
598
|
.table-component {
|
|
625
599
|
touch-action: pan-x pan-y !important;
|
|
626
600
|
}
|
|
627
601
|
|
|
602
|
+
/* Dropdown positioning */
|
|
628
603
|
.dropdown {
|
|
629
604
|
position: relative;
|
|
630
605
|
display: inline-block;
|
|
631
606
|
}
|
|
632
607
|
|
|
633
608
|
.dropdown-content {
|
|
634
|
-
@apply bg-dark-500 text-white shadow rounded-lg top-10 left-0;
|
|
609
|
+
@apply bg-dark-500 text-white shadow rounded-lg top-10 left-0 px-4 py-3;
|
|
635
610
|
position: absolute;
|
|
636
611
|
min-width: 160px;
|
|
637
|
-
padding: 12px 16px;
|
|
638
612
|
z-index: 1;
|
|
639
613
|
}
|
|
640
614
|
|
|
615
|
+
/* Router wrapper */
|
|
641
616
|
.router-wrapper {
|
|
642
|
-
@apply pt-
|
|
643
|
-
transition: margin 0.25s;
|
|
617
|
+
@apply pt-16 lg:pt-20;
|
|
644
618
|
z-index: 0;
|
|
645
619
|
}
|
|
646
620
|
|
|
647
|
-
|
|
648
|
-
transition: margin 0.25s;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
// Page navigation transitions
|
|
621
|
+
/* Page transition animations */
|
|
652
622
|
.page-transition-enter-active {
|
|
653
623
|
transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
654
624
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
MIXINS - Reusable SCSS patterns
|
|
3
|
+
========================================================================== */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Scale hover effect - subtle zoom on hover/active
|
|
7
|
+
* Used across buttons and interactive elements
|
|
8
|
+
*/
|
|
9
|
+
@mixin scale-hover {
|
|
10
|
+
&:hover {
|
|
11
|
+
transform: scale(1.03);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
&:active {
|
|
15
|
+
transform: scale(0.98);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Scale hover for elements with disabled state
|
|
21
|
+
* Only applies transform when not disabled
|
|
22
|
+
*/
|
|
23
|
+
@mixin scale-hover-safe {
|
|
24
|
+
&:hover:not(:disabled) {
|
|
25
|
+
transform: scale(1.03);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&:active:not(:disabled) {
|
|
29
|
+
transform: scale(0.98);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Focus ring with accent green color
|
|
35
|
+
* Replaces repeated outline pattern across components
|
|
36
|
+
*/
|
|
37
|
+
@mixin focus-ring-accent {
|
|
38
|
+
@apply outline outline-1 outline-accent-green outline-offset-0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* User select control with vendor prefixes
|
|
43
|
+
* @param {string} $value - none, text, auto, all
|
|
44
|
+
*/
|
|
45
|
+
@mixin user-select($value) {
|
|
46
|
+
-webkit-user-select: $value;
|
|
47
|
+
-moz-user-select: $value;
|
|
48
|
+
-ms-user-select: $value;
|
|
49
|
+
user-select: $value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Touch scroll reset for dropdowns and scrollable areas
|
|
54
|
+
* Enables smooth touch scrolling on mobile devices
|
|
55
|
+
*/
|
|
56
|
+
@mixin touch-scroll-reset {
|
|
57
|
+
overscroll-behavior: auto !important;
|
|
58
|
+
touch-action: pan-y !important;
|
|
59
|
+
-webkit-overflow-scrolling: touch !important;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Dark button base styling
|
|
64
|
+
* Common pattern for action buttons and modal controls
|
|
65
|
+
*/
|
|
66
|
+
@mixin dark-button-base {
|
|
67
|
+
@apply bg-dark-400 border border-dark-650 text-white;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@mixin transition-standard {
|
|
71
|
+
@apply transition-all duration-150;
|
|
72
|
+
}
|
|
@@ -17,7 +17,6 @@ button {
|
|
|
17
17
|
-webkit-tap-highlight-color: transparent;
|
|
18
18
|
-webkit-touch-callout: none;
|
|
19
19
|
outline: none;
|
|
20
|
-
/* Prevent zoom on input focus (mobile) */
|
|
21
20
|
font-size: 16px;
|
|
22
21
|
}
|
|
23
22
|
|
|
@@ -37,7 +36,6 @@ select:focus {
|
|
|
37
36
|
box-shadow: none !important;
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
/* Remove number input spinners */
|
|
41
39
|
input[type="number"] {
|
|
42
40
|
-moz-appearance: textfield;
|
|
43
41
|
}
|