@nitrogenbuilder/connector-payload 0.1.39 → 0.1.41

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.
Files changed (33) hide show
  1. package/dist/components/LockedJsonField.js +1 -1
  2. package/dist/components/NitrogenComponentInventoryClient.d.ts +3 -2
  3. package/dist/components/NitrogenComponentInventoryClient.js +370 -63
  4. package/dist/components/NitrogenComponentInventoryField.js +15 -5
  5. package/dist/components/NitrogenComponentInventoryView.js +15 -5
  6. package/dist/components/NitrogenDataViewer.js +1 -1
  7. package/dist/components/NitrogenEditButton.js +2 -2
  8. package/dist/components/NitrogenEditButtonRuntime.js +21 -5
  9. package/dist/components/NitrogenViewButton.js +2 -2
  10. package/dist/components/NitrogenViewButtonRuntime.js +7 -3
  11. package/dist/editor/NitrogenEditorPage.js +12 -29
  12. package/dist/editor/index.d.ts +3 -3
  13. package/dist/editor/index.js +3 -3
  14. package/dist/endpoints/all.js +2 -2
  15. package/dist/endpoints/batch.js +2 -2
  16. package/dist/endpoints/collection-endpoints.js +1 -1
  17. package/dist/endpoints/component-inventory.d.ts +1 -1
  18. package/dist/endpoints/component-inventory.js +275 -2
  19. package/dist/endpoints/helpers.d.ts +5 -5
  20. package/dist/endpoints/helpers.js +1 -1
  21. package/dist/endpoints/media.js +1 -1
  22. package/dist/endpoints/nitrogen-settings.js +1 -1
  23. package/dist/endpoints/templates.js +2 -2
  24. package/dist/frontend/NitrogenWrapper.js +2 -2
  25. package/dist/frontend/index.d.ts +5 -5
  26. package/dist/frontend/index.js +3 -3
  27. package/dist/globals/NitrogenSettings.js +46 -11
  28. package/dist/index.d.ts +7 -7
  29. package/dist/index.js +22 -22
  30. package/dist/inventory/indexing.d.ts +2 -2
  31. package/dist/inventory/indexing.js +1 -1
  32. package/dist/types.d.ts +6 -1
  33. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { LockedJsonFieldRuntime } from './LockedJsonFieldRuntime';
