@necrolab/dashboard 0.5.24 → 0.5.26

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/index.html CHANGED
@@ -14,6 +14,20 @@
14
14
  <meta name="description" content="Necro Lab - dashboard" />
15
15
  <meta name="darkreader-lock" />
16
16
  <meta name="color-scheme" content="dark" />
17
+ <meta name="supported-color-schemes" content="dark" />
18
+ <!-- Prevent Noir and other dark mode extensions from modifying the site -->
19
+ <meta name="noir" content="disabled" />
20
+ <meta name="nighteye" content="disabled" />
21
+ <style>
22
+ /* Lock color scheme to prevent iOS extensions from overriding */
23
+ :root {
24
+ color-scheme: dark only;
25
+ -webkit-color-scheme: dark;
26
+ }
27
+ html, body {
28
+ color-scheme: dark only !important;
29
+ }
30
+ </style>
17
31
  <title>Necro Lab - Dashboard</title>
18
32
  <!-- DNS prefetch for external resources -->
19
33
  <link rel="dns-prefetch" href="https://fonts.googleapis.com" />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@necrolab/dashboard",
3
- "version": "0.5.24",
3
+ "version": "0.5.26",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "rm -rf dist && vite build && npx workbox-cli generateSW workbox-config.cjs",
package/src/App.vue CHANGED
@@ -18,8 +18,8 @@
18
18
  <ReconnectIndicator :message="ui.spinnerMessage" />
19
19
  </div>
20
20
  <div v-else key="main-components" class="flex">
21
- <Navbar v-if="layout == 'dashboard'" class="fixed" />
22
- <div class="router-wrapper w-full">
21
+ <Navbar v-if="layout == 'dashboard'" ref="navbarRef" class="fixed" />
22
+ <div class="router-wrapper w-full" :style="routerWrapperStyle">
23
23
  <router-view v-slot="{ Component, route }">
24
24
  <transition name="page-transition" mode="out-in">
25
25
  <component
@@ -36,7 +36,7 @@
36
36
 
37
37
  <script setup>
38
38
  import { storeToRefs } from "pinia";
39
- import { ref, computed } from "vue";
39
+ import { ref, computed, nextTick, onMounted, onUnmounted, watch } 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";
@@ -50,6 +50,8 @@ const ui = useUIStore();
50
50
  const { showSpinner: spinner } = storeToRefs(ui);
51
51
  const router = useRouter();
52
52
  const isLoading = ref(false);
53
+ const navbarRef = ref(null);
54
+ let navbarResizeObserver = null;
53
55
 
54
56
  // Initialize zoom prevention (all browsers)
55
57
  const { KEY_CODES } = useZoomPrevention();
