@dappermountain/payload-plugin-achievements 0.2.5 → 0.2.7
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/README.md +34 -8
- package/dist/admin/LucideIconField.d.ts +1 -1
- package/dist/admin/LucideIconField.d.ts.map +1 -1
- package/dist/admin/LucideIconField.js +139 -24
- package/dist/catalog.d.ts +1 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +1 -1
- package/dist/cli/reconcile.d.ts.map +1 -1
- package/dist/cli/reconcile.js +1 -0
- package/dist/collections/MetricBalances/access/index.d.ts +4 -0
- package/dist/collections/MetricBalances/access/index.d.ts.map +1 -0
- package/dist/collections/MetricBalances/access/index.js +9 -0
- package/dist/collections/MetricBalances/hooks/index.d.ts +3 -0
- package/dist/collections/MetricBalances/hooks/index.d.ts.map +1 -0
- package/dist/collections/MetricBalances/hooks/index.js +1 -0
- package/dist/collections/MetricBalances/index.d.ts +3 -0
- package/dist/collections/MetricBalances/index.d.ts.map +1 -0
- package/dist/collections/MetricBalances/index.js +48 -0
- package/dist/collections/Metrics/index.d.ts.map +1 -1
- package/dist/collections/Metrics/index.js +64 -3
- package/dist/collections/fields/ruleFields.d.ts.map +1 -1
- package/dist/collections/fields/ruleFields.js +45 -2
- package/dist/collections/hooks/preventCatalogDelete.d.ts.map +1 -1
- package/dist/collections/hooks/preventCatalogDelete.js +10 -1
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/collections/index.js +2 -0
- package/dist/collections/slugs.d.ts.map +1 -1
- package/dist/collections/slugs.js +1 -0
- package/dist/defaults.d.ts +4 -0
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +10 -0
- package/dist/endpoints/index.d.ts.map +1 -1
- package/dist/endpoints/index.js +4 -0
- package/dist/endpoints/leaderboard.d.ts +6 -0
- package/dist/endpoints/leaderboard.d.ts.map +1 -0
- package/dist/endpoints/leaderboard.js +59 -0
- package/dist/extensions/elapsedSinceOptions.d.ts +7 -0
- package/dist/extensions/elapsedSinceOptions.d.ts.map +1 -0
- package/dist/extensions/elapsedSinceOptions.js +16 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +9 -0
- package/dist/seed/index.d.ts +10 -0
- package/dist/seed/index.d.ts.map +1 -1
- package/dist/seed/index.js +14 -2
- package/dist/services/achievement/builtInRules.d.ts.map +1 -1
- package/dist/services/achievement/builtInRules.js +59 -21
- package/dist/services/achievement/duration.d.ts +6 -0
- package/dist/services/achievement/duration.d.ts.map +1 -0
- package/dist/services/achievement/duration.js +43 -0
- package/dist/services/achievement/getMetricLeaderboard.d.ts +30 -0
- package/dist/services/achievement/getMetricLeaderboard.d.ts.map +1 -0
- package/dist/services/achievement/getMetricLeaderboard.js +62 -0
- package/dist/services/achievement/index.d.ts +5 -0
- package/dist/services/achievement/index.d.ts.map +1 -1
- package/dist/services/achievement/index.js +4 -0
- package/dist/services/achievement/metricBalances.d.ts +42 -0
- package/dist/services/achievement/metricBalances.d.ts.map +1 -0
- package/dist/services/achievement/metricBalances.js +137 -0
- package/dist/services/achievement/reconcile.d.ts +1 -0
- package/dist/services/achievement/reconcile.d.ts.map +1 -1
- package/dist/services/achievement/reconcile.js +10 -0
- package/dist/services/achievement/recordEvent.d.ts.map +1 -1
- package/dist/services/achievement/recordEvent.js +22 -1
- package/dist/services/achievement/resolveElapsedAnchor.d.ts +15 -0
- package/dist/services/achievement/resolveElapsedAnchor.d.ts.map +1 -0
- package/dist/services/achievement/resolveElapsedAnchor.js +98 -0
- package/dist/services/achievement/resolveMetricValue.d.ts +32 -0
- package/dist/services/achievement/resolveMetricValue.d.ts.map +1 -0
- package/dist/services/achievement/resolveMetricValue.js +73 -0
- package/dist/services/achievement/validateRules.d.ts.map +1 -1
- package/dist/services/achievement/validateRules.js +19 -0
- package/dist/types.d.ts +26 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,7 +79,8 @@ Slugs are prefixed with `achievement-` by default so they don’t collide with y
|
|
|
79
79
|
|
|
80
80
|
| Config key | Default slug | Purpose |
|
|
81
81
|
| --- | --- | --- |
|
|
82
|
-
| `metrics` | `achievement-metrics` | Named
|
|
82
|
+
| `metrics` | `achievement-metrics` | Named numbers: **stored** (score totals) or **computed** (e.g. elapsed days). |
|
|
83
|
+
| `metricBalances` | `achievement-metric-balances` | Projection of stored metric totals per user (+ scope) for fast reads / leaderboards. |
|
|
83
84
|
| `eventTypes` | `achievement-event-types` | Kinds of log entries you can count or require. |
|
|
84
85
|
| `tiers` | `achievement-tiers` | Ordered ladder steps with unlock rules. |
|
|
85
86
|
| `achievements` | `achievement-definitions` | Things a user can earn (with eligibility / completion rules). |
|
|
@@ -106,7 +107,7 @@ Unlock (tiers), eligibility, and completion (achievements) share the same rule t
|
|
|
106
107
|
|
|
107
108
|
- Top-level empty list → everyone passes
|
|
108
109
|
- Nested groups → AND (“all of these”) or OR (“any of these”)
|
|
109
|
-
- Built-in leaves → `tier-at-least`, `achievement-complete`, `metric-minimum`, `event-count`
|
|
110
|
+
- Built-in leaves → `tier-at-least`, `achievement-complete`, `metric-minimum`, `event-count`, `elapsed-since`
|
|
110
111
|
- Custom leaves → register via `extensions.ruleTypes`
|
|
111
112
|
|
|
112
113
|
**Current tier** is computed by walking tiers in `rank` order and evaluating each tier’s unlock rules (`resolveCurrentTier`). Tiers with **`requiresReview`** only become current after an **approved tier request**. **Next-tier fill** uses fractional progress on the same trees (`resolveTierProgress` / `evaluateRuleProgress`):
|
|
@@ -116,9 +117,17 @@ Unlock (tiers), eligibility, and completion (achievements) share the same rule t
|
|
|
116
117
|
| AND | Equal average of *requirement* children |
|
|
117
118
|
| OR | Best child (max) |
|
|
118
119
|
| `tier-at-least` | Gate — skipped in AND averages |
|
|
119
|
-
| `event-count` / `metric-minimum` | Fraction of the target |
|
|
120
|
+
| `event-count` / `metric-minimum` / `elapsed-since` | Fraction of the target |
|
|
120
121
|
| `achievement-complete` | `1` if granted; otherwise rolls up that achievement’s completion (or eligibility) rules |
|
|
121
122
|
|
|
123
|
+
**`elapsed-since`** — time since an anchor ≥ amount (unit: `days` | `hours` | `minutes`). Built-in anchors: user `createdAt`, or earliest log of an event type (`since: 'first-event'`). Hosts register more anchors via `extensions.metricAnchors` (same keys work on computed metrics).
|
|
124
|
+
|
|
125
|
+
**Stored vs computed metrics** — `kind: stored` (default) totals live in `achievement-metric-balances`, updated by `recordMetricChange` (log + balance dual-write). Logs remain the audit/rebuild source; reconcile backfills balances. `kind: computed` + `compute: elapsed` exposes a live number (e.g. days since an anchor); use `metric-minimum` against it. System seed only creates stored `points` — product computed metrics (e.g. `days`) are host-seeded after registering anchors.
|
|
126
|
+
|
|
127
|
+
**Leaderboards** — `getMetricLeaderboard` / `GET /api/achievements/leaderboard?metric=points` ranks stored balances (authenticated). Disable with `endpoints.leaderboard: false`.
|
|
128
|
+
|
|
129
|
+
**System catalog** — by default the plugin runs `seedAchievementCatalog` on Payload `onInit` (opt out with `seedSystemCatalog: false`). Hosts still seed product catalog + locales via `seedAchievements`.
|
|
130
|
+
|
|
122
131
|
**Eligibility rules** gate who may request an achievement. **Completion rules** define what a composed achievement is made of. When `requiresReview` is false and completion rules pass, the parent can auto-grant. Cycles in nested achievement graphs are ignored.
|
|
123
132
|
|
|
124
133
|
### Access model
|
|
@@ -159,8 +168,11 @@ achievementPlugin({
|
|
|
159
168
|
overrides?: Partial<Record<AchievementCollectionKey, AchievementCollectionOverride>>
|
|
160
169
|
}
|
|
161
170
|
users?: { includeJoins?: boolean }
|
|
162
|
-
endpoints?: { me?: string | false }
|
|
163
|
-
extensions?: {
|
|
171
|
+
endpoints?: { me?: string | false; leaderboard?: string | false; reconcile?: string | false }
|
|
172
|
+
extensions?: {
|
|
173
|
+
ruleTypes?: AchievementRuleType[]
|
|
174
|
+
metricAnchors?: Record<string, AchievementMetricAnchor>
|
|
175
|
+
}
|
|
164
176
|
})
|
|
165
177
|
```
|
|
166
178
|
|
|
@@ -169,10 +181,12 @@ achievementPlugin({
|
|
|
169
181
|
| Option | Default | Notes |
|
|
170
182
|
| --- | --- | --- |
|
|
171
183
|
| `enabled` | `true` | Set `false` to leave the plugin installed but inactive (no collections / endpoints merged). |
|
|
184
|
+
| `seedSystemCatalog` | `true` | On `onInit`, upsert system metrics + event types (`points`, `metric.delta`, …). |
|
|
172
185
|
| `usersCollectionSlug` | `'users'` | Users collection the plugin relates to (grants, requests, joins). |
|
|
173
186
|
| `canReview` | _(denied)_ | **Important.** Without this, users cannot manage catalogs or approve requests. Receives `(user, scopeId)` where `scopeId` may be `null`. |
|
|
174
187
|
| `scope` | _(none)_ | Multi-tenant hook-up. `collection` is the relation target (e.g. `'tenants'`). `relationField` defaults to `'scope'`. Adds an optional scope field across plugin collections. |
|
|
175
188
|
| `mediaCollection` | _(none)_ | When set (e.g. `'media'`), tiers get an optional upload field for ladder badge images. Icon string keys still work without this. |
|
|
189
|
+
| `extensions.metricAnchors` | _(none)_ | Named `(ctx) => Date | null` resolvers for computed elapsed metrics / `elapsed-since`. Keys appear in Admin `since` selects. |
|
|
176
190
|
|
|
177
191
|
### `collections`
|
|
178
192
|
|
|
@@ -217,17 +231,25 @@ achievementPlugin({
|
|
|
217
231
|
| Option | Default | Notes |
|
|
218
232
|
| --- | --- | --- |
|
|
219
233
|
| `me` | `'/achievements/me'` | Registers `GET /api/achievements/me` for the signed-in user’s grants snapshot (+ derived tiers and lean requests). Pass `false` to skip. Having review access does **not** widen this endpoint to other users. |
|
|
234
|
+
| `leaderboard` | `'/achievements/leaderboard'` | Registers `GET /api/achievements/leaderboard?metric=<slug>` for authenticated users (stored metrics only). Pass `false` to skip. |
|
|
235
|
+
| `reconcile` | `'/achievements/reconcile'` | Registers `POST /api/achievements/reconcile` for reviewers. Pass `false` to skip. |
|
|
220
236
|
|
|
221
|
-
Query params: `scope`, `limit`, `page`, `requestsLimit`.
|
|
237
|
+
Query params (`me`): `scope`, `limit`, `page`, `requestsLimit`. Leaderboard: `metric` (required), `scope`, `limit`, `page`.
|
|
222
238
|
|
|
223
|
-
### `extensions.ruleTypes`
|
|
239
|
+
### `extensions.ruleTypes` / `metricAnchors`
|
|
224
240
|
|
|
225
|
-
Register custom rule evaluators
|
|
241
|
+
Register custom rule evaluators and/or date anchors:
|
|
226
242
|
|
|
227
243
|
```ts
|
|
228
244
|
achievementPlugin({
|
|
229
245
|
canReview,
|
|
230
246
|
extensions: {
|
|
247
|
+
metricAnchors: {
|
|
248
|
+
'membership-granted': async ({ payload, userId, scopeId }) => {
|
|
249
|
+
// return a Date from host data, or null
|
|
250
|
+
return null
|
|
251
|
+
},
|
|
252
|
+
},
|
|
231
253
|
ruleTypes: [
|
|
232
254
|
{
|
|
233
255
|
type: 'host.custom-check',
|
|
@@ -251,6 +273,8 @@ Use these from hooks, jobs, and trusted endpoints — not from the public client
|
|
|
251
273
|
import {
|
|
252
274
|
recordLog,
|
|
253
275
|
recordMetricChange,
|
|
276
|
+
getMetricLeaderboard,
|
|
277
|
+
resolveMetricValue,
|
|
254
278
|
grantAchievement,
|
|
255
279
|
reconcileProgression,
|
|
256
280
|
reconcileUserProgression,
|
|
@@ -263,6 +287,7 @@ import {
|
|
|
263
287
|
|
|
264
288
|
await recordLog({ payload, userId, scopeId, type: 'host.custom-kind', actorId })
|
|
265
289
|
await recordMetricChange({ payload, userId, scopeId, metric: 'points', change: 10 })
|
|
290
|
+
const board = await getMetricLeaderboard({ payload, metric: 'points', scopeId, limit: 20 })
|
|
266
291
|
const tier = await resolveCurrentTier({ payload, userId, scopeId })
|
|
267
292
|
|
|
268
293
|
// Repair users whose grants predate side-effect hooks (idempotent)
|
|
@@ -306,6 +331,7 @@ Turning **Requires review** off on a definition or tier **approves pending reque
|
|
|
306
331
|
| Goal | Request |
|
|
307
332
|
| --- | --- |
|
|
308
333
|
| Current user snapshot | `GET /api/achievements/me?scope=<scopeId>&limit=10&page=1` |
|
|
334
|
+
| Stored metric leaderboard | `GET /api/achievements/leaderboard?metric=points&scope=<scopeId>&limit=20&page=1` |
|
|
309
335
|
| Repair progression | `POST /api/achievements/reconcile` (reviewer; optional `{ userId, scopeId, achievementId\|achievementSlug, tierId\|tierSlug, limit }`) |
|
|
310
336
|
| List grants | `GET /api/achievement-grants?where[user][equals]=<userId>` |
|
|
311
337
|
| Log history | `GET /api/achievement-logs?where[user][equals]=<userId>` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TextFieldClientComponent } from 'payload';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Emoji-picker style Lucide field: searchable icon grid, stores kebab-case names.
|
|
4
4
|
*/
|
|
5
5
|
export declare const LucideIconField: TextFieldClientComponent;
|
|
6
6
|
//# sourceMappingURL=LucideIconField.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LucideIconField.d.ts","sourceRoot":"","sources":["../../src/admin/LucideIconField.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,
|
|
1
|
+
{"version":3,"file":"LucideIconField.d.ts","sourceRoot":"","sources":["../../src/admin/LucideIconField.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAoCvD;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,wBAsO7B,CAAA"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { FieldDescription, FieldError, FieldLabel,
|
|
3
|
+
import { FieldDescription, FieldError, FieldLabel, useField } from '@payloadcms/ui';
|
|
4
4
|
import { icons } from 'lucide-react';
|
|
5
|
-
import { useMemo } from 'react';
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
6
|
function toKebabCase(pascal) {
|
|
7
7
|
return pascal
|
|
8
8
|
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
@@ -16,15 +16,17 @@ function toPascalCase(kebab) {
|
|
|
16
16
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
17
17
|
.join('');
|
|
18
18
|
}
|
|
19
|
-
const
|
|
19
|
+
const ALL_ICONS = Object.keys(icons)
|
|
20
20
|
.filter((key) => /^[A-Z]/.test(key))
|
|
21
|
-
.map((pascal) => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
})
|
|
25
|
-
.sort((a, b) => a.
|
|
21
|
+
.map((pascal) => ({
|
|
22
|
+
name: toKebabCase(pascal),
|
|
23
|
+
Icon: icons[pascal],
|
|
24
|
+
}))
|
|
25
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
26
|
+
const MAX_VISIBLE = 180;
|
|
27
|
+
const CELL = 36;
|
|
26
28
|
/**
|
|
27
|
-
*
|
|
29
|
+
* Emoji-picker style Lucide field: searchable icon grid, stores kebab-case names.
|
|
28
30
|
*/
|
|
29
31
|
export const LucideIconField = (props) => {
|
|
30
32
|
const { field, path: pathFromProps, readOnly } = props;
|
|
@@ -33,24 +35,137 @@ export const LucideIconField = (props) => {
|
|
|
33
35
|
path,
|
|
34
36
|
});
|
|
35
37
|
const selected = typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
36
|
-
const
|
|
38
|
+
const SelectedIcon = selected
|
|
37
39
|
? icons[toPascalCase(selected)]
|
|
38
40
|
: undefined;
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
+
const [open, setOpen] = useState(false);
|
|
42
|
+
const [query, setQuery] = useState('');
|
|
43
|
+
const rootRef = useRef(null);
|
|
44
|
+
const searchRef = useRef(null);
|
|
45
|
+
const filtered = useMemo(() => {
|
|
46
|
+
const q = query.trim().toLowerCase();
|
|
47
|
+
const list = q
|
|
48
|
+
? ALL_ICONS.filter((entry) => entry.name.includes(q))
|
|
49
|
+
: ALL_ICONS;
|
|
50
|
+
return list.slice(0, MAX_VISIBLE);
|
|
51
|
+
}, [query]);
|
|
52
|
+
const totalMatches = useMemo(() => {
|
|
53
|
+
const q = query.trim().toLowerCase();
|
|
54
|
+
if (!q)
|
|
55
|
+
return ALL_ICONS.length;
|
|
56
|
+
return ALL_ICONS.filter((entry) => entry.name.includes(q)).length;
|
|
57
|
+
}, [query]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!open)
|
|
60
|
+
return;
|
|
61
|
+
const onPointerDown = (event) => {
|
|
62
|
+
if (!rootRef.current?.contains(event.target)) {
|
|
63
|
+
setOpen(false);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const onKeyDown = (event) => {
|
|
67
|
+
if (event.key === 'Escape')
|
|
68
|
+
setOpen(false);
|
|
69
|
+
};
|
|
70
|
+
document.addEventListener('mousedown', onPointerDown);
|
|
71
|
+
document.addEventListener('keydown', onKeyDown);
|
|
72
|
+
queueMicrotask(() => searchRef.current?.focus());
|
|
73
|
+
return () => {
|
|
74
|
+
document.removeEventListener('mousedown', onPointerDown);
|
|
75
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
76
|
+
};
|
|
77
|
+
}, [open]);
|
|
78
|
+
const pick = useCallback((name) => {
|
|
79
|
+
setValue(name);
|
|
80
|
+
setOpen(false);
|
|
81
|
+
setQuery('');
|
|
82
|
+
}, [setValue]);
|
|
83
|
+
const clear = useCallback(() => {
|
|
84
|
+
setValue(null);
|
|
85
|
+
setQuery('');
|
|
86
|
+
}, [setValue]);
|
|
87
|
+
return (_jsxs("div", { className: "field-type", ref: rootRef, children: [_jsx(FieldLabel, { label: field?.label ?? 'Icon', path: path, required: Boolean(field?.required) }), _jsxs("div", { style: { alignItems: 'center', display: 'flex', gap: '0.5rem', position: 'relative' }, children: [_jsxs("button", { "aria-expanded": open, "aria-haspopup": "dialog", disabled: Boolean(readOnly), onClick: () => setOpen((prev) => !prev), style: {
|
|
41
88
|
alignItems: 'center',
|
|
89
|
+
background: 'var(--theme-input-bg)',
|
|
42
90
|
border: '1px solid var(--theme-elevation-150)',
|
|
43
|
-
borderRadius:
|
|
44
|
-
|
|
45
|
-
|
|
91
|
+
borderRadius: 'var(--style-radius-s, 4px)',
|
|
92
|
+
color: 'var(--theme-text)',
|
|
93
|
+
cursor: readOnly ? 'default' : 'pointer',
|
|
94
|
+
display: 'inline-flex',
|
|
95
|
+
gap: '0.5rem',
|
|
96
|
+
minHeight: 40,
|
|
97
|
+
minWidth: 160,
|
|
98
|
+
padding: '0 0.75rem',
|
|
99
|
+
}, type: "button", children: [_jsx("span", { "aria-hidden": true, style: {
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
display: 'inline-flex',
|
|
102
|
+
height: 22,
|
|
103
|
+
justifyContent: 'center',
|
|
104
|
+
width: 22,
|
|
105
|
+
}, children: SelectedIcon ? (_jsx(SelectedIcon, { size: 18 })) : (_jsx("span", { style: { color: 'var(--theme-elevation-400)', fontSize: 12 }, children: "\u2014" })) }), _jsx("span", { style: { fontSize: 13, opacity: selected ? 1 : 0.55 }, children: selected ?? 'Choose icon…' })] }), selected && !readOnly ? (_jsx("button", { onClick: clear, style: {
|
|
106
|
+
background: 'transparent',
|
|
107
|
+
border: '1px solid var(--theme-elevation-150)',
|
|
108
|
+
borderRadius: 'var(--style-radius-s, 4px)',
|
|
109
|
+
color: 'var(--theme-elevation-600)',
|
|
110
|
+
cursor: 'pointer',
|
|
111
|
+
fontSize: 12,
|
|
46
112
|
height: 40,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
113
|
+
padding: '0 0.65rem',
|
|
114
|
+
}, type: "button", children: "Clear" })) : null, open && !readOnly ? (_jsxs("div", { role: "dialog", style: {
|
|
115
|
+
background: 'var(--theme-elevation-0)',
|
|
116
|
+
border: '1px solid var(--theme-elevation-150)',
|
|
117
|
+
borderRadius: 'var(--style-radius-m, 6px)',
|
|
118
|
+
boxShadow: '0 8px 28px rgba(0,0,0,0.18)',
|
|
119
|
+
display: 'flex',
|
|
120
|
+
flexDirection: 'column',
|
|
121
|
+
left: 0,
|
|
122
|
+
maxWidth: 'min(360px, 100vw)',
|
|
123
|
+
padding: '0.65rem',
|
|
124
|
+
position: 'absolute',
|
|
125
|
+
top: 'calc(100% + 6px)',
|
|
126
|
+
width: 360,
|
|
127
|
+
zIndex: 40,
|
|
128
|
+
}, children: [_jsx("input", { "aria-label": "Search Lucide icons", onChange: (event) => setQuery(event.target.value), placeholder: "Search icons\u2026", ref: searchRef, style: {
|
|
129
|
+
background: 'var(--theme-input-bg)',
|
|
130
|
+
border: '1px solid var(--theme-elevation-150)',
|
|
131
|
+
borderRadius: 'var(--style-radius-s, 4px)',
|
|
132
|
+
color: 'var(--theme-text)',
|
|
133
|
+
fontSize: 13,
|
|
134
|
+
marginBottom: '0.5rem',
|
|
135
|
+
padding: '0.45rem 0.6rem',
|
|
136
|
+
width: '100%',
|
|
137
|
+
}, type: "search", value: query }), _jsx("div", { style: {
|
|
138
|
+
display: 'grid',
|
|
139
|
+
gap: 4,
|
|
140
|
+
gridTemplateColumns: `repeat(auto-fill, minmax(${CELL}px, 1fr))`,
|
|
141
|
+
maxHeight: 260,
|
|
142
|
+
overflowY: 'auto',
|
|
143
|
+
paddingBottom: 4,
|
|
144
|
+
}, children: filtered.map(({ name, Icon }) => {
|
|
145
|
+
const isActive = name === selected;
|
|
146
|
+
return (_jsx("button", { onClick: () => pick(name), style: {
|
|
147
|
+
alignItems: 'center',
|
|
148
|
+
aspectRatio: '1',
|
|
149
|
+
background: isActive
|
|
150
|
+
? 'var(--theme-success-100, var(--theme-elevation-100))'
|
|
151
|
+
: 'transparent',
|
|
152
|
+
border: isActive
|
|
153
|
+
? '1px solid var(--theme-success-500, var(--theme-elevation-400))'
|
|
154
|
+
: '1px solid transparent',
|
|
155
|
+
borderRadius: 6,
|
|
156
|
+
color: 'var(--theme-text)',
|
|
157
|
+
cursor: 'pointer',
|
|
158
|
+
display: 'inline-flex',
|
|
159
|
+
justifyContent: 'center',
|
|
160
|
+
padding: 0,
|
|
161
|
+
}, title: name, type: "button", children: _jsx(Icon, { size: 18 }) }, name));
|
|
162
|
+
}) }), _jsx("div", { style: {
|
|
163
|
+
color: 'var(--theme-elevation-500)',
|
|
164
|
+
fontSize: 11,
|
|
165
|
+
marginTop: 6,
|
|
166
|
+
}, children: totalMatches === 0
|
|
167
|
+
? 'No icons match.'
|
|
168
|
+
: totalMatches > MAX_VISIBLE
|
|
169
|
+
? `Showing ${MAX_VISIBLE} of ${totalMatches} — refine search`
|
|
170
|
+
: `${totalMatches} icon${totalMatches === 1 ? '' : 's'}` })] })) : null] }), _jsx(FieldDescription, { description: field?.admin?.description, path: path }), _jsx(FieldError, { message: errorMessage, path: path, showError: showError })] }));
|
|
56
171
|
};
|
package/dist/catalog.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare const ENGINE_EVENT_TYPES: readonly [{
|
|
|
15
15
|
export declare const ENGINE_METRICS: readonly [{
|
|
16
16
|
readonly slug: "points";
|
|
17
17
|
readonly name: "Points";
|
|
18
|
+
readonly kind: "stored";
|
|
18
19
|
}];
|
|
19
20
|
export type EngineEventTypeSlug = (typeof ENGINE_EVENT_TYPES)[number]['slug'];
|
|
20
21
|
export type EngineMetricSlug = (typeof ENGINE_METRICS)[number]['slug'];
|
package/dist/catalog.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;EAKrB,CAAA;AAEV,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;EAKrB,CAAA;AAEV,eAAO,MAAM,cAAc;;;;EAAyE,CAAA;AAEpG,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;AAC7E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;AAEtE,eAAO,MAAM,+BAA+B,EAAE,gBAA2B,CAAA;AAEzE,+DAA+D;AAC/D,eAAO,MAAM,0BAA0B,UAAkC,CAAA"}
|
package/dist/catalog.js
CHANGED
|
@@ -4,7 +4,7 @@ export const ENGINE_EVENT_TYPES = [
|
|
|
4
4
|
{ slug: 'metric.delta', name: 'Metric delta', requiresMetric: true },
|
|
5
5
|
{ slug: 'tier.changed', name: 'Tier changed' },
|
|
6
6
|
];
|
|
7
|
-
export const ENGINE_METRICS = [{ slug: 'points', name: 'Points' }];
|
|
7
|
+
export const ENGINE_METRICS = [{ slug: 'points', name: 'Points', kind: 'stored' }];
|
|
8
8
|
export const DEFAULT_ACHIEVEMENT_METRIC_SLUG = 'points';
|
|
9
9
|
/** @deprecated Use {@link DEFAULT_ACHIEVEMENT_METRIC_SLUG}. */
|
|
10
10
|
export const DEFAULT_ACHIEVEMENT_METRIC = DEFAULT_ACHIEVEMENT_METRIC_SLUG;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/cli/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,SAAS,CAAA;AAE1D,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAChC,MAAM,sCAAsC,CAAA;AAE7C,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,wBAAwB,EAAE,SAAS,GAAG,KAAK,CAAC,CAAA;AA2BnF,sFAAsF;AACtF,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,mBAAmB,CAUjG;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAc5C;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;IAClD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,CAAC,EAAE,mBAAmB,CAAA;IAC7B,kFAAkF;IAClF,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,GAAG,OAAO,CAAC,0BAA0B,CAAC,
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/cli/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,SAAS,CAAA;AAE1D,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAChC,MAAM,sCAAsC,CAAA;AAE7C,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,wBAAwB,EAAE,SAAS,GAAG,KAAK,CAAC,CAAA;AA2BnF,sFAAsF;AACtF,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,mBAAmB,CAUjG;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAc5C;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;IAClD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,CAAC,EAAE,mBAAmB,CAAA;IAC7B,kFAAkF;IAClF,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA4BtC"}
|
package/dist/cli/reconcile.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/collections/MetricBalances/access/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAK/C,4EAA4E;AAC5E,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAK1D,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { requireOne } from '../../../access/index.js';
|
|
2
|
+
import { isReviewer } from '../../../access/roles/index.js';
|
|
3
|
+
/** Projection rows — writes only via trusted helpers (`overrideAccess`). */
|
|
4
|
+
export const access = {
|
|
5
|
+
read: requireOne(isReviewer('metricBalances')),
|
|
6
|
+
create: () => false,
|
|
7
|
+
update: () => false,
|
|
8
|
+
delete: requireOne(isReviewer('metricBalances')),
|
|
9
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/collections/MetricBalances/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,eAAO,MAAM,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAM,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const hooks = {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/MetricBalances/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAa/C,wBAAgB,6BAA6B,IAAI,gBAAgB,CA4ChE"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { collectionAdmin, collectionOf, columnsWithOptionalScope, optionalFields, slugOf, } from '../helpers.js';
|
|
2
|
+
import { scopeField, userField } from '../fields/index.js';
|
|
3
|
+
import { access } from './access/index.js';
|
|
4
|
+
import { hooks } from './hooks/index.js';
|
|
5
|
+
export function buildMetricBalancesCollection() {
|
|
6
|
+
return {
|
|
7
|
+
slug: slugOf('metricBalances'),
|
|
8
|
+
access: { ...access },
|
|
9
|
+
hooks,
|
|
10
|
+
admin: collectionAdmin({
|
|
11
|
+
useAsTitle: 'key',
|
|
12
|
+
defaultColumns: columnsWithOptionalScope(['metric', 'user', 'value', 'scope', 'updatedAt']),
|
|
13
|
+
description: 'Stored metric totals per user (and scope). Updated by recordMetricChange; repair with reconcile. Not for computed metrics.',
|
|
14
|
+
}),
|
|
15
|
+
fields: [
|
|
16
|
+
userField(),
|
|
17
|
+
...optionalFields(scopeField()),
|
|
18
|
+
{
|
|
19
|
+
name: 'metric',
|
|
20
|
+
type: 'relationship',
|
|
21
|
+
relationTo: collectionOf('metrics'),
|
|
22
|
+
required: true,
|
|
23
|
+
index: true,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'value',
|
|
27
|
+
type: 'number',
|
|
28
|
+
required: true,
|
|
29
|
+
defaultValue: 0,
|
|
30
|
+
index: true,
|
|
31
|
+
admin: {
|
|
32
|
+
description: 'Current total (sum of metric.delta changes).',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'key',
|
|
37
|
+
type: 'text',
|
|
38
|
+
required: true,
|
|
39
|
+
unique: true,
|
|
40
|
+
index: true,
|
|
41
|
+
admin: {
|
|
42
|
+
readOnly: true,
|
|
43
|
+
description: 'Stable unique key: user:scope:metric.',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/Metrics/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/Metrics/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAS/C,wBAAgB,sBAAsB,IAAI,gBAAgB,CA2FzD"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { collectionAdmin, columnsWithOptionalScope, optionalFields, slugOf } from '../helpers.js';
|
|
1
|
+
import { collectionAdmin, collectionOf, columnsWithOptionalScope, optionalFields, slugOf } from '../helpers.js';
|
|
2
2
|
import { localizedNameField, scopeField } from '../fields/index.js';
|
|
3
|
+
import { catalogFilterOptions } from '../fields/catalogFilter.js';
|
|
4
|
+
import { elapsedSinceSelectOptions } from '../../extensions/elapsedSinceOptions.js';
|
|
3
5
|
import { access } from './access/index.js';
|
|
4
6
|
import { hooks } from './hooks/index.js';
|
|
5
7
|
export function buildMetricsCollection() {
|
|
@@ -9,8 +11,8 @@ export function buildMetricsCollection() {
|
|
|
9
11
|
hooks,
|
|
10
12
|
admin: collectionAdmin({
|
|
11
13
|
useAsTitle: 'name',
|
|
12
|
-
defaultColumns: columnsWithOptionalScope(['name', 'slug', 'system', 'scope']),
|
|
13
|
-
description: '
|
|
14
|
+
defaultColumns: columnsWithOptionalScope(['name', 'slug', 'kind', 'system', 'scope']),
|
|
15
|
+
description: 'Named numbers. Stored metrics are score totals from log deltas; computed metrics are derived at evaluation time.',
|
|
14
16
|
}),
|
|
15
17
|
fields: [
|
|
16
18
|
...optionalFields(scopeField()),
|
|
@@ -21,6 +23,65 @@ export function buildMetricsCollection() {
|
|
|
21
23
|
{ name: 'slug', type: 'text', required: true, unique: true, index: true, admin: { width: '50%' } },
|
|
22
24
|
],
|
|
23
25
|
},
|
|
26
|
+
{
|
|
27
|
+
name: 'kind',
|
|
28
|
+
type: 'select',
|
|
29
|
+
required: true,
|
|
30
|
+
defaultValue: 'stored',
|
|
31
|
+
options: [
|
|
32
|
+
{ label: 'Stored (log deltas)', value: 'stored' },
|
|
33
|
+
{ label: 'Computed', value: 'computed' },
|
|
34
|
+
],
|
|
35
|
+
admin: {
|
|
36
|
+
description: 'Stored = sum of metric.delta logs (via balances). Computed = derived value.',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'compute',
|
|
41
|
+
type: 'select',
|
|
42
|
+
defaultValue: 'elapsed',
|
|
43
|
+
options: [{ label: 'Elapsed time', value: 'elapsed' }],
|
|
44
|
+
admin: {
|
|
45
|
+
condition: (_, sibling) => sibling?.kind === 'computed',
|
|
46
|
+
description: 'How this number is derived.',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'unit',
|
|
51
|
+
type: 'select',
|
|
52
|
+
defaultValue: 'days',
|
|
53
|
+
options: [
|
|
54
|
+
{ label: 'Days', value: 'days' },
|
|
55
|
+
{ label: 'Hours', value: 'hours' },
|
|
56
|
+
{ label: 'Minutes', value: 'minutes' },
|
|
57
|
+
],
|
|
58
|
+
admin: {
|
|
59
|
+
condition: (_, sibling) => sibling?.kind === 'computed' && sibling?.compute === 'elapsed',
|
|
60
|
+
description: 'Unit of the exposed number (e.g. whole days since the anchor).',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'since',
|
|
65
|
+
type: 'select',
|
|
66
|
+
defaultValue: 'user-created-at',
|
|
67
|
+
options: elapsedSinceSelectOptions(),
|
|
68
|
+
admin: {
|
|
69
|
+
condition: (_, sibling) => sibling?.kind === 'computed' && sibling?.compute === 'elapsed',
|
|
70
|
+
description: 'Which timestamp starts the clock.',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'eventType',
|
|
75
|
+
type: 'relationship',
|
|
76
|
+
relationTo: collectionOf('eventTypes'),
|
|
77
|
+
filterOptions: catalogFilterOptions,
|
|
78
|
+
admin: {
|
|
79
|
+
condition: (_, sibling) => sibling?.kind === 'computed' &&
|
|
80
|
+
sibling?.compute === 'elapsed' &&
|
|
81
|
+
sibling?.since === 'first-event',
|
|
82
|
+
description: 'Event type whose first log starts the clock.',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
24
85
|
{
|
|
25
86
|
name: 'system',
|
|
26
87
|
type: 'checkbox',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ruleFields.d.ts","sourceRoot":"","sources":["../../../src/collections/fields/ruleFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"ruleFields.d.ts","sourceRoot":"","sources":["../../../src/collections/fields/ruleFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAkJpC;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,KAAK,EAAE,CAqB7C;AAED,mDAAmD;AACnD,wBAAgB,kBAAkB,IAAI,KAAK,EAAE,CAE5C"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { catalogFilterOptions } from './catalogFilter.js';
|
|
2
2
|
import { collectionOf } from '../helpers.js';
|
|
3
|
+
import { elapsedSinceSelectOptions } from '../../extensions/elapsedSinceOptions.js';
|
|
3
4
|
function combinatorField(visibleWhenGroup) {
|
|
4
5
|
return {
|
|
5
6
|
name: 'combinator',
|
|
@@ -25,6 +26,7 @@ function ruleTypeSelect(includeGroup) {
|
|
|
25
26
|
{ label: 'Achievement complete', value: 'achievement-complete' },
|
|
26
27
|
{ label: 'Metric minimum', value: 'metric-minimum' },
|
|
27
28
|
{ label: 'Event count', value: 'event-count' },
|
|
29
|
+
{ label: 'Elapsed since', value: 'elapsed-since' },
|
|
28
30
|
...(includeGroup ? [{ label: 'Group', value: 'group' }] : []),
|
|
29
31
|
],
|
|
30
32
|
};
|
|
@@ -75,8 +77,9 @@ function ruleParamFields() {
|
|
|
75
77
|
relationTo: collectionOf('eventTypes'),
|
|
76
78
|
filterOptions: catalogFilterOptions,
|
|
77
79
|
admin: {
|
|
78
|
-
condition: (_, sibling) => sibling?.type === 'event-count'
|
|
79
|
-
|
|
80
|
+
condition: (_, sibling) => sibling?.type === 'event-count' ||
|
|
81
|
+
(sibling?.type === 'elapsed-since' && sibling?.since === 'first-event'),
|
|
82
|
+
description: 'Event type to count, or (for elapsed since) the type whose first log starts the clock.',
|
|
80
83
|
},
|
|
81
84
|
},
|
|
82
85
|
{
|
|
@@ -87,6 +90,46 @@ function ruleParamFields() {
|
|
|
87
90
|
description: 'How many times it needs to have happened.',
|
|
88
91
|
},
|
|
89
92
|
},
|
|
93
|
+
{
|
|
94
|
+
name: 'since',
|
|
95
|
+
type: 'select',
|
|
96
|
+
defaultValue: 'user-created-at',
|
|
97
|
+
options: elapsedSinceSelectOptions(),
|
|
98
|
+
admin: {
|
|
99
|
+
condition: (_, sibling) => sibling?.type === 'elapsed-since',
|
|
100
|
+
description: 'Which timestamp starts the clock.',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
type: 'row',
|
|
105
|
+
admin: {
|
|
106
|
+
condition: (_, sibling) => sibling?.type === 'elapsed-since',
|
|
107
|
+
},
|
|
108
|
+
fields: [
|
|
109
|
+
{
|
|
110
|
+
name: 'amount',
|
|
111
|
+
type: 'number',
|
|
112
|
+
admin: {
|
|
113
|
+
width: '50%',
|
|
114
|
+
description: 'How much time must pass.',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'unit',
|
|
119
|
+
type: 'select',
|
|
120
|
+
defaultValue: 'days',
|
|
121
|
+
options: [
|
|
122
|
+
{ label: 'Days', value: 'days' },
|
|
123
|
+
{ label: 'Hours', value: 'hours' },
|
|
124
|
+
{ label: 'Minutes', value: 'minutes' },
|
|
125
|
+
],
|
|
126
|
+
admin: {
|
|
127
|
+
width: '50%',
|
|
128
|
+
description: 'Unit for the amount.',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
90
133
|
];
|
|
91
134
|
}
|
|
92
135
|
function buildRuleLeafFields() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preventCatalogDelete.d.ts","sourceRoot":"","sources":["../../../src/collections/hooks/preventCatalogDelete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAA;AA6CzD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,IAAI,EAAE,WAAW,GAAG,QAAQ,CAAA;CAC7B,GAAG,0BAA0B,
|
|
1
|
+
{"version":3,"file":"preventCatalogDelete.d.ts","sourceRoot":"","sources":["../../../src/collections/hooks/preventCatalogDelete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAA;AA6CzD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,IAAI,EAAE,WAAW,GAAG,QAAQ,CAAA;CAC7B,GAAG,0BAA0B,CA+D7B"}
|