2
+ import { LockedJsonFieldRuntime } from './LockedJsonFieldRuntime.js';
3
3
  export const LockedJsonField = ({ field, value }) => {
4
4
  const label = typeof field.label === 'string' ? field.label : field.name;
5
5
  const description = typeof field.admin?.description === 'string' ? field.admin.description : undefined;
@@ -1,5 +1,5 @@
1
1
  import type { ComponentManifestEntry } from '@nitrogenbuilder/types';
2
- import type { NitrogenComponentUsageDoc } from '../types';
2
+ import type { NitrogenComponentUsageDoc } from '../types.js';
3
3
  type InventoryClientProps = {
4
4
  adminRoute: string;
5
5
  components: Array<{
@@ -8,6 +8,7 @@ type InventoryClientProps = {
8
8
  isUnknown: boolean;
9
9
  usages: NitrogenComponentUsageDoc[];
10
10
  }>;
11
+ previewUrl: string;
11
12
  };
12
- export default function NitrogenComponentInventoryClient({ adminRoute, components, }: InventoryClientProps): import("react/jsx-runtime").JSX.Element;
13
+ export default function NitrogenComponentInventoryClient({ adminRoute, components, previewUrl, }: InventoryClientProps): import("react/jsx-runtime").JSX.Element;
13
14
  export {};
@@ -1,7 +1,19 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { formatAdminURL } from 'payload/shared';
5
+ const EDITABLE_PROP_TYPES = new Set([
6
+ 'boolean',
7
+ 'color',
8
+ 'color-select',
9
+ 'color-swatch',
10
+ 'enum',
11
+ 'enum-inline',
12
+ 'number',
13
+ 'string',
14
+ 'url',
15
+ 'wysiwyg',
16
+ ]);
5
17
  function formatSourceLabel(slug) {
6
18
  if (slug === 'nitrogen-templates') {
7
19
  return 'Nitrogen Templates';
@@ -12,6 +24,86 @@ function formatSourceLabel(slug) {
12
24
  .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
13
25
  .join(' ');
14
26
  }
27
+ function getPreviewTargetOrigin(previewUrl) {
28
+ try {
29
+ return new URL(previewUrl).origin;
30
+ }
31
+ catch {
32
+ return '*';
33
+ }
34
+ }
35
+ function normalizeManifestOptions(options) {
36
+ if (!options?.length) {
37
+ return undefined;
38
+ }
39
+ return options.map((option) => {
40
+ if (typeof option === 'string') {
41
+ return {
42
+ label: option,
43
+ value: option,
44
+ };
45
+ }
46
+ return {
47
+ label: option.label || option.value,
48
+ value: option.value,
49
+ };
50
+ });
51
+ }
52
+ function isEditableProp(prop) {
53
+ return EDITABLE_PROP_TYPES.has(prop.type);
54
+ }
55
+ function collectEditablePropOptions(definition) {
56
+ if (!definition) {
57
+ return [];
58
+ }
59
+ const options = [];
60
+ Object.values(definition.categories).forEach((category) => {
61
+ Object.values(category.groups).forEach((group) => {
62
+ Object.entries(group.props).forEach(([propName, prop]) => {
63
+ if (!isEditableProp(prop)) {
64
+ return;
65
+ }
66
+ options.push({
67
+ defaultValue: prop.default,
68
+ label: `${category.label} / ${group.label} / ${prop.label || propName}`,
69
+ options: normalizeManifestOptions(prop.options),
70
+ path: `${group.key}.${propName}`,
71
+ type: prop.type,
72
+ });
73
+ });
74
+ });
75
+ });
76
+ return options;
77
+ }
78
+ function stringifyPropValue(value, propType) {
79
+ if (propType === 'boolean') {
80
+ return Boolean(value);
81
+ }
82
+ if (typeof value === 'string') {
83
+ return value;
84
+ }
85
+ if (typeof value === 'number') {
86
+ return String(value);
87
+ }
88
+ return '';
89
+ }
90
+ function coercePropValue(value, propType) {
91
+ if (propType === 'boolean') {
92
+ return Boolean(value);
93
+ }
94
+ if (propType === 'number') {
95
+ const parsed = Number(value);
96
+ return Number.isFinite(parsed) ? parsed : 0;
97
+ }
98
+ return String(value);
99
+ }
100
+ function getDefaultDraft(option) {
101
+ return {
102
+ propPath: option?.path || '',
103
+ status: 'idle',
104
+ value: option ? stringifyPropValue(option.defaultValue, option.type) : '',
105
+ };
106
+ }
15
107
  function renderProp(name, prop, depth = 0) {
16
108
  return (_jsxs("div", { style: { paddingLeft: depth * 16 }, children: [_jsxs("div", { style: { fontFamily: 'monospace', fontSize: 12, lineHeight: 1.5 }, children: [_jsx("strong", { children: name }), " ", _jsxs("span", { style: { color: '#6b7280' }, children: ["(", prop.type, ")"] }), prop.label ? _jsxs("span", { style: { color: '#6b7280' }, children: [" - ", prop.label] }) : null] }), prop.props ? (_jsx("div", { style: { marginTop: 4 }, children: Object.entries(prop.props).map(([childName, childProp]) => renderProp(childName, childProp, depth + 1)) })) : null] }, `${name}-${depth}`));
17
109
  }
@@ -34,51 +126,22 @@ function buttonStyle(kind) {
34
126
  padding: '8px 12px',
35
127
  };
36
128
  }
37
- function buildRelationshipSummaries(usages, getRelationshipName) {
38
- const relationshipMap = new Map();
39
- usages.forEach((usage) => {
40
- const relationshipName = getRelationshipName(usage);
41
- if (!relationshipName) {
42
- return;
43
- }
44
- const entry = relationshipMap.get(relationshipName) ?? {
45
- documentKeys: new Set(),
46
- occurrenceCount: 0,
47
- };
48
- entry.occurrenceCount += 1;
49
- entry.documentKeys.add(`${usage.sourceCollection}:${usage.sourceDocumentId}`);
50
- relationshipMap.set(relationshipName, entry);
51
- });
52
- return Array.from(relationshipMap.entries())
53
- .map(([componentName, entry]) => ({
54
- componentName,
55
- documentCount: entry.documentKeys.size,
56
- occurrenceCount: entry.occurrenceCount,
57
- }))
58
- .sort((a, b) => {
59
- if (b.documentCount !== a.documentCount) {
60
- return b.documentCount - a.documentCount;
61
- }
62
- if (b.occurrenceCount !== a.occurrenceCount) {
63
- return b.occurrenceCount - a.occurrenceCount;
64
- }
65
- return a.componentName.localeCompare(b.componentName);
66
- });
67
- }
68
- function renderRelationshipList(title, summaries, emptyMessage) {
69
- return (_jsxs("div", { style: { marginTop: 16 }, children: [_jsx("h3", { style: { margin: '0 0 10px', fontSize: 15 }, children: title }), summaries.length ? (_jsx("div", { style: { display: 'grid', gap: 8 }, children: summaries.map((summary) => (_jsxs("div", { style: {
70
- border: '1px solid #e5e7eb',
71
- borderRadius: 6,
72
- padding: 12,
73
- }, children: [_jsx("div", { style: { fontWeight: 600 }, children: summary.componentName }), _jsxs("div", { style: { color: '#6b7280', fontSize: 12, marginTop: 2 }, children: [summary.documentCount, " doc", summary.documentCount === 1 ? '' : 's', " \u00B7", ' ', summary.occurrenceCount, " occurrence", summary.occurrenceCount === 1 ? '' : 's'] })] }, `${title}-${summary.componentName}`))) })) : (_jsx("p", { style: { color: '#6b7280', margin: 0 }, children: emptyMessage }))] }));
74
- }
75
- export default function NitrogenComponentInventoryClient({ adminRoute, components, }) {
129
+ export default function NitrogenComponentInventoryClient({ adminRoute, components, previewUrl, }) {
76
130
  const sourceFilterRef = useRef(null);
131
+ const previewFrameRefs = useRef({});
77
132
  const allComponentNames = useMemo(() => components.map(({ componentName }) => componentName), [components]);
78
133
  const [searchValue, setSearchValue] = useState('');
79
134
  const [sourceSearchValue, setSourceSearchValue] = useState('');
80
135
  const [selectedSources, setSelectedSources] = useState([]);
81
136
  const [openNames, setOpenNames] = useState({});
137
+ const [previewStates, setPreviewStates] = useState({});
138
+ const [propUpdateDrafts, setPropUpdateDrafts] = useState({});
139
+ const editablePropOptionsByComponent = useMemo(() => {
140
+ return new Map(components.map((component) => [
141
+ component.componentName,
142
+ collectEditablePropOptions(component.definition),
143
+ ]));
144
+ }, [components]);
82
145
  const sourceOptions = useMemo(() => {
83
146
  const collectionOptions = new Map();
84
147
  const documentOptions = new Map();
@@ -140,15 +203,6 @@ export default function NitrogenComponentInventoryClient({ adminRoute, component
140
203
  return !hasSourceFilter || component.usages.length > 0;
141
204
  });
142
205
  }, [components, selectedSources]);
143
- const allSourceFilteredUsages = useMemo(() => sourceFilteredComponents.flatMap((component) => component.usages), [sourceFilteredComponents]);
144
- const containsByComponent = useMemo(() => {
145
- const componentMap = new Map();
146
- sourceFilteredComponents.forEach(({ componentName }) => {
147
- const summaries = buildRelationshipSummaries(allSourceFilteredUsages.filter((usage) => usage.parentComponentName === componentName), (usage) => usage.componentName);
148
- componentMap.set(componentName, summaries);
149
- });
150
- return componentMap;
151
- }, [allSourceFilteredUsages, sourceFilteredComponents]);
152
206
  const filteredComponents = useMemo(() => {
153
207
  const normalizedQuery = searchValue.trim().toLowerCase();
154
208
  return sourceFilteredComponents.filter((component) => {
@@ -170,6 +224,22 @@ export default function NitrogenComponentInventoryClient({ adminRoute, component
170
224
  document.removeEventListener('mousedown', handlePointerDown);
171
225
  };
172
226
  }, []);
227
+ useEffect(() => {
228
+ setPropUpdateDrafts((current) => {
229
+ const next = { ...current };
230
+ Object.entries(next).forEach(([componentName, draft]) => {
231
+ if (!draft.result) {
232
+ return;
233
+ }
234
+ next[componentName] = {
235
+ ...draft,
236
+ result: undefined,
237
+ status: 'idle',
238
+ };
239
+ });
240
+ return next;
241
+ });
242
+ }, [selectedSources]);
173
243
  const expandAll = () => {
174
244
  setOpenNames((current) => ({
175
245
  ...current,
@@ -196,6 +266,154 @@ export default function NitrogenComponentInventoryClient({ adminRoute, component
196
266
  : selectedSourceLabels.length === 1
197
267
  ? selectedSourceLabels[0]
198
268
  : `${selectedSourceLabels.length} sources`;
269
+ const postPreviewDataToFrame = useCallback((componentName, data) => {
270
+ if (!previewUrl || !data) {
271
+ return;
272
+ }
273
+ const frameWindow = previewFrameRefs.current[componentName]?.contentWindow;
274
+ if (!frameWindow) {
275
+ return;
276
+ }
277
+ frameWindow.postMessage({
278
+ componentName,
279
+ payload: data,
280
+ type: 'nitrogen-component-preview',
281
+ }, getPreviewTargetOrigin(previewUrl));
282
+ }, [previewUrl]);
283
+ const loadPreviewData = useCallback(async (componentName, usages) => {
284
+ if (!previewUrl) {
285
+ return;
286
+ }
287
+ setPreviewStates((current) => {
288
+ if (current[componentName]?.status === 'loading' || current[componentName]?.data) {
289
+ return current;
290
+ }
291
+ return {
292
+ ...current,
293
+ [componentName]: {
294
+ status: 'loading',
295
+ },
296
+ };
297
+ });
298
+ const previewUsage = usages[0];
299
+ const params = new URLSearchParams({
300
+ componentName,
301
+ });
302
+ if (previewUsage) {
303
+ params.set('sourceCollection', previewUsage.sourceCollection);
304
+ params.set('sourceDocumentId', previewUsage.sourceDocumentId);
305
+ if (previewUsage.moduleId) {
306
+ params.set('moduleId', previewUsage.moduleId);
307
+ }
308
+ }
309
+ try {
310
+ const response = await fetch(`/api/nitrogen/v1/component-inventory/preview-data?${params}`);
311
+ const data = (await response.json());
312
+ if (!response.ok || !('success' in data)) {
313
+ throw new Error('error' in data && data.error ? data.error : 'Failed to load preview data.');
314
+ }
315
+ setPreviewStates((current) => ({
316
+ ...current,
317
+ [componentName]: {
318
+ data,
319
+ status: 'loaded',
320
+ },
321
+ }));
322
+ window.setTimeout(() => postPreviewDataToFrame(componentName, data), 0);
323
+ }
324
+ catch (error) {
325
+ setPreviewStates((current) => ({
326
+ ...current,
327
+ [componentName]: {
328
+ error: error instanceof Error ? error.message : 'Failed to load preview data.',
329
+ status: 'error',
330
+ },
331
+ }));
332
+ }
333
+ }, [postPreviewDataToFrame, previewUrl]);
334
+ useEffect(() => {
335
+ filteredComponents.forEach((component) => {
336
+ if (openNames[component.componentName]) {
337
+ void loadPreviewData(component.componentName, component.usages);
338
+ }
339
+ });
340
+ }, [filteredComponents, loadPreviewData, openNames]);
341
+ useEffect(() => {
342
+ function handlePreviewReady(event) {
343
+ if (!previewUrl || event.origin !== getPreviewTargetOrigin(previewUrl)) {
344
+ return;
345
+ }
346
+ const data = event.data;
347
+ if (data.type !== 'nitrogen-component-preview-ready' || typeof data.componentName !== 'string') {
348
+ return;
349
+ }
350
+ postPreviewDataToFrame(data.componentName, previewStates[data.componentName]?.data);
351
+ }
352
+ window.addEventListener('message', handlePreviewReady);
353
+ return () => {
354
+ window.removeEventListener('message', handlePreviewReady);
355
+ };
356
+ }, [postPreviewDataToFrame, previewStates, previewUrl]);
357
+ function updatePropDraft(componentName, nextDraft) {
358
+ setPropUpdateDrafts((current) => ({
359
+ ...current,
360
+ [componentName]: {
361
+ ...(current[componentName] || getDefaultDraft()),
362
+ ...nextDraft,
363
+ },
364
+ }));
365
+ }
366
+ async function runPropUpdate(componentName, editableProps, mode, usages) {
367
+ const draft = propUpdateDrafts[componentName] || getDefaultDraft(editableProps[0]);
368
+ const selectedProp = editableProps.find((option) => option.path === draft.propPath);
369
+ if (!selectedProp) {
370
+ updatePropDraft(componentName, {
371
+ error: 'Select a supported prop first.',
372
+ status: 'error',
373
+ });
374
+ return;
375
+ }
376
+ updatePropDraft(componentName, {
377
+ error: undefined,
378
+ status: mode === 'apply' ? 'applying' : 'previewing',
379
+ });
380
+ try {
381
+ const response = await fetch(`/api/nitrogen/v1/component-inventory/prop-update/${mode}`, {
382
+ body: JSON.stringify({
383
+ componentName,
384
+ propPath: selectedProp.path,
385
+ sourceFilters: selectedSources,
386
+ value: coercePropValue(draft.value, selectedProp.type),
387
+ }),
388
+ headers: {
389
+ 'Content-Type': 'application/json',
390
+ },
391
+ method: 'POST',
392
+ });
393
+ const result = (await response.json());
394
+ if (!response.ok || !('success' in result)) {
395
+ throw new Error('error' in result && result.error ? result.error : 'Prop update failed.');
396
+ }
397
+ updatePropDraft(componentName, {
398
+ result,
399
+ status: mode === 'apply' ? 'applied' : 'ready',
400
+ });
401
+ if (mode === 'apply') {
402
+ setPreviewStates((current) => {
403
+ const next = { ...current };
404
+ delete next[componentName];
405
+ return next;
406
+ });
407
+ void loadPreviewData(componentName, usages);
408
+ }
409
+ }
410
+ catch (error) {
411
+ updatePropDraft(componentName, {
412
+ error: error instanceof Error ? error.message : 'Prop update failed.',
413
+ status: 'error',
414
+ });
415
+ }
416
+ }
199
417
  return (_jsxs("div", { style: { display: 'grid', gap: 16 }, children: [_jsxs("div", { style: {
200
418
  alignItems: 'center',
201
419
  display: 'flex',
@@ -267,8 +485,13 @@ export default function NitrogenComponentInventoryClient({ adminRoute, component
267
485
  }, children: option.kind === 'collection' ? 'Collection' : 'Item' })] }), _jsx("span", { style: { color: '#6b7280', fontSize: 12 }, children: option.description })] })] }, option.key));
268
486
  })) : (_jsx("p", { style: { color: '#6b7280', fontSize: 13, margin: 0 }, children: "No sources match." })) })] }) })] }), _jsx("button", { onClick: expandAll, style: buttonStyle('secondary'), type: "button", children: "Expand All" }), _jsx("button", { onClick: collapseAll, style: buttonStyle('secondary'), type: "button", children: "Collapse All" })] })] }), filteredComponents.map(({ componentName, definition, isUnknown, usages }) => {
269
487
  const isOpen = Boolean(openNames[componentName]);
270
- const usedInside = buildRelationshipSummaries(usages, (usage) => usage.parentComponentName);
271
- const contains = containsByComponent.get(componentName) ?? [];
488
+ const editableProps = editablePropOptionsByComponent.get(componentName) || [];
489
+ const propDraft = propUpdateDrafts[componentName] || getDefaultDraft(editableProps[0]);
490
+ const selectedEditableProp = editableProps.find((option) => option.path === propDraft.propPath);
491
+ const previewState = previewStates[componentName] || { status: 'idle' };
492
+ const componentPreviewUrl = previewUrl
493
+ ? `${previewUrl}${previewUrl.includes('?') ? '&' : '?'}componentName=${encodeURIComponent(componentName)}`
494
+ : '';
272
495
  return (_jsxs("section", { style: {
273
496
  background: '#fff',
274
497
  border: '1px solid #e5e7eb',
@@ -289,21 +512,105 @@ export default function NitrogenComponentInventoryClient({ adminRoute, component
289
512
  padding: 16,
290
513
  textAlign: 'left',
291
514
  width: '100%',
292
- }, type: "button", children: [_jsxs("div", { style: { display: 'grid', gap: 4 }, children: [_jsxs("div", { style: { alignItems: 'center', display: 'flex', flexWrap: 'wrap', gap: 8 }, children: [_jsx("span", { style: { fontSize: 16, fontWeight: 700 }, children: componentName }), _jsxs("span", { style: { color: '#6b7280', fontSize: 13, fontWeight: 500 }, children: [usages.length, " usage", usages.length === 1 ? '' : 's'] }), contains.length ? (_jsxs("span", { style: { color: '#6b7280', fontSize: 13, fontWeight: 500 }, children: ["Contains ", contains.length, " component", contains.length === 1 ? '' : 's'] })) : null, isUnknown ? (_jsx("span", { style: { color: '#b45309', fontSize: 13, fontWeight: 600 }, children: "Missing from manifest" })) : null] }), definition ? (_jsxs("div", { style: { color: '#6b7280', fontSize: 13 }, children: [definition.sidebarCategory || 'Uncategorized', definition.scope ? ` - ${definition.scope}` : ''] })) : null] }), _jsx("span", { style: { color: '#6b7280', fontSize: 18, lineHeight: 1 }, children: isOpen ? '−' : '+' })] }), isOpen ? (_jsxs("div", { style: {
515
+ }, type: "button", children: [_jsxs("div", { style: { display: 'grid', gap: 4 }, children: [_jsxs("div", { style: { alignItems: 'center', display: 'flex', flexWrap: 'wrap', gap: 8 }, children: [_jsx("span", { style: { fontSize: 16, fontWeight: 700 }, children: componentName }), _jsxs("span", { style: { color: '#6b7280', fontSize: 13, fontWeight: 500 }, children: [usages.length, " usage", usages.length === 1 ? '' : 's'] }), isUnknown ? (_jsx("span", { style: { color: '#b45309', fontSize: 13, fontWeight: 600 }, children: "Missing from manifest" })) : null] }), definition ? (_jsxs("div", { style: { color: '#6b7280', fontSize: 13 }, children: [definition.sidebarCategory || 'Uncategorized', definition.scope ? ` - ${definition.scope}` : ''] })) : null] }), _jsx("span", { style: { color: '#6b7280', fontSize: 18, lineHeight: 1 }, children: isOpen ? '−' : '+' })] }), isOpen ? (_jsxs("div", { style: {
293
516
  borderTop: '1px solid #e5e7eb',
294
517
  display: 'grid',
295
518
  gap: 0,
296
- gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
297
- }, children: [_jsxs("div", { style: { borderRight: '1px solid #e5e7eb', padding: 16 }, children: [_jsx("h3", { style: { margin: '0 0 10px', fontSize: 15 }, children: "Used On" }), usages.length ? (_jsx("div", { style: { display: 'grid', gap: 8 }, children: usages.map((usage) => (_jsxs("a", { href: formatAdminURL({
298
- adminRoute,
299
- path: `/collections/${usage.sourceCollection}/${usage.sourceDocumentId}`,
300
- }), style: {
301
- border: '1px solid #e5e7eb',
302
- borderRadius: 6,
303
- color: 'inherit',
519
+ gridTemplateColumns: 'minmax(0, 1.45fr) minmax(300px, 0.9fr)',
520
+ }, children: [_jsxs("div", { style: {
521
+ borderRight: '1px solid #e5e7eb',
522
+ display: 'grid',
523
+ gap: 18,
524
+ padding: 16,
525
+ }, children: [_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 10px', fontSize: 15 }, children: "Used On" }), usages.length ? (_jsx("div", { style: { display: 'grid', gap: 8 }, children: usages.map((usage) => (_jsxs("a", { href: formatAdminURL({
526
+ adminRoute,
527
+ path: `/collections/${usage.sourceCollection}/${usage.sourceDocumentId}`,
528
+ }), style: {
529
+ border: '1px solid #e5e7eb',
530
+ borderRadius: 6,
531
+ color: 'inherit',
532
+ display: 'block',
533
+ padding: 12,
534
+ textDecoration: 'none',
535
+ }, children: [_jsx("div", { style: { fontWeight: 600 }, children: usage.sourceTitle || usage.sourceSlug || usage.sourceDocumentId }), _jsxs("div", { style: { color: '#6b7280', fontSize: 12, marginTop: 2 }, children: [usage.sourceCollection, " - ", usage.modulePath] })] }, usage.usageKey))) })) : (_jsx("p", { style: { color: '#6b7280', margin: 0 }, children: "No indexed usages yet." }))] }), _jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 10px', fontSize: 15 }, children: "Props" }), definition ? (Object.values(definition.categories).map((category) => renderCategory(category))) : (_jsx("p", { style: { color: '#6b7280', margin: 0 }, children: "No manifest definition available." }))] }), _jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 10px', fontSize: 15 }, children: "Update Prop Across Site" }), editableProps.length ? (_jsxs("div", { style: {
536
+ border: '1px solid #e5e7eb',
537
+ borderRadius: 8,
538
+ display: 'grid',
539
+ gap: 10,
540
+ padding: 12,
541
+ }, children: [_jsxs("label", { style: { display: 'grid', gap: 6, fontSize: 13, fontWeight: 600 }, children: ["Prop", _jsx("select", { onChange: (event) => {
542
+ const nextProp = editableProps.find((option) => option.path === event.target.value);
543
+ updatePropDraft(componentName, {
544
+ error: undefined,
545
+ propPath: event.target.value,
546
+ result: undefined,
547
+ status: 'idle',
548
+ value: nextProp
549
+ ? stringifyPropValue(nextProp.defaultValue, nextProp.type)
550
+ : '',
551
+ });
552
+ }, style: {
553
+ border: '1px solid #d1d5db',
554
+ borderRadius: 6,
555
+ color: '#111827',
556
+ maxWidth: 520,
557
+ padding: '8px 10px',
558
+ width: '100%',
559
+ }, value: propDraft.propPath, children: editableProps.map((option) => (_jsx("option", { value: option.path, children: option.label }, option.path))) })] }), selectedEditableProp?.type === 'boolean' ? (_jsxs("label", { style: { alignItems: 'center', display: 'flex', gap: 8, fontSize: 13 }, children: [_jsx("input", { checked: Boolean(propDraft.value), onChange: (event) => updatePropDraft(componentName, {
560
+ result: undefined,
561
+ status: 'idle',
562
+ value: event.target.checked,
563
+ }), type: "checkbox" }), "Value"] })) : selectedEditableProp?.options?.length ? (_jsxs("label", { style: { display: 'grid', gap: 6, fontSize: 13, fontWeight: 600 }, children: ["Value", _jsx("select", { onChange: (event) => updatePropDraft(componentName, {
564
+ result: undefined,
565
+ status: 'idle',
566
+ value: event.target.value,
567
+ }), style: {
568
+ border: '1px solid #d1d5db',
569
+ borderRadius: 6,
570
+ color: '#111827',
571
+ maxWidth: 520,
572
+ padding: '8px 10px',
573
+ width: '100%',
574
+ }, value: String(propDraft.value), children: selectedEditableProp.options.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value))) })] })) : (_jsxs("label", { style: { display: 'grid', gap: 6, fontSize: 13, fontWeight: 600 }, children: ["Value", _jsx("input", { onChange: (event) => updatePropDraft(componentName, {
575
+ result: undefined,
576
+ status: 'idle',
577
+ value: event.target.value,
578
+ }), style: {
579
+ border: '1px solid #d1d5db',
580
+ borderRadius: 6,
581
+ color: '#111827',
582
+ maxWidth: 520,
583
+ padding: '8px 10px',
584
+ width: '100%',
585
+ }, type: selectedEditableProp?.type === 'number' ? 'number' : 'text', value: String(propDraft.value) })] })), _jsxs("div", { style: { color: '#6b7280', fontSize: 12 }, children: ["Scope: ", selectedSources.length ? sourceFilterLabel : 'all indexed usages'] }), _jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 8 }, children: [_jsx("button", { disabled: propDraft.status === 'previewing' || propDraft.status === 'applying', onClick: () => void runPropUpdate(componentName, editableProps, 'preview', usages), style: buttonStyle('secondary'), type: "button", children: propDraft.status === 'previewing' ? 'Checking...' : 'Dry Run' }), _jsx("button", { disabled: propDraft.status === 'previewing' ||
586
+ propDraft.status === 'applying' ||
587
+ !propDraft.result, onClick: () => void runPropUpdate(componentName, editableProps, 'apply', usages), style: {
588
+ ...buttonStyle('primary'),
589
+ opacity: propDraft.result ? 1 : 0.5,
590
+ }, type: "button", children: propDraft.status === 'applying' ? 'Applying...' : 'Apply Update' })] }), propDraft.error ? (_jsx("p", { style: { color: '#b91c1c', fontSize: 13, margin: 0 }, children: propDraft.error })) : null, propDraft.result ? (_jsxs("div", { style: { color: '#374151', fontSize: 13 }, children: [propDraft.result.mode === 'apply' ? 'Updated' : 'Would update', ' ', propDraft.result.occurrenceCount, " occurrence", propDraft.result.occurrenceCount === 1 ? '' : 's', " in", ' ', propDraft.result.documentCount, " document", propDraft.result.documentCount === 1 ? '' : 's', "."] })) : null] })) : (_jsx("p", { style: { color: '#6b7280', margin: 0 }, children: "No scalar props are available for bulk updates." }))] })] }), _jsxs("div", { style: { padding: 16 }, children: [_jsxs("div", { style: {
591
+ alignItems: 'center',
592
+ display: 'flex',
593
+ gap: 12,
594
+ justifyContent: 'space-between',
595
+ marginBottom: 10,
596
+ }, children: [_jsxs("div", { children: [_jsx("h3", { style: { margin: 0, fontSize: 15 }, children: "Preview" }), previewState.data?.source ? (_jsxs("div", { style: { color: '#6b7280', fontSize: 12, marginTop: 2 }, children: ["Rendering saved usage from ", previewState.data.source.title || previewState.data.source.documentId] })) : null] }), previewState.status === 'loading' ? (_jsx("span", { style: { color: '#6b7280', fontSize: 12 }, children: "Loading preview..." })) : null] }), !previewUrl ? (_jsx("p", { style: { color: '#6b7280', margin: 0 }, children: "Configure a Nitrogen development or frontend URL to enable component previews." })) : previewState.status === 'error' ? (_jsx("p", { style: { color: '#b91c1c', margin: 0 }, children: previewState.error })) : (_jsx("div", { style: {
597
+ background: '#f9fafb',
598
+ border: '1px solid #e5e7eb',
599
+ borderRadius: 8,
600
+ overflow: 'hidden',
601
+ position: 'relative',
602
+ width: '100%',
603
+ aspectRatio: '4 / 3',
604
+ }, children: _jsx("iframe", { ref: (element) => {
605
+ previewFrameRefs.current[componentName] = element;
606
+ }, onLoad: () => postPreviewDataToFrame(componentName, previewState.data), src: componentPreviewUrl, style: {
607
+ border: 0,
304
608
  display: 'block',
305
- padding: 12,
306
- textDecoration: 'none',
307
- }, children: [_jsx("div", { style: { fontWeight: 600 }, children: usage.sourceTitle || usage.sourceSlug || usage.sourceDocumentId }), _jsxs("div", { style: { color: '#6b7280', fontSize: 12, marginTop: 2 }, children: [usage.sourceCollection, " - ", usage.modulePath] })] }, usage.usageKey))) })) : (_jsx("p", { style: { color: '#6b7280', margin: 0 }, children: "No indexed usages yet." })), renderRelationshipList('Used Inside', usedInside, 'This component is not indexed inside another component.')] }), _jsxs("div", { style: { padding: 16 }, children: [renderRelationshipList('Contains', contains, 'No nested child components were indexed for this component.'), _jsx("h3", { style: { margin: '16px 0 10px', fontSize: 15 }, children: "Props" }), definition ? (Object.values(definition.categories).map((category) => renderCategory(category))) : (_jsx("p", { style: { color: '#6b7280', margin: 0 }, children: "No manifest definition available." }))] })] })) : null] }, componentName));
609
+ height: '100%',
610
+ left: 0,
611
+ position: 'absolute',
612
+ top: 0,
613
+ width: '100%',
614
+ }, title: `${componentName} preview` }) }))] })] })) : null] }, componentName));
308
615
  })] }));