@@ -78,6 +80,71 @@ document.onkeydown = function (evt) {
78
80
  if (!window.location.href.includes(":5173")) ui.startSpinner("Loading...");
79
81
 
80
82
  const layout = computed(() => router.currentRoute.value.meta.layout);
83
+ const routerWrapperStyle = computed(() => ({
84
+ paddingTop: layout.value === "dashboard" ? "var(--dashboard-navbar-height, 80px)" : "0px",
85
+ paddingLeft: "var(--safe-area-left, 0px)",
86
+ paddingRight: "var(--safe-area-right, 0px)",
87
+ paddingBottom: "var(--safe-area-bottom, 0px)"
88
+ }));
89
+
90
+ const updateNavbarHeight = () => {
91
+ if (layout.value !== "dashboard") {
92
+ document.documentElement.style.setProperty("--dashboard-navbar-height", "0px");
93
+ return;
94
+ }
95
+
96
+ const navbarElement = navbarRef.value?.$el;
97
+ if (!navbarElement) return;
98
+
99
+ const height = Math.ceil(navbarElement.getBoundingClientRect().height);
100
+ if (height > 0) {
101
+ document.documentElement.style.setProperty("--dashboard-navbar-height", `${height}px`);
102
+ }
103
+ };
104
+
105
+ const scheduleNavbarHeightUpdate = () => {
106
+ requestAnimationFrame(updateNavbarHeight);
107
+ };
108
+
109
+ const syncNavbarObserver = () => {
110
+ if (!navbarResizeObserver) return;
111
+
112
+ navbarResizeObserver.disconnect();
113
+ const navbarElement = navbarRef.value?.$el;
114
+ if (layout.value === "dashboard" && navbarElement) {
115
+ navbarResizeObserver.observe(navbarElement);
116
+ }
117
+ };
118
+
119
+ watch(
120
+ [layout, () => navbarRef.value?.$el],
121
+ () => {
122
+ nextTick(() => {
123
+ syncNavbarObserver();
124
+ scheduleNavbarHeightUpdate();
125
+ });
126
+ },
127
+ { immediate: true }
128
+ );
129
+
130
+ onMounted(() => {
131
+ scheduleNavbarHeightUpdate();
132
+ window.addEventListener("resize", scheduleNavbarHeightUpdate, { passive: true });
133
+ window.visualViewport?.addEventListener("resize", scheduleNavbarHeightUpdate);
134
+
135
+ if (typeof ResizeObserver !== "undefined") {
136
+ navbarResizeObserver = new ResizeObserver(scheduleNavbarHeightUpdate);
137
+ syncNavbarObserver();
138
+ }
139
+ });
140
+
141
+ onUnmounted(() => {
142
+ window.removeEventListener("resize", scheduleNavbarHeightUpdate);
143
+ window.visualViewport?.removeEventListener("resize", scheduleNavbarHeightUpdate);
144
+ navbarResizeObserver?.disconnect();
145
+ navbarResizeObserver = null;
146
+ document.documentElement.style.setProperty("--dashboard-navbar-height", "0px");
147
+ });
81
148
  </script>
82
149
 
83
150
  <style lang="scss">
@@ -148,7 +215,6 @@ const layout = computed(() => router.currentRoute.value.meta.layout);
148
215
 
149
216
  /* Router wrapper */
150
217
  .router-wrapper {
151
- @apply pt-16 lg:pt-20;
152
218
  z-index: 0;
153
219
  }
154
220
 
@@ -5,14 +5,21 @@
5
5
 
6
6
  .page-header {
7
7
  @apply flex items-center justify-between;
8
- padding-top: 1.5rem;
8
+ padding-top: 0.75rem;
9
9
  padding-bottom: 0.5rem;
10
+ flex-wrap: nowrap;
11
+ gap: 0.5rem;
10
12
 
11
13
  .page-header-card {
12
14
  @apply flex items-center gap-2.5 rounded-lg;
13
15
  padding: 0.375rem 0.75rem;
14
16
  background: linear-gradient(135deg, var(--color-gradient-start) 0%, var(--color-gradient-end) 100%);
15
17
  border: 1px solid var(--color-border-light);
18
+ flex: 0 1 auto;
19
+ min-width: 0;
20
+ max-width: 60%;
21
+ white-space: nowrap;
22
+ overflow: hidden;
16
23
 
17
24
  svg, img {
18
25
  width: 17px;
@@ -74,8 +81,6 @@
74
81
  }
75
82
 
76
83
  @screen sm {
77
- padding-top: 1rem;
78
-
79
84
  .page-header-card {
80
85
  gap: 0.625rem;
81
86
  padding: 0.5rem 0.875rem;
@@ -111,9 +116,3 @@
111
116
  }
112
117
  }
113
118
  }
114
-
115
- @media (display-mode: standalone) {
116
- .page-header {
117
- padding-top: 3.5rem !important;
118
- }
119
- }
@@ -11,6 +11,7 @@
11
11
  background: var(--color-bg-input);
12
12
  transition: all 0.15s ease;
13
13
  display: flex;
14
+ position: relative;
14
15
 
15
16
  .tag-toggle,
16
17
  .dropdown,
@@ -33,18 +34,65 @@
33
34
  .tag-toggle {
34
35
  padding: 0;
35
36
  border: 0 !important;
37
+ flex-shrink: 0 !important;
38
+ min-width: max-content !important;
36
39
  }
37
40
 
38
41
  .tag-toggle button {
39
42
  font-size: 0.875rem;
40
43
  padding: 0.5rem 0.75rem;
41
- min-width: fit-content;
44
+ min-width: fit-content !important;
42
45
  border: 0 !important;
46
+ white-space: nowrap;
47
+ flex-shrink: 0 !important;
48
+ }
49
+
50
+ /* iPad - prevent TagToggle from being compressed */
51
+ @media (min-width: 768px) and (max-width: 1024px) {
52
+ .tag-toggle {
53
+ flex-shrink: 0 !important;
54
+ min-width: max-content !important;
55
+ }
56
+
57
+ .tag-toggle button {
58
+ padding: 0.375rem 0.5rem;
59
+ font-size: 0.75rem;
60
+ flex-shrink: 0 !important;
61
+ }
62
+
63
+ input {
64
+ flex: 0 1 auto;
65
+ min-width: 100px !important;
66
+ }
43
67
  }
44
68
 
45
69
  .dropdown {
46
70
  width: 120px !important;
47
71
  min-width: 120px !important;
72
+ overflow: visible !important;
73
+ position: static !important;
74
+
75
+ /* iPad responsive - ensure arrow is visible */
76
+ @media (min-width: 768px) and (max-width: 1024px) {
77
+ width: 140px !important;
78
+ min-width: 140px !important;
79
+ flex-shrink: 0;
80
+ }
81
+
82
+ /* Mobile - more flexible */
83
+ @media (max-width: 767px) {
84
+ width: auto !important;
85
+ min-width: 90px !important;
86
+ max-width: 110px !important;
87
+ flex-shrink: 1;
88
+ }
89
+ }
90
+
91
+ /* Ensure dropdown menu portal isn't clipped */
92
+ .dropdown-menu-portal,
93
+ .dropdown-content-portal {
94
+ overflow: visible !important;
95
+ z-index: 9999 !important;
48
96
  }
49
97
 
50
98
  .dropdown:not(.transparent):focus-within,
@@ -78,43 +78,22 @@
78
78
  /* Console main container with responsive heights */
79
79
  .console-main {
80
80
  @apply relative overflow-x-auto overflow-y-auto rounded border-2 border-dark-550 bg-dark-400 p-2 font-mono text-white;
81
- height: calc(100vh - 20rem);
82
- max-height: calc(100vh - 20rem);
83
81
  scroll-padding: 0.5rem;
84
82
  -webkit-overflow-scrolling: touch;
85
83
  overscroll-behavior: contain;
86
84
  min-height: 12rem !important;
87
85
  touch-action: pan-y pan-up pan-down;
88
86
 
89
- @screen md {
90
- height: calc(100vh - 18rem);
91
- max-height: calc(100vh - 18rem);
92
- }
93
-
94
87
  @screen lg {
95
- height: calc(100vh - 16rem);
96
- max-height: calc(100vh - 16rem);
97
88
  padding: 1.25rem;
98
89
  }
99
90
 
100
91
  @screen mobile-portrait {
101
- height: calc(100vh - 22rem);
102
- max-height: calc(100vh - 22rem);
103
92
  overflow: auto;
104
93
  padding: 0.25rem;
105
94
  font-size: 0.75rem;
106
95
  line-height: 1rem;
107
96
  }
108
-
109
- @media (orientation: landscape) and (max-width: 1023px) {
110
- height: calc(100vh - 12rem);
111
- max-height: calc(100vh - 12rem);
112
- }
113
-
114
- @media (max-width: 767px) and (orientation: landscape) {
115
- height: auto;
116
- max-height: 65vh;
117
- }
118
97
  }
119
98
 
120
99
  /* FilterBuilder action button */
@@ -22,6 +22,13 @@
22
22
  BODY LAYOUT & BACKGROUND
23
23
  ========================================================================== */
24
24
 
25
+ :root {
26
+ --safe-area-top: 0px;
27
+ --safe-area-right: 0px;
28
+ --safe-area-bottom: 0px;
29
+ --safe-area-left: 0px;
30
+ }
31
+
25
32
  html {
26
33
  @apply bg-dark-300;
27
34
  min-height: 100vh;
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div>
3
- <div class="mb-3 flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
3
+ <div class="mb-2 flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
4
4
  <div class="flex flex-col gap-3 md:flex-1 md:flex-row md:items-center">
5
5
  <div class="w-full md:w-64">
6
6
  <Dropdown
@@ -79,7 +79,7 @@
79
79
  </button>
80
80
  </div>
81
81
  </div>
82
- <div class="mb-6 mt-4 flex justify-between md:hidden mobile-portrait:mb-16 mobile-portrait:mt-6">
82
+ <div class="mb-2 mt-2 flex justify-between gap-2 md:hidden">
83
83
  <button class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
84
84
  <h3 class="text-sm text-white">Hide Monitors</h3>
85
85
  <Switch class="scale-75" :model-value="filteredLogs" @update:model-value="$emit('update:filteredLogs', $event)" />
@@ -2,18 +2,19 @@
2
2
  <div class="admin-editor-section" :class="{ 'landscape-hidden': isHidden }" v-if="!isHidden">
3
3
  <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Admin Editor</h5>
4
4
  <div class="flex items-center gap-2 w-full">
5
- <div class="unified-search-group flex flex-1 dropdown-container z-dropdown-high lg:flex-initial lg:min-w-64">
6
- <Dropdown
7
- class="admin-file-dropdown flex-1"
8
- default="Select a file"
9
- :onClick="loadFile"
10
- :options="availableFiles"
11
- :allowDefault="false"
12
- :includeAdjacentButtons="true"
13
- rightAmount="right-1" />
5
+ <div class="admin-file-selector flex-1 lg:flex-initial lg:w-64 dropdown-container z-dropdown-high">
6
+ <div class="relative min-w-0 flex-grow">
7
+ <Dropdown
8
+ class="admin-file-dropdown w-full"
9
+ default="Select a file"
10
+ :onClick="loadFile"
11
+ :options="availableFiles"
12
+ :allowDefault="false"
13
+ :includeAdjacentButtons="true"
14
+ rightAmount="right-1" />
15
+ </div>
14
16
  <button
15
- class="refresh-button flex items-center justify-center w-9 h-10 flex-shrink-0 text-white bg-dark-400 hover:bg-dark-450"
16
- style="transition: all 0.2s ease"
17
+ class="refresh-button"
17
18
  @click="$emit('refresh-files')"
18
19
  title="Refresh file list">
19
20
  <ReloadIcon class="refresh-icon icon-md" />
@@ -180,6 +181,40 @@ onMounted(() => {
180
181
  </script>
181
182
 
182
183
  <style scoped>
184
+ .admin-file-selector {
185
+ @apply flex items-center overflow-hidden rounded-lg border-2 border-dark-550 bg-dark-500;
186
+ min-height: 40px;
187
+ transition: border-color 0.15s ease;
188
+ }
189
+
190
+ .admin-file-selector:hover {
191
+ @apply border-dark-650;
192
+ }
193
+
194
+ .admin-file-selector:focus-within {
195
+ border-color: var(--color-primary);
196
+ outline: 1px solid var(--color-primary);
197
+ outline-offset: 0;
198
+ }
199
+
200
+ :deep(.admin-file-dropdown.dropdown) {
201
+ @apply h-10 border-0 rounded-none bg-transparent shadow-none;
202
+ padding: 0 0.75rem !important;
203
+ }
204
+
205
+ :deep(.admin-file-dropdown .dropdown-display) {
206
+ padding: 0;
207
+ }
208
+
209
+ .refresh-button {
210
+ @apply flex h-10 w-10 flex-shrink-0 items-center justify-center border-l border-dark-550 bg-dark-400 text-white;
211
+ transition: all 0.2s ease;
212
+ }
213
+
214
+ .refresh-button:hover {
215
+ @apply border-dark-625 bg-dark-450;
216
+ }
217
+
183
218
  .editor-action-btn {
184
219
  @apply flex items-center justify-center w-9 h-9 rounded border-2 border-dark-550 bg-dark-400 text-white;
185
220
  transition: all 0.2s ease;
@@ -1,8 +1,9 @@
1
1
  <template>
2
2
  <button
3
3
  @click="increase"
4
- class="h-10 px-1 text-white bg-dark-500 relative overflow-hidden"
4
+ class="h-10 px-3 text-white bg-dark-500 relative overflow-hidden"
5
5
  :class="noBorder ? 'border-0' : 'border-2 border-dark-550'"
6
+ style="min-width: max-content;"
6
7
  >
7
8
  <span v-if="sortOptions[currentOpt % sortOptions.length] === 'Enabled'"
8
9
  ><img class="mx-auto" height="16px" width="14px" src="@/assets/img/square_check.svg"
@@ -300,7 +300,7 @@ props.filterBuilder.onUpdate(() => {
300
300
 
301
301
  <style scoped>
302
302
  .filter-card {
303
- @apply bg-dark-500 border border-dark-625/30 relative mb-2 transition-colors duration-200;
303
+ @apply bg-dark-500 border border-dark-625/30 relative transition-colors duration-200;
304
304
  }
305
305
 
306
306
  .filter-card:hover:not(.expanded-filter) {
@@ -1,18 +1,34 @@
1
1
  <template>
2
2
  <div class="navbar" :class="{ 'force-z': menuOpen }">
3
- <div :class="['component-container ios-wrapper flex items-center relative px-3 lg:px-4', { 'ios-wrapper': landscapeIos }]">
3
+ <div
4
+ :class="[
5
+ 'component-container ios-wrapper relative flex items-center px-3 lg:px-4',
6
+ { 'ios-wrapper': landscapeIos }
7
+ ]">
4
8
  <router-link to="/">
5
- <img src="@/assets/img/logo_trans.png" class="h-6 lg:h-8 mr-4 z-30 object-cover cursor-pointer" alt="Logo: Necro" />
9
+ <img
10
+ src="@/assets/img/logo_trans.png"
11
+ class="z-30 mr-4 h-6 cursor-pointer object-cover lg:h-8"
12
+ alt="Logo: Necro" />
6
13
  </router-link>
7
14
  <ul class="hidden lg:flex">
8
15
  <li>
9
- <router-link to="/"><TasksIcon /><span class="hidden xl:block">Tasks</span></router-link>
16
+ <router-link to="/">
17
+ <TasksIcon />
18
+ <span class="hidden xl:block">Tasks</span>
19
+ </router-link>
10
20
  </li>
11
21
  <li>
12
- <router-link to="/editor"><EditIcon /><span class="hidden xl:block">Editor</span></router-link>
22
+ <router-link to="/editor">
23
+ <EditIcon />
24
+ <span class="hidden xl:block">Editor</span>
25
+ </router-link>
13
26
  </li>
14
27
  <li v-if="ui.profile?.admin">
15
- <router-link to="/console"><ConsoleIcon /><span class="hidden xl:block">Console</span></router-link>
28
+ <router-link to="/console">
29
+ <ConsoleIcon />
30
+ <span class="hidden xl:block">Console</span>
31
+ </router-link>
16
32
  </li>
17
33
  <li>
18
34
  <router-link to="/profiles">
@@ -27,45 +43,60 @@
27
43
  </router-link>
28
44
  </li>
29
45
  <li>
30
- <router-link to="/filter"><FilterIcon /><span class="hidden xl:block">Filter</span></router-link>
46
+ <router-link to="/filter">
47
+ <FilterIcon />
48
+ <span class="hidden xl:block">Filter</span>
49
+ </router-link>
31
50
  </li>
32
51
  </ul>
33
52
 
34
- <button class="hidden lg:block ml-auto mr-4 smooth-hover" @click="logout()" aria-label="Logout">
53
+ <button class="smooth-hover ml-auto mr-4 hidden lg:block" @click="logout()" aria-label="Logout">
35
54
  <LogoutIcon />
36
55
  </button>
37
- <h4 v-if="ui.profile?.name" class="hidden lg:block text-white text-sm font-medium">
38
- <span class="text-lightgray">Logged in as </span>
39
- <span class="font-black"> {{ ui.profile?.name }}</span>
56
+ <h4 v-if="ui.profile?.name" class="hidden text-sm font-medium text-white lg:block">
57
+ <span class="text-lightgray">Logged in as</span>
58
+ <span class="ml-1 font-black">{{ ui.profile?.name }}</span>
40
59
  </h4>
41
- <h4 v-else class="hidden lg:block text-white text-sm font-medium">
42
- <span class="text-lightgray">Loading </span>
60
+ <h4 v-else class="hidden text-sm font-medium text-white lg:block">
61
+ <span class="text-lightgray">Loading</span>
43
62
  </h4>
44
63
  <img
45
64
  v-if="ui.profile?.profilePicture"
46
65
  :src="ui.profile?.profilePicture"
47
66
  alt="Profile Picture"
48
- class="size-10 rounded-full hidden lg:block mx-4"
49
- />
50
- <div v-else class="size-10 rounded-full hidden lg:block mx-4 bg-dark-400" />
51
- <CountryChooser class="hidden lg:block" />
67
+ class="ipad-safe-top mx-4 hidden size-10 rounded-full lg:block" />
68
+ <div v-else class="ipad-safe-top mx-4 hidden size-10 rounded-full bg-dark-400 lg:block" />
69
+ <CountryChooser class="ipad-safe-top hidden lg:block" />
52
70
 
53
- <button class="flex lg:hidden ml-auto z-30" @click="toggleMenu" aria-label="Toggle navigation menu" :aria-expanded="menuOpen">
71
+ <button
72
+ class="z-30 -m-2 ml-auto flex p-2 lg:hidden"
73
+ @click="toggleMenu"
74
+ aria-label="Toggle navigation menu"
75
+ :aria-expanded="menuOpen">
54
76
  <MenuIcon />
55
77
  </button>
56
78
  </div>
57
79
  <transition name="fade">
58
- <div class="mobile-menu flex lg:hidden flex-col z-30" v-if="menuOpen">
59
- <CountryChooser class="mx-auto landscape:block hidden mt-4" />
60
- <ul class="flex landscape:grid grid-cols-3 mx-auto mt-12 flex-col">
80
+ <div class="mobile-menu z-30 flex flex-col lg:hidden" v-if="menuOpen">
81
+ <CountryChooser class="mx-auto mt-4 hidden landscape:block" />
82
+ <ul class="mx-auto mt-12 flex grid-cols-3 flex-col landscape:grid">
61
83
  <li @click="toggleMenu">
62
- <router-link to="/"><TasksIcon />Tasks</router-link>
84
+ <router-link to="/">
85
+ <TasksIcon />
86
+ Tasks
87
+ </router-link>
63
88
  </li>
64
89
  <li @click="toggleMenu">
65
- <router-link to="/editor"><EditIcon />Editor</router-link>
90
+ <router-link to="/editor">
91
+ <EditIcon />
92
+ Editor
93
+ </router-link>
66
94
  </li>
67
95
  <li v-if="ui.profile?.admin" @click="toggleMenu">
68
- <router-link to="/console"><ConsoleIcon />Console</router-link>
96
+ <router-link to="/console">
97
+ <ConsoleIcon />
98
+ Console
99
+ </router-link>
69
100
  </li>
70
101
  <li @click="toggleMenu">
71
102
  <router-link to="/profiles">
@@ -80,22 +111,25 @@
80
111
  </router-link>
81
112
  </li>
82
113
  <li @click="toggleMenu">
83
- <router-link to="/filter"><FilterIcon />Filter</router-link>
114
+ <router-link to="/filter">
115
+ <FilterIcon />
116
+ Filter
117
+ </router-link>
84
118
  </li>
85
119
  </ul>
86
- <CountryChooser class="mx-auto block landscape:hidden mb-auto" />
87
- <div class="flex mx-auto items-center landscape:mb-0">
120
+ <CountryChooser class="mx-auto mb-auto block landscape:hidden" />
121
+ <div class="mx-auto flex items-center landscape:mb-0">
88
122
  <button class="mr-4" @click="logout()" aria-label="Logout">
89
123
  <LogoutIcon />
90
124
  </button>
91
- <h4 class="text-white text-sm font-medium mr-4">
92
- <span class="text-lightgray">Logged in as </span>
93
- <span class="font-black"> {{ ui.profile?.name }}</span>
125
+ <h4 class="mr-4 text-sm font-medium text-white">
126
+ <span class="text-lightgray">Logged in as</span>
127
+ <span class="ml-1 font-black">{{ ui.profile?.name }}</span>
94
128
  </h4>
95
129
  <img :src="ui.profile?.profilePicture" alt="" class="size-10 rounded-full" />
96
130
  </div>
97
131
 
98
- <div class="mx-auto mt-6 mb-14 flex gap-3 items-center">
132
+ <div class="mx-auto mb-14 mt-6 flex items-center gap-3">
99
133
  <div class="version-badge">
100
134
  <span class="version-label">Dashboard</span>
101
135
  <span class="version-number">v{{ dashVersion }}</span>
@@ -111,25 +145,27 @@
111
145
  </template>
112
146
  <style lang="scss" scoped>
113
147
  .navbar {
114
- @apply border-b py-5 fixed w-full;
148
+ @apply fixed w-full border-b py-5;
115
149
  top: 0;
116
150
  left: 0;
117
151
  z-index: 1000;
118
- background: theme('colors.dark.300 / 0.95');
152
+ background: theme("colors.dark.300 / 0.95");
119
153
  backdrop-filter: blur(23px);
120
154
  -webkit-backdrop-filter: blur(23px);
121
- border-color: theme('colors.border');
155
+ border-color: theme("colors.border");
122
156
 
123
157
  // Consistent padding base
124
- padding-top: 1.25rem;
158
+ padding-top: calc(1.1rem + var(--safe-area-top, 0px));
125
159
  padding-bottom: 1.25rem;
160
+ padding-left: var(--safe-area-left, 0px);
161
+ padding-right: var(--safe-area-right, 0px);
126
162
 
127
163
  // Only add safe area top padding for portrait notch devices
128
- @supports (padding-top: env(safe-area-inset-top)) {
129
- @media (max-device-width: 430px) and (orientation: portrait) {
130
- padding-top: max(1.25rem, env(safe-area-inset-top));
131
- }
132
- }
164
+ // @supports (padding-top: env(safe-area-inset-top)) {
165
+ // @media (max-device-width: 430px) and (orientation: portrait) {
166
+ // padding-top: max(1.25rem, env(safe-area-inset-top));
167
+ // }
168
+ // }
133
169
 
134
170
  // Extend background above viewport for notch area with theme gradient
135
171
  &::before {
@@ -139,7 +175,7 @@
139
175
  left: 0;
140
176
  right: 0;
141
177
  height: 100px;
142
- background: theme('colors.dark.300');
178
+ background: theme("colors.dark.300");
143
179
  backdrop-filter: blur(8px);
144
180
  -webkit-backdrop-filter: blur(8px);
145
181
  z-index: -1;
@@ -158,7 +194,7 @@
158
194
  }
159
195
 
160
196
  li a {
161
- @apply flex text-white text-sm items-center rounded-lg;
197
+ @apply flex items-center rounded-lg text-sm text-white;
162
198
  height: 40px;
163
199
  border: 1px solid transparent;
164
200
  border-left: 3px solid transparent;
@@ -170,16 +206,16 @@
170
206
  }
171
207
 
172
208
  &.router-link-exact-active {
173
- border-bottom: 2px solid theme('colors.primary');
174
- color: theme('colors.primary');
175
- background: theme('colors.primary / 0.08');
209
+ border-bottom: 2px solid theme("colors.primary");
210
+ color: theme("colors.primary");
211
+ background: theme("colors.primary / 0.08");
176
212
  margin-bottom: -2px;
177
213
 
178
214
  // Mobile styling
179
215
  @media (max-width: 1023px) {
180
- border: 2px solid theme('colors.primary');
181
- color: theme('colors.primary');
182
- background: theme('colors.primary / 0.08');
216
+ border: 2px solid theme("colors.primary");
217
+ color: theme("colors.primary");
218
+ background: theme("colors.primary / 0.08");
183
219
  padding: 0.5rem 0.75rem;
184
220
  border-radius: 0.5rem;
185
221
  margin-bottom: 0;
@@ -213,9 +249,9 @@
213
249
  }
214
250
 
215
251
  &.router-link-exact-active {
216
- border: 2px solid theme('colors.primary') !important;
217
- color: theme('colors.primary') !important;
218
- background: theme('colors.primary / 0.08') !important;
252
+ border: 2px solid theme("colors.primary") !important;
253
+ color: theme("colors.primary") !important;
254
+ background: theme("colors.primary / 0.08") !important;
219
255
  border-radius: 0.5rem !important;
220
256
  width: 40px !important;
221
257
  height: 40px !important;
@@ -237,6 +273,13 @@
237
273
  }
238
274
  }
239
275
 
276
+ /* iPad - push right-side elements down from status bar */
277
+ @media (min-width: 768px) and (max-width: 1024px) {
278
+ .ipad-safe-top {
279
+ margin-top: 3px;
280
+ }
281
+ }
282
+
240
283
  .force-z {
241
284
  z-index: 20000;
242
285
  }
@@ -246,10 +289,14 @@
246
289
  height: 100vh;
247
290
  height: 100dvh; // Dynamic viewport height for mobile
248
291
  will-change: transform, opacity, filter;
249
- @apply bg-dark-400 fixed inset-0 pt-20 pb-8 w-full z-10;
292
+ @apply fixed inset-0 z-10 w-full bg-dark-400 pb-8 pt-20;
293
+ padding-top: calc(5rem + var(--safe-area-top, 0px));
294
+ padding-bottom: calc(2rem + var(--safe-area-bottom, 0px));
295
+ padding-left: var(--safe-area-left, 0px);
296
+ padding-right: var(--safe-area-right, 0px);
250
297
 
251
298
  ul li {
252
- @apply mb-4 mx-auto;
299
+ @apply mx-auto mb-4;
253
300
 
254
301
  a {
255
302
  font-size: 20px !important;
@@ -258,18 +305,18 @@
258
305
  }
259
306
 
260
307
  .version-badge {
261
- @apply flex items-center gap-x-2 px-3 py-2 rounded-lg border;
262
- background: theme('colors.bg-elevated');
263
- border-color: theme('colors.dark.550');
308
+ @apply flex items-center gap-x-2 rounded-lg border px-3 py-2;
309
+ background: theme("colors.bg-elevated");
310
+ border-color: theme("colors.dark.550");
264
311
 
265
312
  .version-label {
266
313
  @apply text-xs font-medium;
267
- color: theme('colors.text-muted');
314
+ color: theme("colors.text-muted");
268
315
  }
269
316
 
270
317
  .version-number {
271
318
  @apply text-xs font-bold;
272
- color: theme('colors.primary');
319
+ color: theme("colors.primary");
273
320
  }
274
321
  }
275
322
  </style>
@@ -1,7 +1,7 @@
1
- import { ref, watch, onMounted } from 'vue';
2
- import { useRouter } from 'vue-router';
3
- import { useDeviceDetection } from './useDeviceDetection';
4
- import { DEBUG } from '@/utils/debug';
1
+ import { ref, watch, onMounted } from "vue";
2
+ import { useRouter } from "vue-router";
3
+ import { useDeviceDetection } from "./useDeviceDetection";
4
+ import { DEBUG } from "@/utils/debug";
5
5
 
6
6
  export function useNotchHandling(logger) {
7
7
  const { isIOS, isIpadOS } = useDeviceDetection();
@@ -44,11 +44,19 @@ export function useNotchHandling(logger) {
44
44
  if (isNotchBusy && !force) return;
45
45
 
46
46
  try {
47
- if (!isIOS() || isIpadOS()) return;
47
+ if (!isIOS()) {
48
+ document.documentElement.style.setProperty("--safe-area-top", "0px");
49
+ document.documentElement.style.setProperty("--safe-area-right", "0px");
50
+ document.documentElement.style.setProperty("--safe-area-bottom", "0px");
51
+ document.documentElement.style.setProperty("--safe-area-left", "0px");
52
+ return;
53
+ }
48
54
 
49
- const wrappers = document.querySelectorAll(".ios-wrapper");
50
- if (wrappers.length === 0) {
51
- setTimeout(() => handleNotch(force), 100);
55
+ if (isIpadOS()) {
56
+ document.documentElement.style.setProperty("--safe-area-top", "12px");
57
+ document.documentElement.style.setProperty("--safe-area-right", "0px");
58
+ document.documentElement.style.setProperty("--safe-area-bottom", "0px");
59
+ document.documentElement.style.setProperty("--safe-area-left", "0px");
52
60
  return;
53
61
  }
54
62
 
@@ -78,56 +86,22 @@ export function useNotchHandling(logger) {
78
86
  });
79
87
  }
80
88
 
81
- if (hasNotch && isLandscape) {
82
- const testDiv = document.createElement("div");
83
- testDiv.style.position = "fixed";
84
- testDiv.style.top = "0";
85
- testDiv.style.left = "0";
86
- testDiv.style.visibility = "hidden";
87
- testDiv.style.paddingLeft = "env(safe-area-inset-left)";
88
- testDiv.style.paddingRight = "env(safe-area-inset-right)";
89
- document.body.appendChild(testDiv);
90
-
91
- const computedStyle = getComputedStyle(testDiv);
92
- const leftInset = parseFloat(computedStyle.paddingLeft) || 0;
93
- const rightInset = parseFloat(computedStyle.paddingRight) || 0;
94
-
95
- document.body.removeChild(testDiv);
96
-
97
- if (DEBUG && logger) logger.Debug("🔍 Safe area insets:", { leftInset, rightInset });
98
-
99
- wrappers.forEach((wrapper) => {
100
- if (leftInset > 0) {
101
- wrapper.style.paddingLeft = "env(safe-area-inset-left)";
102
- wrapper.style.paddingRight = "0.5rem";
103
- } else if (rightInset > 0) {
104
- wrapper.style.paddingLeft = "0.5rem";
105
- wrapper.style.paddingRight = "env(safe-area-inset-right)";
106
- } else {
107
- wrapper.style.paddingLeft = "0.5rem";
108
- wrapper.style.paddingRight = "0.5rem";
109
- }
110
- });
89
+ if (hasNotch) {
90
+ document.documentElement.style.setProperty("--safe-area-top", "min(env(safe-area-inset-top), 32px)");
91
+ document.documentElement.style.setProperty("--safe-area-right", "env(safe-area-inset-right)");
92
+ document.documentElement.style.setProperty("--safe-area-bottom", "env(safe-area-inset-bottom)");
93
+ document.documentElement.style.setProperty("--safe-area-left", "env(safe-area-inset-left)");
111
94
  } else {
112
- const RESPONSIVE_PADDING = {
113
- XL: { minWidth: 1280, padding: "2.5rem" },
114
- LG: { minWidth: 1030, padding: "1.5rem" },
115
- MD: { minWidth: 768, padding: "0.5rem" },
116
- DEFAULT: { padding: "0.5rem" }
117
- };
118
-
119
- const padding =
120
- window.innerWidth > RESPONSIVE_PADDING.XL.minWidth
121
- ? RESPONSIVE_PADDING.XL.padding
122
- : window.innerWidth > RESPONSIVE_PADDING.LG.minWidth
123
- ? RESPONSIVE_PADDING.LG.padding
124
- : window.innerWidth > RESPONSIVE_PADDING.MD.minWidth
125
- ? RESPONSIVE_PADDING.MD.padding
126
- : RESPONSIVE_PADDING.DEFAULT.padding;
127
-
128
- wrappers.forEach((wrapper) => {
129
- wrapper.style.paddingLeft = padding;
130
- wrapper.style.paddingRight = padding;
95
+ document.documentElement.style.setProperty("--safe-area-top", "0px");
96
+ document.documentElement.style.setProperty("--safe-area-right", "0px");
97
+ document.documentElement.style.setProperty("--safe-area-bottom", "0px");
98
+ document.documentElement.style.setProperty("--safe-area-left", "0px");
99
+ }
100
+
101
+ if (DEBUG && logger) {
102
+ logger.Debug("🔍 Safe area vars updated", {
103
+ hasNotch,
104
+ isLandscape
131
105
  });
132
106
  }
133
107
 
@@ -38,7 +38,7 @@
38
38
  @change="(f) => (ui.search.accounts.show = f)"
39
39
  :noBorder="true" />
40
40
  <input
41
- class="h-10 w-44 text-white text-sm p-2 bg-dark-500 flex items-center relative"
41
+ class="h-10 w-28 md:w-36 lg:w-44 text-white text-sm p-2 bg-dark-500 flex items-center relative flex-shrink-1"
42
42
  placeholder="Search Email"
43
43
  aria-label="Search email"
44
44
  autocomplete="new-password"
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div class="mb-8 pb-16 md:mb-0 md:pb-0 mobile-portrait:mb-12 mobile-portrait:pb-24">
3
- <div class="page-header" style="padding-bottom: 0.75rem;">
2
+ <div class="mb-2 md:mb-0" ref="consolePageRef">
3
+ <div class="page-header" style="padding-bottom: 0.5rem;">
4
4
  <div class="page-header-card">
5
5
  <ConsoleIcon />
6
6
  <h4>Console</h4>
@@ -8,21 +8,24 @@
8
8
  </div>
9
9
 
10
10
  <div>
11
- <ConsoleToolbar
12
- v-model:currentTaskLog="currentTaskLog"
13
- v-model:searchQuery="searchQuery"
14
- v-model:filteredLogs="filteredLogs"
15
- v-model:autoscrollToggled="autoscrollToggled"
16
- :taskLogMapping="taskLogMapping"
17
- :userScrolledUp="userScrolledUp"
18
- :filteredCount="filteredCount"
19
- @scroll="startScrolling"
20
- @scroll-stop="stopScrolling"
21
- @autoscroll-toggle="onAutoscrollToggle" />
11
+ <div ref="toolbarRef">
12
+ <ConsoleToolbar
13
+ v-model:currentTaskLog="currentTaskLog"
14
+ v-model:searchQuery="searchQuery"
15
+ v-model:filteredLogs="filteredLogs"
16
+ v-model:autoscrollToggled="autoscrollToggled"
17
+ :taskLogMapping="taskLogMapping"
18
+ :userScrolledUp="userScrolledUp"
19
+ :filteredCount="filteredCount"
20
+ @scroll="startScrolling"
21
+ @scroll-stop="stopScrolling"
22
+ @autoscroll-toggle="onAutoscrollToggle" />
23
+ </div>
22
24
 
23
25
  <Smoothie
24
26
  :weight="0.2"
25
27
  class="console-main"
28
+ :style="consoleMainStyle"
26
29
  ref="$autoscroll"
27
30
  @wheel.stop
28
31
  @touchmove.stop
@@ -45,22 +48,6 @@
45
48
  v-bind:key="`log-${index}`"
46
49
  :style="{ '--index': index }"><code class="md:text-sm lg:text-base mobile-portrait:text-xs+ mobile-portrait:leading-tight" v-html="line"></code></pre>
47
50
  </Smoothie>
48
- <div class="mb-6 mt-4 flex justify-between md:hidden mobile-portrait:mb-16 mobile-portrait:mt-6">
49
- <button
50
- class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
51
- <h3 class="text-sm text-white">Hide Monitors</h3>
52
- <Switch class="scale-75" v-model="filteredLogs" />
53
- </button>
54
- <button
55
- class="relative flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
56
- <h3 class="text-sm text-white">Auto</h3>
57
- <Switch class="scale-75" v-model="autoscrollToggled" @change="onAutoscrollToggle" />
58
- <div
59
- v-if="userScrolledUp && autoscrollToggled"
60
- class="absolute -right-1 -top-1 h-2 w-2 animate-pulse rounded-full bg-yellow-500"
61
- title="Autoscroll paused - scroll to bottom to resume"></div>
62
- </button>
63
- </div>
64
51
  </div>
65
52
  </div>
66
53
  </template>
@@ -123,12 +110,14 @@ import { DEBUG } from "@/utils/debug";
123
110
 
124
111
  import Filter from "@/libs/ansii.js";
125
112
  import { ConsoleIcon } from "@/components/icons";
126
- import Switch from "@/components/ui/controls/atomic/Switch.vue";
127
113
  import WebsocketHeartbeatJs from "websocket-heartbeat-js";
128
114
  import { onMounted, onUnmounted, ref, nextTick, computed, watch } from "vue";
129
115
  import ConsoleToolbar from "@/components/Console/ConsoleToolbar.vue";
116
+ import { TABLE_LAYOUT } from "@/constants/tableLayout";
130
117
 
131
118
  const $autoscroll = ref(null);
119
+ const consolePageRef = ref(null);
120
+ const toolbarRef = ref(null);
132
121
  const logLines = ref([]);
133
122
  const ansii = new Filter();
134
123
  const autoscrollToggled = ref(true);
@@ -139,6 +128,8 @@ const userScrolledUp = ref(false);
139
128
  const scrollInterval = ref(null);
140
129
  const isScrolling = ref(false);
141
130
  const searchQuery = ref("");
131
+ const consoleHeight = ref(0);
132
+ let consoleResizeObserver = null;
142
133
 
143
134
  // Optimized: Cache stripped versions to avoid regex on every filter
144
135
  const logPlainTextCache = new Map();
@@ -173,8 +164,56 @@ const filteredCount = computed(() => {
173
164
  const path = "/api/updates?type=console";
174
165
  const url = (window.location.protocol === "http:" ? "ws://" : "wss://") + window.location.host + path;
175
166
 
176
- const SCROLL_THRESHOLD = 50;
167
+ const SCROLL_THRESHOLD = 100;
177
168
  const SCROLL_AMOUNT = 100;
169
+ const MIN_CONSOLE_HEIGHT = 192;
170
+
171
+ const getTableHeightCap = (viewportHeight) => {
172
+ const isPWA = window.matchMedia("(display-mode: standalone)").matches;
173
+ const isMobile = window.innerWidth <= 768;
174
+ const isSmallMobile = window.innerWidth <= 480;
175
+ const extraBuffer = isPWA && isMobile ? 60 : 0;
176
+ const mobileEdgeBuffer = isMobile ? (isSmallMobile ? 20 : 12) : 0;
177
+
178
+ const availableHeight =
179
+ viewportHeight -
180
+ TABLE_LAYOUT.PROFILES.TOP_RESERVED_SPACE -
181
+ TABLE_LAYOUT.PROFILES.BOTTOM_BUFFER -
182
+ extraBuffer -
183
+ mobileEdgeBuffer;
184
+ const rowHeight = TABLE_LAYOUT.PROFILES.ROW_HEIGHT;
185
+ const minHeight = TABLE_LAYOUT.PROFILES.MIN_ROWS_TO_SHOW * rowHeight;
186
+ const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight);
187
+ return maxCompleteRows * rowHeight;
188
+ };
189
+
190
+ const consoleMainStyle = computed(() => {
191
+ if (!consoleHeight.value) return {};
192
+ const height = `${consoleHeight.value}px`;
193
+ return {
194
+ height,
195
+ maxHeight: height
196
+ };
197
+ });
198
+
199
+ const updateConsoleHeight = () => {
200
+ const element = $autoscroll.value?.el;
201
+ if (!element) return;
202
+
203
+ const viewportHeight = window.visualViewport?.height || window.innerHeight;
204
+ const isMobile = window.innerWidth <= 768;
205
+ const isSmallMobile = window.innerWidth <= 480;
206
+ const baseBottomPadding = window.matchMedia("(display-mode: standalone)").matches ? 12 : 8;
207
+ const mobileEdgePadding = isMobile ? (isSmallMobile ? 16 : 10) : 0;
208
+ const bottomPadding = baseBottomPadding + mobileEdgePadding;
209
+ const availableHeight = Math.floor(viewportHeight - element.getBoundingClientRect().top - bottomPadding);
210
+ const tableHeightCap = getTableHeightCap(viewportHeight);
211
+ consoleHeight.value = Math.max(MIN_CONSOLE_HEIGHT, Math.min(availableHeight, tableHeightCap));
212
+ };
213
+
214
+ const scheduleConsoleHeightUpdate = () => {
215
+ requestAnimationFrame(updateConsoleHeight);
216
+ };
178
217
 
179
218
  const handleScroll = (event) => {
180
219
  if (!autoscrollToggled.value) return;
@@ -184,6 +223,8 @@ const handleScroll = (event) => {
184
223
 
185
224
  const isNearBottom = element.scrollTop + element.clientHeight >= element.scrollHeight - SCROLL_THRESHOLD;
186
225
 
226
+ // Only mark as scrolled up if user deliberately scrolled away from bottom
227
+ // Ignore scroll events triggered by content additions
187
228
  if (userScrolledUp.value === isNearBottom) {
188
229
  userScrolledUp.value = !isNearBottom;
189
230
  }
@@ -258,19 +299,28 @@ const stopScrolling = () => {
258
299
  const autoScrollToBottom = () => {
259
300
  if (!$autoscroll.value?.el || !autoscrollToggled.value) return;
260
301
 
261
- // Only scroll if user hasn't manually scrolled up
262
- if (!userScrolledUp.value) {
263
- const element = $autoscroll.value.el;
264
- const targetScrollTop = element.scrollHeight - element.clientHeight;
302
+ const element = $autoscroll.value.el;
303
+ const targetScrollTop = element.scrollHeight - element.clientHeight;
304
+ const currentDistanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
265
305
 
266
- if (element.scrollTo && Math.abs(element.scrollTop - targetScrollTop) > 5) {
267
- element.scrollTo({
268
- top: targetScrollTop,
269
- behavior: "smooth"
270
- });
271
- } else {
272
- element.scrollTop = targetScrollTop;
273
- }
306
+ // Only respect userScrolledUp if they're significantly away from bottom
307
+ // This prevents autoscroll from pausing due to content-triggered scroll events
308
+ if (currentDistanceFromBottom > SCROLL_THRESHOLD && userScrolledUp.value) {
309
+ return; // User has deliberately scrolled up
310
+ }
311
+
312
+ // Reset userScrolledUp if we're near bottom
313
+ if (currentDistanceFromBottom < SCROLL_THRESHOLD) {
314
+ userScrolledUp.value = false;
315
+ }
316
+
317
+ if (element.scrollTo && Math.abs(element.scrollTop - targetScrollTop) > 5) {
318
+ element.scrollTo({
319
+ top: targetScrollTop,
320
+ behavior: "smooth"
321
+ });
322
+ } else {
323
+ element.scrollTop = targetScrollTop;
274
324
  }
275
325
  };
276
326
 
@@ -358,6 +408,18 @@ watch([currentTaskLog, filteredLogs], () => {
358
408
  onMounted(() => {
359
409
  const socket = new WebsocketHeartbeatJs({ url, pingMsg: "ping" });
360
410
 
411
+ scheduleConsoleHeightUpdate();
412
+ requestAnimationFrame(scheduleConsoleHeightUpdate);
413
+ window.addEventListener("resize", scheduleConsoleHeightUpdate, { passive: true });
414
+ window.addEventListener("orientationchange", scheduleConsoleHeightUpdate, { passive: true });
415
+ window.visualViewport?.addEventListener("resize", scheduleConsoleHeightUpdate);
416
+ window.visualViewport?.addEventListener("scroll", scheduleConsoleHeightUpdate);
417
+ if (typeof ResizeObserver !== "undefined") {
418
+ consoleResizeObserver = new ResizeObserver(scheduleConsoleHeightUpdate);
419
+ if (consolePageRef.value) consoleResizeObserver.observe(consolePageRef.value);
420
+ if (toolbarRef.value) consoleResizeObserver.observe(toolbarRef.value);
421
+ }
422
+
361
423
  socket.onmessage = (event) => {
362
424
  const msg = JSON.parse(event.data);
363
425
  ;
@@ -369,5 +431,11 @@ onMounted(() => {
369
431
 
370
432
  onUnmounted(() => {
371
433
  stopScrolling();
434
+ window.removeEventListener("resize", scheduleConsoleHeightUpdate);
435
+ window.removeEventListener("orientationchange", scheduleConsoleHeightUpdate);
436
+ window.visualViewport?.removeEventListener("resize", scheduleConsoleHeightUpdate);
437
+ window.visualViewport?.removeEventListener("scroll", scheduleConsoleHeightUpdate);
438
+ consoleResizeObserver?.disconnect();
439
+ consoleResizeObserver = null;
372
440
  });
373
441
  </script>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div>
3
- <div class="page-header" style="padding-bottom: 1.25rem;">
3
+ <div class="page-header" style="padding-bottom: 0.5rem;">
4
4
  <div class="page-header-card">
5
5
  <img src="@/assets/img/pencil.svg" />
6
6
  <h4>Editor</h4>
@@ -102,11 +102,11 @@
102
102
  </div>
103
103
  </div>
104
104
  </div>
105
- <div class="hidden-scrollbars flex-1 overflow-y-auto overflow-x-hidden bg-dark-400" style="max-height: 100%; min-height: 150px;">
105
+ <div class="hidden-scrollbars flex-1 overflow-y-auto overflow-x-hidden bg-dark-400">
106
106
  <div
107
107
  v-if="filterBuilder.filters.length"
108
108
  ref="filtersListRef"
109
- class="space-y-0 p-1">
109
+ class="flex flex-col gap-2 p-2">
110
110
  <Filter
111
111
  v-for="(f, i) in filterBuilder.filters"
112
112
  v-show="doesFilterShow(f)"
@@ -568,9 +568,9 @@ watch(renderSeats, async () => {
568
568
 
569
569
  /* Force filters list to be constrained and scrollable */
570
570
  .hidden-scrollbars.flex-1.overflow-y-auto {
571
- max-height: 100%;
572
- min-height: 150px;
571
+ flex: 1 1 0;
573
572
  overflow-y: auto !important;
573
+ overflow-x: hidden !important;
574
574
  }
575
575
 
576
576
  /* SVG and Filters container responsive sizing */