@quazardous/qdadm 2.29.0 → 2.30.0
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
|
@@ -58,7 +58,8 @@ import {
|
|
|
58
58
|
type ShowResolvedFieldConfig,
|
|
59
59
|
} from './createShowFieldResolver'
|
|
60
60
|
import type { OrchestratorLike } from '../entity/EntityManager.interface'
|
|
61
|
-
import type {
|
|
61
|
+
import type { LiveEntityPolicy } from '../entity/EntityManager.types'
|
|
62
|
+
import type { ButtonSeverity, EntityRecord } from '../types'
|
|
62
63
|
import { useActionRegistry } from './useActionRegistry'
|
|
63
64
|
import { useLiveEntity } from './useLiveEntity'
|
|
64
65
|
|
|
@@ -329,17 +330,35 @@ export function useEntityItemShowPage<T = Record<string, unknown>>(
|
|
|
329
330
|
const notifications = useNotifications()
|
|
330
331
|
const route = useRoute()
|
|
331
332
|
useLiveEntity(entity, manager, async () => {
|
|
333
|
+
// A burst of events is one reload, so this is the record from before the burst.
|
|
334
|
+
const before = data.value
|
|
332
335
|
const record = await base.reload()
|
|
333
336
|
// A change made elsewhere to the record on screen leaves a line in the
|
|
334
|
-
// history (#2677): short-lived, linked back to it, never a pop-up.
|
|
337
|
+
// history (#2677): short-lived, linked back to it, never a pop-up. The
|
|
338
|
+
// entity may say the change is not worth one, or what it means (#2685).
|
|
335
339
|
if (!record) return
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
const to = route?.name ? { to: { name: String(route.name), params: { ...route.params } } } : {}
|
|
341
|
+
const describeUpdate = (manager as { live?: LiveEntityPolicy }).live?.describeUpdate
|
|
342
|
+
if (!describeUpdate) {
|
|
343
|
+
const name = entityLabel.value ? `"${entityLabel.value}"` : `#${entityId.value}`
|
|
344
|
+
notifications.addNotification({
|
|
345
|
+
severity: 'info',
|
|
346
|
+
summary: `${manager.label ?? entity} ${name} updated elsewhere`,
|
|
347
|
+
keep: 'short',
|
|
348
|
+
...to,
|
|
349
|
+
})
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
if (!before) return
|
|
353
|
+
let entry
|
|
354
|
+
try {
|
|
355
|
+
entry = describeUpdate(before as unknown as EntityRecord, record as unknown as EntityRecord)
|
|
356
|
+
} catch (err) {
|
|
357
|
+
console.warn(`[qdadm] live.describeUpdate failed for ${entity} #${entityId.value}:`, err)
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
if (!entry) return
|
|
361
|
+
notifications.addNotification({ severity: 'info', keep: 'short', ...to, ...entry })
|
|
343
362
|
}, {
|
|
344
363
|
id: () => entityId.value,
|
|
345
364
|
// The update has arrived: say so on the badge at once — before the reload is
|
|
@@ -105,7 +105,7 @@ export class EntityManager<T extends EntityRecord = EntityRecord> {
|
|
|
105
105
|
protected _warmup: boolean
|
|
106
106
|
protected _authSensitive: boolean
|
|
107
107
|
/** Reaction policy for changes made outside this session (#1888). */
|
|
108
|
-
protected _live: Required<LiveEntityPolicy
|
|
108
|
+
protected _live: LiveEntityPolicy & Required<Pick<LiveEntityPolicy, 'refresh' | 'coalesceMs'>>
|
|
109
109
|
protected _system: boolean
|
|
110
110
|
|
|
111
111
|
protected _scopeWhitelist: string[] | null
|
|
@@ -215,6 +215,7 @@ export class EntityManager<T extends EntityRecord = EntityRecord> {
|
|
|
215
215
|
this._live = {
|
|
216
216
|
refresh: live?.refresh ?? 'mounted',
|
|
217
217
|
coalesceMs: live?.coalesceMs ?? 300,
|
|
218
|
+
...(live?.describeUpdate ? { describeUpdate: live.describeUpdate } : {}),
|
|
218
219
|
}
|
|
219
220
|
this._system = system
|
|
220
221
|
|
|
@@ -640,7 +641,7 @@ export class EntityManager<T extends EntityRecord = EntityRecord> {
|
|
|
640
641
|
* Check if user can read entities
|
|
641
642
|
*/
|
|
642
643
|
/** Reaction policy for changes made outside this session (#1888). */
|
|
643
|
-
get live(): Required<LiveEntityPolicy
|
|
644
|
+
get live(): LiveEntityPolicy & Required<Pick<LiveEntityPolicy, 'refresh' | 'coalesceMs'>> {
|
|
644
645
|
return this._live
|
|
645
646
|
}
|
|
646
647
|
|
|
@@ -17,6 +17,7 @@ import type { HookRegistry } from '../hooks/HookRegistry'
|
|
|
17
17
|
// Circular type import — safe because `import type` is erased at runtime
|
|
18
18
|
import type { EntityManager } from './EntityManager'
|
|
19
19
|
import type { RouteStatePersister } from '../routeState'
|
|
20
|
+
import type { NewNotification } from '../notifications/NotificationStore'
|
|
20
21
|
|
|
21
22
|
// ============ INTERNAL TYPES ============
|
|
22
23
|
|
|
@@ -230,8 +231,24 @@ export interface LiveEntityPolicy {
|
|
|
230
231
|
* Default 300. `0` reloads on every event — rarely what you want.
|
|
231
232
|
*/
|
|
232
233
|
coalesceMs?: number
|
|
234
|
+
/**
|
|
235
|
+
* Whether a live reload of a detail page is worth a notification entry, and
|
|
236
|
+
* what it says (#2685). Called once per reload, with the record that was on
|
|
237
|
+
* screen and the reloaded one.
|
|
238
|
+
*
|
|
239
|
+
* Return `null` / `undefined` for no entry (the badge still flashes), or the
|
|
240
|
+
* entry: `severity` defaults to `'info'`, `keep` to `'short'`, `to` to the
|
|
241
|
+
* record's page. Without this hook, the page adds a generic
|
|
242
|
+
* "… updated elsewhere" entry. If it throws, no entry is added.
|
|
243
|
+
*
|
|
244
|
+
* Do not mutate `before` or `after`: they are the page's own records.
|
|
245
|
+
*/
|
|
246
|
+
describeUpdate?: (before: EntityRecord, after: EntityRecord) => LiveUpdateEntry | null | undefined
|
|
233
247
|
}
|
|
234
248
|
|
|
249
|
+
/** The notification entry `LiveEntityPolicy.describeUpdate` asks for (#2685). */
|
|
250
|
+
export type LiveUpdateEntry = Omit<NewNotification, 'severity'> & Partial<Pick<NewNotification, 'severity'>>
|
|
251
|
+
|
|
235
252
|
/**
|
|
236
253
|
* EntityManager constructor options
|
|
237
254
|
*/
|
package/src/index.ts
CHANGED
|
@@ -41,7 +41,7 @@ export { SSEBridge, createSSEBridge, SSE_SIGNALS } from './kernel/SSEBridge'
|
|
|
41
41
|
export type { SSEBridgeOptions } from './kernel/SSEBridge'
|
|
42
42
|
export { useLiveEntity } from './composables/useLiveEntity'
|
|
43
43
|
export type { UseLiveEntityOptions } from './composables/useLiveEntity'
|
|
44
|
-
export type { LiveEntityPolicy } from './entity/EntityManager.types'
|
|
44
|
+
export type { LiveEntityPolicy, LiveUpdateEntry } from './entity/EntityManager.types'
|
|
45
45
|
export { LiveEntityRouter, createLiveEntityRouter } from './kernel/LiveEntityRouter'
|
|
46
46
|
export type {
|
|
47
47
|
LiveEntityRouterOptions,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Mobile: Full width overlay at bottom of screen
|
|
9
9
|
*
|
|
10
10
|
* Features:
|
|
11
|
-
* -
|
|
11
|
+
* - Toolbar row (mark read, clear, close) above the list
|
|
12
12
|
* - Status items section (custom module items)
|
|
13
13
|
* - Notification list (most recent first)
|
|
14
14
|
* - Empty state
|
|
@@ -189,16 +189,15 @@ function formatTime(timestamp: number): string {
|
|
|
189
189
|
left: calc(var(--fad-sidebar-width-collapsed, 2.5rem) + 0.375rem);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
/*
|
|
192
|
+
/* Toolbar - its own row above the list, so its buttons never cover an entry's dismiss button */
|
|
193
193
|
.notification-toolbar {
|
|
194
|
-
position: absolute;
|
|
195
|
-
top: 0;
|
|
196
|
-
right: 0;
|
|
197
194
|
display: flex;
|
|
198
195
|
align-items: center;
|
|
196
|
+
justify-content: flex-end;
|
|
199
197
|
gap: 1px;
|
|
200
198
|
padding: 0.25rem;
|
|
201
|
-
|
|
199
|
+
border-bottom: 1px solid var(--p-surface-100, #f1f5f9);
|
|
200
|
+
flex-shrink: 0;
|
|
202
201
|
}
|
|
203
202
|
|
|
204
203
|
.notification-toolbar-btn {
|
|
@@ -228,7 +227,6 @@ function formatTime(timestamp: number): string {
|
|
|
228
227
|
/* Status items */
|
|
229
228
|
.notification-panel-status {
|
|
230
229
|
padding: 0.5rem 0.625rem;
|
|
231
|
-
padding-right: 2.5rem;
|
|
232
230
|
border-bottom: 1px solid var(--p-surface-100, #f1f5f9);
|
|
233
231
|
flex-shrink: 0;
|
|
234
232
|
}
|
|
@@ -309,10 +307,6 @@ function formatTime(timestamp: number): string {
|
|
|
309
307
|
transition: background 0.1s;
|
|
310
308
|
}
|
|
311
309
|
|
|
312
|
-
.notification-item:first-child {
|
|
313
|
-
padding-right: 2.5rem;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
310
|
.notification-item:hover {
|
|
317
311
|
background: var(--p-surface-50, #f8fafc);
|
|
318
312
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -175,7 +175,7 @@ export interface EntityManagerOptions<T extends EntityRecord = EntityRecord> {
|
|
|
175
175
|
warmup?: boolean
|
|
176
176
|
authSensitive?: boolean
|
|
177
177
|
/** What this entity does when it changed elsewhere (#1888). */
|
|
178
|
-
live?:
|
|
178
|
+
live?: import('../entity/EntityManager.types').LiveEntityPolicy
|
|
179
179
|
system?: boolean
|
|
180
180
|
scopeWhitelist?: string[] | null
|
|
181
181
|
isOwn?: ((record: T, user: unknown) => boolean) | null
|