309
616
  }
@@ -1,11 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { findAllDocs, NITROGEN_COMPONENT_CATALOG_COLLECTION, NITROGEN_COMPONENT_USAGE_COLLECTION, } from '../inventory/indexing';
3
- import NitrogenComponentInventoryClient from './NitrogenComponentInventoryClient';
4
- import NitrogenComponentInventoryReindexButton from './NitrogenComponentInventoryReindexButton';
2
+ import { findAllDocs, NITROGEN_COMPONENT_CATALOG_COLLECTION, NITROGEN_COMPONENT_USAGE_COLLECTION, } from '../inventory/indexing.js';
3
+ import { getNitrogenSettings } from '../endpoints/helpers.js';
4
+ import NitrogenComponentInventoryClient from './NitrogenComponentInventoryClient.js';
5
+ import NitrogenComponentInventoryReindexButton from './NitrogenComponentInventoryReindexButton.js';
6
+ function buildComponentPreviewUrl(settings) {
7
+ const frontendBaseUrl = String(settings.frontendDevUrl || settings.developmentUrl || settings.frontendUrl || '').replace(/\/$/, '');
8
+ if (!frontendBaseUrl) {
9
+ return '';
10
+ }
11
+ const url = new URL('/nitrogen-component-preview', frontendBaseUrl);
12
+ return url.toString();
13
+ }
5
14
  const NitrogenComponentInventoryField = async ({ payload, req, }) => {
6
- const [catalogDocs, usageDocs] = await Promise.all([
15
+ const [catalogDocs, usageDocs, nitrogenSettings] = await Promise.all([
7
16
  findAllDocs(payload, NITROGEN_COMPONENT_CATALOG_COLLECTION),
8
17
  findAllDocs(payload, NITROGEN_COMPONENT_USAGE_COLLECTION),
18
+ getNitrogenSettings(payload).catch(() => null),
9
19
  ]);
10
20
  const catalogByName = new Map(catalogDocs.map((doc) => [doc.componentName, doc.definition]));
11
21
  const usageByName = new Map();
@@ -21,6 +31,6 @@ const NitrogenComponentInventoryField = async ({ payload, req, }) => {
21
31
  isUnknown: !catalogByName.get(componentName),
22
32
  usages: usageByName.get(componentName) || [],
23
33
  }));
24
- return (_jsxs("div", { style: { display: 'grid', gap: 24, paddingBottom: 24 }, children: [_jsxs("div", { style: { display: 'grid', gap: 12 }, children: [_jsx("p", { style: { margin: 0, color: '#4b5563', maxWidth: 920 }, children: "Inventory view for the registered Nitrogen component manifest and all indexed usages across Nitrogen-enabled documents and templates." }), _jsx(NitrogenComponentInventoryReindexButton, {})] }), _jsx(NitrogenComponentInventoryClient, { adminRoute: req.payload.config.routes.admin, components: components })] }));
34
+ return (_jsxs("div", { style: { display: 'grid', gap: 24, paddingBottom: 24 }, children: [_jsxs("div", { style: { display: 'grid', gap: 12 }, children: [_jsx("p", { style: { margin: 0, color: '#4b5563', maxWidth: 920 }, children: "Inventory view for the registered Nitrogen component manifest and all indexed usages across Nitrogen-enabled documents and templates." }), _jsx(NitrogenComponentInventoryReindexButton, {})] }), _jsx(NitrogenComponentInventoryClient, { adminRoute: req.payload.config.routes.admin, components: components, previewUrl: nitrogenSettings ? buildComponentPreviewUrl(nitrogenSettings) : '' })] }));
25
35
  };
