@necrolab/dashboard 0.5.30 → 0.5.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@necrolab/dashboard",
3
- "version": "0.5.30",
3
+ "version": "0.5.31",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "rm -rf dist && vite build && npx workbox-cli generateSW workbox-config.cjs",
Binary file
@@ -30,7 +30,7 @@
30
30
  </div>
31
31
  </Header>
32
32
  <RecycleScroller
33
- v-if="props.accounts.length !== 0"
33
+ v-if="props.accounts.length !== 0 && useVirtualScroller"
34
34
  class="hidden-scrollbars touch-pan-y overflow-y-auto overflow-x-hidden transition-colors duration-150 table-scroll scrollable"
35
35
  :style="{ height: dynamicTableHeight, maxHeight: dynamicTableHeight }"
36
36
  :items="virtualAccounts"
@@ -46,6 +46,20 @@
46
46
  </div>
47
47
  </template>
48
48
  </RecycleScroller>
49
+ <div
50
+ v-else-if="props.accounts.length !== 0"
51
+ class="hidden-scrollbars touch-pan-y overflow-y-auto overflow-x-hidden transition-colors duration-150 table-scroll scrollable"
52
+ :style="{ maxHeight: dynamicTableHeight }">
53
+ <div
54
+ v-for="(item, index) in virtualAccounts"
55
+ :key="item.virtualKey"
56
+ class="min-h-16 flex-shrink-0 hover:bg-dark-550">
57
+ <Account
58
+ :class="getRowClass(index)"
59
+ :account="item.account"
60
+ :privacy="props.privacy" />
61
+ </div>
62
+ </div>
49
63
  <EmptyState v-else :icon="MailIcon" message="No accounts found" subtitle="Create accounts to get started" />
50
64
  </div>
51
65
  </template>
@@ -89,6 +103,8 @@ const virtualAccounts = computed(() =>
89
103
  virtualKey: String(account.id ?? account.email ?? "account")
90
104
  }))
91
105
  );
106
+ const ACCOUNT_VIRTUALIZE_THRESHOLD = 80;
107
+ const useVirtualScroller = computed(() => props.accounts.length > ACCOUNT_VIRTUALIZE_THRESHOLD);
92
108
 
93
109
  const handleVirtualWheel = (event) => {
94
110
  const target = event.currentTarget;
@@ -34,7 +34,7 @@
34
34
  </div>
35
35
  </Header>
36
36
  <RecycleScroller
37
- v-if="props.profiles.length !== 0"
37
+ v-if="props.profiles.length !== 0 && useVirtualScroller"
38
38
  class="hidden-scrollbars touch-pan-y overflow-y-auto overflow-x-hidden transition-colors duration-150 table-scroll scrollable"
39
39
  :style="{ height: dynamicTableHeight, maxHeight: dynamicTableHeight }"
40
40
  :items="virtualProfiles"
@@ -50,6 +50,20 @@
50
50
  </div>
51
51
  </template>
52
52
  </RecycleScroller>
53
+ <div
54
+ v-else-if="props.profiles.length !== 0"
55
+ class="hidden-scrollbars touch-pan-y overflow-y-auto overflow-x-hidden transition-colors duration-150 table-scroll scrollable"
56
+ :style="{ maxHeight: dynamicTableHeight }">
57
+ <div
58
+ v-for="(item, index) in virtualProfiles"
59
+ :key="item.virtualKey"
60
+ class="min-h-16 flex-shrink-0 hover:bg-dark-550">
61
+ <Profile
62
+ :class="getRowClass(index)"
63
+ :profile="item.profile"
64
+ :privacy="props.privacy" />
65
+ </div>
66
+ </div>
53
67
  <EmptyState v-else :icon="ProfileIcon" message="No profiles found" subtitle="Create profiles to get started" />
54
68
  </div>
55
69
  </template>
@@ -88,6 +102,8 @@ const virtualProfiles = computed(() =>
88
102
  virtualKey: String(profile.id ?? `${profile.profileName ?? "profile"}-${profile.cardNumber ?? ""}`)
89
103
  }))
90
104
  );
105
+ const PROFILE_VIRTUALIZE_THRESHOLD = 80;
106
+ const useVirtualScroller = computed(() => props.profiles.length > PROFILE_VIRTUALIZE_THRESHOLD);
91
107
 
