|
50 | 50 | }) |
51 | 51 |
|
52 | 52 |
|
| 53 | + // Memoize access check dependencies to avoid unnecessary recalculations |
| 54 | + // This derived value only recalculates when userStore or selectedPreviewRole changes |
| 55 | + const accessCheckContext = $derived.by(() => { |
| 56 | + const user = $userStore |
| 57 | + const usePreview = user?.is_admin && selectedPreviewRole !== userEffectiveRole |
| 58 | + return { user, usePreview, previewRole: selectedPreviewRole } |
| 59 | + }) |
| 60 | +
|
53 | 61 | /** |
54 | 62 | * Check if the current user (or preview role) has access to a roles array. |
55 | 63 | * Handles both normal access and admin preview mode. |
56 | 64 | */ |
57 | 65 | function checkAccess(roles?: Role[]): boolean { |
58 | | - const user = $userStore |
| 66 | + const context = accessCheckContext |
59 | 67 | // Use preview function if admin has selected a different role to preview |
60 | | - if (user?.is_admin && selectedPreviewRole !== userEffectiveRole) { |
61 | | - return hasRoleAccessForPreview(selectedPreviewRole, roles) |
| 68 | + if (context.usePreview) { |
| 69 | + return hasRoleAccessForPreview(context.previewRole, roles) |
62 | 70 | } |
63 | | - return hasRoleAccess(user, roles) |
| 71 | + return hasRoleAccess(context.user, roles) |
64 | 72 | } |
65 | 73 |
|
66 | 74 | // Get active tabs only (filtered by active and roles) |
| 75 | + // Optimized: $derived.by() automatically memoizes - only recalculates when dependencies change |
67 | 76 | const activeTabs = $derived.by(() => { |
| 77 | + // Access context to establish reactive dependency |
| 78 | + const context = accessCheckContext |
68 | 79 | return (Object.entries(TUTORIALS_CONFIG) as [TabId, TabConfig][]).filter(([, config]) => { |
69 | 80 | // Filter by active |
70 | 81 | if (config.active === false) return false |
71 | | - // Filter by roles |
72 | | - return checkAccess(config.roles) |
| 82 | + // Filter by roles (context is captured in closure) |
| 83 | + if (context.usePreview) { |
| 84 | + return hasRoleAccessForPreview(context.previewRole, config.roles) |
| 85 | + } |
| 86 | + return hasRoleAccess(context.user, config.roles) |
73 | 87 | }) |
74 | 88 | }) |
75 | 89 |
|
|
91 | 105 | const currentTabConfig = $derived(TUTORIALS_CONFIG[tab]) |
92 | 106 |
|
93 | 107 | // Filter tutorials by role and active status (same logic as displayed tutorials) |
94 | | - const visibleTutorials = $derived( |
95 | | - currentTabConfig.tutorials.filter((tutorial) => { |
| 108 | + // Optimized: $derived.by() automatically memoizes - only recalculates when tab or accessCheckContext changes |
| 109 | + const visibleTutorials = $derived.by(() => { |
| 110 | + // Access context to establish reactive dependency |
| 111 | + const context = accessCheckContext |
| 112 | + return currentTabConfig.tutorials.filter((tutorial) => { |
96 | 113 | if (tutorial.active === false) return false |
97 | | - return checkAccess(tutorial.roles) |
| 114 | + // Use context directly to avoid function call overhead |
| 115 | + if (context.usePreview) { |
| 116 | + return hasRoleAccessForPreview(context.previewRole, tutorial.roles) |
| 117 | + } |
| 118 | + return hasRoleAccess(context.user, tutorial.roles) |
98 | 119 | }) |
99 | | - ) |
| 120 | + }) |
100 | 121 |
|
101 | 122 | // Create tutorial index mapping for current tab (only visible tutorials with index defined) |
102 | | - const currentTabTutorialIndexes = $derived( |
103 | | - Object.fromEntries( |
| 123 | + // Optimized: only recalculates when visibleTutorials changes |
| 124 | + const currentTabTutorialIndexes = $derived.by(() => { |
| 125 | + return Object.fromEntries( |
104 | 126 | visibleTutorials |
105 | 127 | .filter((tutorial) => tutorial.index !== undefined) |
106 | 128 | .map((tutorial) => [tutorial.id, tutorial.index!]) |
107 | 129 | ) |
108 | | - ) |
| 130 | + }) |
109 | 131 |
|
110 | 132 | // Calculate progress for current tab (only counting visible tutorials) |
111 | 133 | const totalTutorials = $derived(getTutorialProgressTotal(currentTabTutorialIndexes)) |
|
0 commit comments