26
36
  export default NitrogenComponentInventoryField;
@@ -1,14 +1,24 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // @ts-expect-error The consuming Payload app provides @payloadcms/next at runtime.
3
3
  import { DefaultTemplate } from '@payloadcms/next/templates';
4
- import { findAllDocs, NITROGEN_COMPONENT_CATALOG_COLLECTION, NITROGEN_COMPONENT_USAGE_COLLECTION, } from '../inventory/indexing';
5
- import NitrogenComponentInventoryClient from './NitrogenComponentInventoryClient';
6
- import NitrogenComponentInventoryReindexButton from './NitrogenComponentInventoryReindexButton';
4
+ import { findAllDocs, NITROGEN_COMPONENT_CATALOG_COLLECTION, NITROGEN_COMPONENT_USAGE_COLLECTION, } from '../inventory/indexing.js';
5
+ import { getNitrogenSettings } from '../endpoints/helpers.js';
6
+ import NitrogenComponentInventoryClient from './NitrogenComponentInventoryClient.js';
7
+ import NitrogenComponentInventoryReindexButton from './NitrogenComponentInventoryReindexButton.js';
8
+ function buildComponentPreviewUrl(settings) {
9
+ const frontendBaseUrl = String(settings.frontendDevUrl || settings.developmentUrl || settings.frontendUrl || '').replace(/\/$/, '');
10
+ if (!frontendBaseUrl) {
11
+ return '';
12
+ }
13
+ const url = new URL('/nitrogen-component-preview', frontendBaseUrl);
14
+ return url.toString();
15
+ }
7
16
  export default async function NitrogenComponentInventoryView({ clientConfig, initPageResult, i18n, locale, params, searchParams, viewActions, viewType, }) {
8
17
  const payload = initPageResult.req.payload;
9
- const [catalogDocs, usageDocs] = await Promise.all([
18
+ const [catalogDocs, usageDocs, nitrogenSettings] = await Promise.all([
10
19
  findAllDocs(payload, NITROGEN_COMPONENT_CATALOG_COLLECTION),
11
20
  findAllDocs(payload, NITROGEN_COMPONENT_USAGE_COLLECTION),
21
+ getNitrogenSettings(payload).catch(() => null),
12
22
  ]);
13
23
  const catalogByName = new Map(catalogDocs.map((doc) => [doc.componentName, doc.definition]));
14
24
  const usageByName = new Map();
@@ -24,5 +34,5 @@ export default async function NitrogenComponentInventoryView({ clientConfig, ini
24
34
  isUnknown: !catalogByName.get(componentName),
25
35
  usages: usageByName.get(componentName) || [],
26
36
  }));
27
- return (_jsx(DefaultTemplate, { i18n: i18n, locale: locale, params: params, payload: payload, req: initPageResult.req, searchParams: searchParams, user: initPageResult.req.user, viewActions: viewActions, viewType: viewType, visibleEntities: initPageResult.visibleEntities, children: _jsxs("div", { style: { padding: 24 }, children: [_jsxs("div", { style: { display: 'grid', gap: 12, marginBottom: 24 }, children: [_jsx("h1", { style: { margin: 0, fontSize: 28 }, children: "Nitrogen Components" }), _jsx("p", { style: { margin: 0, color: '#4b5563', maxWidth: 920 }, children: "Inventory view for the registered Nitrogen component manifest and all indexed usages across Nitrogen-enabled documents and templates." }), _jsx(NitrogenComponentInventoryReindexButton, {})] }), _jsx(NitrogenComponentInventoryClient, { adminRoute: clientConfig.routes.admin, components: components })] }) }));
37
+ return (_jsx(DefaultTemplate, { i18n: i18n, locale: locale, params: params, payload: payload, req: initPageResult.req, searchParams: searchParams, user: initPageResult.req.user, viewActions: viewActions, viewType: viewType, visibleEntities: initPageResult.visibleEntities, children: _jsxs("div", { style: { padding: 24 }, children: [_jsxs("div", { style: { display: 'grid', gap: 12, marginBottom: 24 }, children: [_jsx("h1", { style: { margin: 0, fontSize: 28 }, children: "Nitrogen Components" }), _jsx("p", { style: { margin: 0, color: '#4b5563', maxWidth: 920 }, children: "Inventory view for the registered Nitrogen component manifest and all indexed usages across Nitrogen-enabled documents and templates." }), _jsx(NitrogenComponentInventoryReindexButton, {})] }), _jsx(NitrogenComponentInventoryClient, { adminRoute: clientConfig.routes.admin, components: components, previewUrl: nitrogenSettings ? buildComponentPreviewUrl(nitrogenSettings) : '' })] }) }));
28
38
  }
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import NitrogenDataViewerRuntime from './NitrogenDataViewerRuntime';
2
+ import NitrogenDataViewerRuntime from './NitrogenDataViewerRuntime.js';
3
3
  const NitrogenDataViewer = ({ field, path, value }) => {
4
4
  const label = typeof field.label === 'string' ? field.label : field.name;
5
5
  const description = typeof field.admin?.description === 'string' ? field.admin.description : undefined;
@@ -1,6 +1,6 @@
1
1
  'use client';
2
- import dynamic from 'next/dynamic';
3
- export const NitrogenEditButton = dynamic(() => import('./NitrogenEditButtonRuntime').then((module) => module.NitrogenEditButtonRuntime), {
2
+ import dynamic from 'next/dynamic.js';
3
+ export const NitrogenEditButton = dynamic(() => import('./NitrogenEditButtonRuntime.js').then((module) => module.NitrogenEditButtonRuntime), {
4
4
  loading: () => null,
5
5
  ssr: false,
6
6
  });