92
108
  const handleVirtualWheel = (event) => {
93
109
  const target = event.currentTarget;
@@ -35,9 +35,9 @@
35
35
  </div>
36
36
  </Header>
37
37
  <DynamicScroller
38
- v-if="virtualTaskItems.length"
38
+ v-if="virtualTaskItems.length && useVirtualScroller"
39
39
  class="hidden-scrollbars touch-pan-y min-h-0 overflow-y-auto overflow-x-hidden scrollable"
40
- :style="{ height: dynamicTableHeight, maxHeight: dynamicTableHeight }"
40
+ :style="{ height: maxTableHeight, maxHeight: maxTableHeight }"
41
41
  :items="virtualTaskItems"
42
42
  :min-item-size="virtualMinItemSize"
43
43
  :buffer="virtualBuffer"
@@ -66,10 +66,25 @@
66
66
  </DynamicScrollerItem>
67
67
  </template>
68
68
  </DynamicScroller>
69
+ <div
70
+ v-else-if="virtualTaskItems.length"
71
+ class="hidden-scrollbars touch-pan-y min-h-0 overflow-y-auto overflow-x-hidden scrollable"
72
+ :style="{ maxHeight: maxTableHeight }">
73
+ <div
74
+ v-for="(item, index) in virtualTaskItems"
75
+ :key="item.taskId"
76
+ class="shrink-0 border-b border-dark-650 min-h-14.5 md:min-h-17.25 has-[.event-details]:min-h-18.75 mobile-portrait:min-h-12.5 transition-colors duration-150 ease-in-out hover:!bg-dark-550">
77
+ <Task
78
+ v-if="props.tasks[item.taskId]"
79
+ :task="props.tasks[item.taskId]"
80
+ :preferEventName="props.preferEventName"
81
+ :class="getRowClass(index)" />
82
+ </div>
83
+ </div>
69
84
  <div
70
85
  v-else
71
86
  class="empty-state flex flex-col items-center justify-center py-8 text-center bg-dark-400 text-light-500 text-sm font-medium"
72
- :style="{ minHeight: dynamicTableHeight, maxHeight: dynamicTableHeight }">
87
+ :style="{ minHeight: emptyStateHeight }">
73
88
  <div
74
89
  v-if="
75
90
  !ui.queueStats.queued && !ui.queueStats.sleeping && ui.queueStats.nextQueuePasses.length === 0
@@ -237,6 +252,9 @@ onUnmounted(() => {
237
252
 
238
253
  const virtualMinItemSize = computed(() => (windowWidth.value <= 768 ? 58 : 69));
239
254
  const virtualBuffer = computed(() => virtualMinItemSize.value * 8);
255
+ const TASK_VIRTUALIZE_THRESHOLD = 80;
256
+ const useVirtualScroller = computed(() => virtualTaskItems.value.length > TASK_VIRTUALIZE_THRESHOLD);
257
+ const emptyStateHeight = computed(() => `${virtualMinItemSize.value * 2}px`);
240
258
 
241
259
  const handleVirtualWheel = (event) => {
242
260
  const target = event.currentTarget;
@@ -244,7 +262,7 @@ const handleVirtualWheel = (event) => {
244
262
  target.scrollTop += event.deltaY;
245
263
  };
246
264
 
247
- const dynamicTableHeight = computed(() => {
265
+ const maxTableHeight = computed(() => {
248
266
  // Detect PWA mode (standalone display)
249
267
  const isPWA = window.matchMedia('(display-mode: standalone)').matches;
250
268
 
@@ -280,12 +298,10 @@ const dynamicTableHeight = computed(() => {
280
298
 
281
299
  // Calculate row height based on screen size
282
300
  const rowHeight = windowWidth.value <= 768 ? 58 : 69; // Mobile vs desktop row height
283
- const minRowsToShow = 2; // Always show at least 2 rows
284
- const minHeight = minRowsToShow * rowHeight;
285
-
286
- // Calculate how many complete rows can fit
287
- const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight) + 1;
288
- const exactHeight = maxCompleteRows * rowHeight;
301
+ const safeAvailableHeight = Math.max(availableHeight, rowHeight * 2);
302
+ const desktopBonusRows = windowWidth.value >= 1024 ? 1 : 0;
303
+ const maxRowsThatFit = Math.max(1, Math.floor(safeAvailableHeight / rowHeight) + desktopBonusRows);
304
+ const exactHeight = maxRowsThatFit * rowHeight;
289
305
 
290
306
  return exactHeight + "px";
291
307
  });
@@ -1,14 +1,15 @@
1
- import { computed } from "vue";
1
+ import { computed, unref } from "vue";
2
2
  import { useWindowDimensions } from "./useWindowDimensions";
3
3
 
4
- export function useDynamicTableHeight(options = {}) {
4
+ export function useDynamicTableHeight(options = {}, itemCount = null) {
5
5
  const { windowHeight, windowWidth } = useWindowDimensions();
6
6
 
7
7
  const {
8
8
  topReservedSpace = options.TOP_RESERVED_SPACE ?? 243,
9
9
  bottomBuffer = options.BOTTOM_BUFFER ?? 16,
10
10
  rowHeight = options.ROW_HEIGHT ?? 64,
11
- minRowsToShow = options.MIN_ROWS_TO_SHOW ?? 2
11
+ minRowsToShow = options.MIN_ROWS_TO_SHOW ?? 2,
12
+ minRowsWhenEmpty = options.MIN_ROWS_WHEN_EMPTY ?? minRowsToShow
12
13
  } = options;
13
14
 
14
15
  const dynamicTableHeight = computed(() => {
@@ -22,9 +23,25 @@ export function useDynamicTableHeight(options = {}) {
22
23
  const extraBuffer = isPWA && isMobile ? (isIPhonePortrait ? 24 : 40) : 0;
23
24
 
24
25
  const availableHeight = windowHeight.value - topReservedSpace - bottomBuffer - extraBuffer;
25
- const minHeight = minRowsToShow * rowHeight;
26
- const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight);
27
- const exactHeight = maxCompleteRows * rowHeight;
26
+ const safeAvailableHeight = Math.max(availableHeight, rowHeight);
27
+ const maxRowsThatFit = Math.max(1, Math.floor(safeAvailableHeight / rowHeight));
28
+
29
+ if (itemCount === null || itemCount === undefined) {
30
+ const minRows = Math.max(1, minRowsToShow);
31
+ const exactHeight = Math.max(minRows, maxRowsThatFit) * rowHeight;
32
+ return exactHeight + "px";
33
+ }
34
+
35
+ const count = Number(unref(itemCount) || 0);
36
+ let targetRows = Math.max(1, minRowsWhenEmpty);
37
+
38
+ if (count > 0) {
39
+ targetRows = Math.max(1, Math.min(count, maxRowsThatFit));
40
+ } else {
41
+ targetRows = Math.min(maxRowsThatFit, Math.max(1, minRowsWhenEmpty));
42
+ }
43
+
44
+ const exactHeight = targetRows * rowHeight;
28
45
 
29
46
  return exactHeight + "px";
30
47
  });