@igstack/app-catalog-frontend-core 2.0.4 → 3.0.0-alpha-20260917212201
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/dist/esm/__tests__/modules/appCatalog/utils/areaGrouping.test.d.ts +1 -0
- package/dist/esm/__tests__/modules/pwa/PwaAutoUpdateController.test.d.ts +1 -0
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/catalog/AppCatalogGrid.d.ts +6 -3
- package/dist/esm/modules/appCatalog/ui/catalog/AppCatalogGrid.js +112 -32
- package/dist/esm/modules/appCatalog/ui/catalog/AppCatalogGrid.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.js +0 -5
- package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.js.map +1 -1
- package/dist/esm/modules/appCatalog/utils/areaGrouping.d.ts +22 -0
- package/dist/esm/modules/appCatalog/utils/areaGrouping.js +46 -0
- package/dist/esm/modules/appCatalog/utils/areaGrouping.js.map +1 -0
- package/dist/esm/modules/pwa/PwaAutoUpdateController.d.ts +8 -0
- package/dist/esm/modules/pwa/PwaAutoUpdateController.js +15 -1
- package/dist/esm/modules/pwa/PwaAutoUpdateController.js.map +1 -1
- package/dist/esm/types/uiSettings.d.ts +28 -0
- package/package.json +4 -4
- package/src/__tests__/modules/appCatalog/utils/areaGrouping.test.ts +83 -0
- package/src/__tests__/modules/pwa/PwaAutoUpdateController.test.ts +61 -0
- package/src/index.tsx +2 -1
- package/src/modules/appCatalog/ui/catalog/AppCatalogGrid.tsx +159 -34
- package/src/modules/appCatalog/ui/pages/AppCatalogPage.tsx +0 -10
- package/src/modules/appCatalog/utils/areaGrouping.ts +85 -0
- package/src/modules/pwa/PwaAutoUpdateController.ts +19 -3
- package/src/types/uiSettings.ts +30 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GroupingTagDefinition,
|
|
3
|
+
Resource,
|
|
4
|
+
} from '@igstack/app-catalog-backend-core'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Grouping of the full resource list into areas.
|
|
8
|
+
*
|
|
9
|
+
* The vocabulary itself is NOT here: which `category:<value>` tags exist, what
|
|
10
|
+
* they are called and which of them belong on the day-to-day shelf is
|
|
11
|
+
* deployment-specific. Labels come from the catalog's own tag definitions and
|
|
12
|
+
* icons from `UiSettings.areas`, so the core only knows the mechanism.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const CATEGORY_PREFIX = 'category:'
|
|
16
|
+
const EVERYONE_TAG = 'universality:everyone'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Synthetic key for the merged first group. Underscored so it cannot collide
|
|
20
|
+
* with a real `category:<value>`.
|
|
21
|
+
*/
|
|
22
|
+
export const DAY_TO_DAY_AREA_KEY = '__day-to-day__'
|
|
23
|
+
|
|
24
|
+
/** Fallback area for resources carrying no `category:` tag. */
|
|
25
|
+
export const OTHER_AREA_KEY = 'other'
|
|
26
|
+
|
|
27
|
+
export const categoryOf = (app: Resource): string =>
|
|
28
|
+
app.tags
|
|
29
|
+
?.find((t) => t.startsWith(CATEGORY_PREFIX))
|
|
30
|
+
?.slice(CATEGORY_PREFIX.length) ?? OTHER_AREA_KEY
|
|
31
|
+
|
|
32
|
+
const humanize = (key: string): string => {
|
|
33
|
+
const words = key.replace(/[-_]+/g, ' ').trim()
|
|
34
|
+
return words.charAt(0).toUpperCase() + words.slice(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Day-to-day tools first, then one area per category, biggest area first.
|
|
39
|
+
* Input order is preserved inside each group.
|
|
40
|
+
*
|
|
41
|
+
* `dayToDayCategories` are categories that fold into the day-to-day group
|
|
42
|
+
* instead of getting an area of their own (perks, office services, …).
|
|
43
|
+
*/
|
|
44
|
+
export function groupByArea(
|
|
45
|
+
apps: Resource[],
|
|
46
|
+
dayToDayCategories: readonly string[] = [],
|
|
47
|
+
): [string, Resource[]][] {
|
|
48
|
+
const folded = new Set(dayToDayCategories)
|
|
49
|
+
const dayToDay: Resource[] = []
|
|
50
|
+
const byCategory = new Map<string, Resource[]>()
|
|
51
|
+
|
|
52
|
+
for (const app of apps) {
|
|
53
|
+
const key = categoryOf(app)
|
|
54
|
+
if (app.tags?.includes(EVERYONE_TAG) || folded.has(key)) {
|
|
55
|
+
dayToDay.push(app)
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
const list = byCategory.get(key)
|
|
59
|
+
if (list) list.push(app)
|
|
60
|
+
else byCategory.set(key, [app])
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const areas = [...byCategory.entries()].sort(
|
|
64
|
+
(a, b) => b[1].length - a[1].length,
|
|
65
|
+
)
|
|
66
|
+
return dayToDay.length
|
|
67
|
+
? [[DAY_TO_DAY_AREA_KEY, dayToDay] as [string, Resource[]], ...areas]
|
|
68
|
+
: areas
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Title for an area. The catalog's `category` tag definition supplies the
|
|
73
|
+
* display names; an unknown value still gets a readable humanized slug.
|
|
74
|
+
*/
|
|
75
|
+
export function areaLabel(
|
|
76
|
+
key: string,
|
|
77
|
+
tagsDefinitions: readonly GroupingTagDefinition[] = [],
|
|
78
|
+
dayToDayLabel = 'Day-to-day tools',
|
|
79
|
+
): string {
|
|
80
|
+
if (key === DAY_TO_DAY_AREA_KEY) return dayToDayLabel
|
|
81
|
+
const defined = tagsDefinitions
|
|
82
|
+
.find((d) => d.prefix === 'category')
|
|
83
|
+
?.values.find((v) => v.value === key)?.displayName
|
|
84
|
+
return defined || humanize(key)
|
|
85
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PwaAutoUpdateOptions, PwaUpdateHandle } from './types'
|
|
2
2
|
|
|
3
|
-
const DEFAULT_IDLE_TIMEOUT_MS =
|
|
3
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 60 * 1000 // 1 minute
|
|
4
4
|
const DEFAULT_MIN_CHECK_INTERVAL_MS = 30 * 1000 // 30 seconds
|
|
5
5
|
const VISIBILITY_DEBOUNCE_MS = 1000
|
|
6
6
|
const ERROR_UPDATE_WAIT_MS = 3000
|
|
@@ -96,6 +96,8 @@ export class PwaAutoUpdateController {
|
|
|
96
96
|
} catch {
|
|
97
97
|
this.log('registration.update() failed, reloading anyway')
|
|
98
98
|
}
|
|
99
|
+
|
|
100
|
+
this.activateWaitingWorker(registration)
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
// Wait briefly for a new SW to activate, then reload
|
|
@@ -158,8 +160,22 @@ export class PwaAutoUpdateController {
|
|
|
158
160
|
} catch (error) {
|
|
159
161
|
this.log('registration.update() failed:', error)
|
|
160
162
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
|
|
164
|
+
this.activateWaitingWorker(registration)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A freshly installed worker stays in `waiting` for as long as this page is
|
|
169
|
+
* controlled by the old one — a reload does not release it, only closing
|
|
170
|
+
* every tab on the origin does. Ask it to take over now; the generated
|
|
171
|
+
* worker answers this message with `skipWaiting()`, and the registration's
|
|
172
|
+
* own `activated` listener reloads the page onto the new build.
|
|
173
|
+
*/
|
|
174
|
+
private activateWaitingWorker(registration: ServiceWorkerRegistration): void {
|
|
175
|
+
if (!registration.waiting) return
|
|
176
|
+
|
|
177
|
+
this.log('New worker is waiting, sending SKIP_WAITING')
|
|
178
|
+
registration.waiting.postMessage({ type: 'SKIP_WAITING' })
|
|
163
179
|
}
|
|
164
180
|
|
|
165
181
|
private reload(): void {
|
package/src/types/uiSettings.ts
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
|
+
import type { ComponentType } from 'react'
|
|
1
2
|
import type { PwaAutoUpdateOptions } from '~/modules/pwa/types'
|
|
2
3
|
|
|
4
|
+
/** Icon component for an area header. Lucide icon components satisfy this. */
|
|
5
|
+
export type AreaIcon = ComponentType<{
|
|
6
|
+
size?: number
|
|
7
|
+
strokeWidth?: number
|
|
8
|
+
className?: string
|
|
9
|
+
}>
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Presentation of the area sections on the home view. The taxonomy is
|
|
13
|
+
* deployment-specific, so the core ships none of it: labels come from the
|
|
14
|
+
* catalog's own `category` tag definition and everything below is optional.
|
|
15
|
+
*/
|
|
16
|
+
export interface AreasSettings {
|
|
17
|
+
/**
|
|
18
|
+
* Icon per `category:<value>`; the key `DAY_TO_DAY_AREA_KEY` targets the
|
|
19
|
+
* merged first group. Areas without an icon render title-only.
|
|
20
|
+
*/
|
|
21
|
+
icons?: Record<string, AreaIcon>
|
|
22
|
+
/**
|
|
23
|
+
* Category values that fold into the day-to-day group instead of getting an
|
|
24
|
+
* area of their own (e.g. perks, office services).
|
|
25
|
+
*/
|
|
26
|
+
dayToDayCategories?: string[]
|
|
27
|
+
/** Title of the merged first group. Defaults to "Day-to-day tools". */
|
|
28
|
+
dayToDayLabel?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
3
31
|
/**
|
|
4
32
|
* UI settings passed from the app entry point.
|
|
5
33
|
*/
|
|
@@ -11,6 +39,8 @@ export interface UiSettings {
|
|
|
11
39
|
}
|
|
12
40
|
/** Frontend build identifier baked at build time (e.g., pipeline ID via VITE env var) */
|
|
13
41
|
frontendBuildId?: string
|
|
42
|
+
/** Area sections on the home view: icons and the day-to-day fold. */
|
|
43
|
+
areas?: AreasSettings
|
|
14
44
|
/** PWA auto-update configuration (idle timeout, check interval, debug) */
|
|
15
45
|
pwaAutoUpdate?: PwaAutoUpdateOptions
|
|
16
46
|
/**
|