@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
|
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:
|
|
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:
|
|
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
|
|
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
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
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
|
|
26
|
-
const
|
|
27
|
-
|
|
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
|
});
|