@nitrogenbuilder/connector-payload 0.1.33 → 0.1.35
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/collections/NitrogenComponentCatalog.d.ts +2 -0
- package/dist/collections/NitrogenComponentCatalog.js +44 -0
- package/dist/collections/NitrogenComponentUsage.d.ts +2 -0
- package/dist/collections/NitrogenComponentUsage.js +81 -0
- package/dist/components/LockedJsonField.d.ts +2 -9
- package/dist/components/LockedJsonField.js +7 -6
- package/dist/components/LockedJsonFieldRuntime.d.ts +3 -7
- package/dist/components/LockedJsonFieldRuntime.js +21 -22
- package/dist/components/NitrogenComponentInventoryClient.d.ts +13 -0
- package/dist/components/NitrogenComponentInventoryClient.js +309 -0
- package/dist/components/NitrogenComponentInventoryField.d.ts +3 -0
- package/dist/components/NitrogenComponentInventoryField.js +26 -0
- package/dist/components/NitrogenComponentInventoryNavLink.d.ts +1 -0
- package/dist/components/NitrogenComponentInventoryNavLink.js +75 -0
- package/dist/components/NitrogenComponentInventoryReindexButton.d.ts +1 -0
- package/dist/components/NitrogenComponentInventoryReindexButton.js +91 -0
- package/dist/components/NitrogenComponentInventoryView.d.ts +2 -0
- package/dist/components/NitrogenComponentInventoryView.js +28 -0
- package/dist/components/NitrogenDataViewer.d.ts +2 -2
- package/dist/components/NitrogenDataViewer.js +7 -6
- package/dist/components/NitrogenDataViewerRuntime.d.ts +6 -2
- package/dist/components/NitrogenDataViewerRuntime.js +24 -40
- package/dist/components/NitrogenNavGroup.d.ts +1 -0
- package/dist/components/NitrogenNavGroup.js +74 -0
- package/dist/editor/NitrogenEditorPage.d.ts +1 -2
- package/dist/endpoints/collection-endpoints.js +50 -5
- package/dist/endpoints/component-inventory.d.ts +6 -0
- package/dist/endpoints/component-inventory.js +20 -0
- package/dist/frontend/NitrogenWrapper.d.ts +1 -2
- package/dist/globals/NitrogenComponents.d.ts +2 -0
- package/dist/globals/NitrogenComponents.js +22 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +94 -3
- package/dist/inventory/indexing.d.ts +31 -0
- package/dist/inventory/indexing.js +230 -0
- package/dist/inventory/manifest.d.ts +3 -0
- package/dist/inventory/manifest.js +22 -0
- package/dist/types.d.ts +35 -1
- package/package.json +7 -2
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const isAuthenticated = ({ req }) => !!req.user;
|
|
2
|
+
export const NitrogenComponentCatalog = {
|
|
3
|
+
slug: 'nitrogen-component-catalog',
|
|
4
|
+
admin: {
|
|
5
|
+
hidden: true,
|
|
6
|
+
useAsTitle: 'componentName',
|
|
7
|
+
defaultColumns: ['componentName', 'sidebarCategory', 'scope', 'updatedAt'],
|
|
8
|
+
group: 'Nitrogen',
|
|
9
|
+
},
|
|
10
|
+
access: {
|
|
11
|
+
read: isAuthenticated,
|
|
12
|
+
create: isAuthenticated,
|
|
13
|
+
update: isAuthenticated,
|
|
14
|
+
delete: isAuthenticated,
|
|
15
|
+
},
|
|
16
|
+
fields: [
|
|
17
|
+
{
|
|
18
|
+
name: 'componentName',
|
|
19
|
+
type: 'text',
|
|
20
|
+
required: true,
|
|
21
|
+
unique: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'scope',
|
|
25
|
+
type: 'text',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'sidebarCategory',
|
|
29
|
+
type: 'text',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'icon',
|
|
33
|
+
type: 'text',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'definition',
|
|
37
|
+
type: 'json',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'source',
|
|
41
|
+
type: 'text',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const isAuthenticated = ({ req }) => !!req.user;
|
|
2
|
+
export const NitrogenComponentUsage = {
|
|
3
|
+
slug: 'nitrogen-component-usage',
|
|
4
|
+
admin: {
|
|
5
|
+
hidden: true,
|
|
6
|
+
useAsTitle: 'usageKey',
|
|
7
|
+
defaultColumns: ['componentName', 'sourceCollection', 'sourceTitle', 'updatedAt'],
|
|
8
|
+
group: 'Nitrogen',
|
|
9
|
+
},
|
|
10
|
+
access: {
|
|
11
|
+
read: isAuthenticated,
|
|
12
|
+
create: isAuthenticated,
|
|
13
|
+
update: isAuthenticated,
|
|
14
|
+
delete: isAuthenticated,
|
|
15
|
+
},
|
|
16
|
+
fields: [
|
|
17
|
+
{
|
|
18
|
+
name: 'usageKey',
|
|
19
|
+
type: 'text',
|
|
20
|
+
required: true,
|
|
21
|
+
unique: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'componentName',
|
|
25
|
+
type: 'text',
|
|
26
|
+
required: true,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'depth',
|
|
30
|
+
type: 'number',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'sourceCollection',
|
|
34
|
+
type: 'text',
|
|
35
|
+
required: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'sourceType',
|
|
39
|
+
type: 'text',
|
|
40
|
+
required: true,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'sourceDocumentId',
|
|
44
|
+
type: 'text',
|
|
45
|
+
required: true,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'sourceTitle',
|
|
49
|
+
type: 'text',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'sourceSlug',
|
|
53
|
+
type: 'text',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'sourceStatus',
|
|
57
|
+
type: 'text',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'moduleId',
|
|
61
|
+
type: 'text',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'modulePath',
|
|
65
|
+
type: 'text',
|
|
66
|
+
required: true,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'parentComponentName',
|
|
70
|
+
type: 'text',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'parentModuleId',
|
|
74
|
+
type: 'text',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'parentModulePath',
|
|
78
|
+
type: 'text',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
field: {
|
|
4
|
-
name: string;
|
|
5
|
-
admin?: {
|
|
6
|
-
description?: string;
|
|
7
|
-
};
|
|
8
|
-
};
|
|
9
|
-
}>;
|
|
1
|
+
import type { JSONFieldServerComponent } from 'payload';
|
|
2
|
+
export declare const LockedJsonField: JSONFieldServerComponent;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
export const LockedJsonField =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
});
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { LockedJsonFieldRuntime } from './LockedJsonFieldRuntime';
|
|
3
|
+
export const LockedJsonField = ({ field, value }) => {
|
|
4
|
+
const label = typeof field.label === 'string' ? field.label : field.name;
|
|
5
|
+
const description = typeof field.admin?.description === 'string' ? field.admin.description : undefined;
|
|
6
|
+
return (_jsx(LockedJsonFieldRuntime, { description: description, label: label, value: value }));
|
|
7
|
+
};
|
|
@@ -1,38 +1,37 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
import { useMemo, useState } from 'react';
|
|
4
|
+
export const LockedJsonFieldRuntime = ({ description, label, value }) => {
|
|
5
|
+
const [copied, setCopied] = useState(false);
|
|
6
|
+
const displayValue = useMemo(() => {
|
|
7
|
+
if (typeof value === 'string') {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
return value != null ? JSON.stringify(value, null, 2) : '';
|
|
11
|
+
}, [value]);
|
|
12
|
+
return (_jsxs("div", { style: { marginBottom: '1.5rem' }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '0.5rem' }, children: [_jsx("label", { style: { fontWeight: 500 }, children: label }), _jsx("button", { type: "button", onClick: () => {
|
|
13
|
+
navigator.clipboard.writeText(displayValue).then(() => {
|
|
14
|
+
setCopied(true);
|
|
15
|
+
setTimeout(() => setCopied(false), 2000);
|
|
16
|
+
});
|
|
17
|
+
}, style: {
|
|
10
18
|
padding: '4px 12px',
|
|
11
19
|
fontSize: '12px',
|
|
12
|
-
background:
|
|
20
|
+
background: '#6b7280',
|
|
13
21
|
color: '#fff',
|
|
14
22
|
border: 'none',
|
|
15
23
|
borderRadius: '4px',
|
|
16
24
|
cursor: 'pointer',
|
|
17
|
-
}, children:
|
|
18
|
-
if (!unlocked)
|
|
19
|
-
return;
|
|
20
|
-
try {
|
|
21
|
-
setValue(JSON.parse(e.target.value));
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
// Allow typing invalid JSON while editing
|
|
25
|
-
}
|
|
26
|
-
}, style: {
|
|
25
|
+
}, children: copied ? 'Copied' : 'Copy' })] }), _jsx("textarea", { value: displayValue, readOnly: true, style: {
|
|
27
26
|
width: '100%',
|
|
28
27
|
minHeight: '120px',
|
|
29
28
|
fontFamily: 'monospace',
|
|
30
29
|
fontSize: '12px',
|
|
31
30
|
padding: '8px',
|
|
32
|
-
background:
|
|
33
|
-
color:
|
|
34
|
-
border:
|
|
31
|
+
background: '#111',
|
|
32
|
+
color: '#888',
|
|
33
|
+
border: '1px solid #333',
|
|
35
34
|
borderRadius: '4px',
|
|
36
35
|
resize: 'vertical',
|
|
37
|
-
} }),
|
|
36
|
+
} }), description && (_jsx("p", { style: { fontSize: '12px', color: '#888', marginTop: '4px' }, children: description }))] }));
|
|
38
37
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComponentManifestEntry } from '@nitrogenbuilder/types';
|
|
2
|
+
import type { NitrogenComponentUsageDoc } from '../types';
|
|
3
|
+
type InventoryClientProps = {
|
|
4
|
+
adminRoute: string;
|
|
5
|
+
components: Array<{
|
|
6
|
+
componentName: string;
|
|
7
|
+
definition?: ComponentManifestEntry;
|
|
8
|
+
isUnknown: boolean;
|
|
9
|
+
usages: NitrogenComponentUsageDoc[];
|
|
10
|
+
}>;
|
|
11
|
+
};
|
|
12
|
+
export default function NitrogenComponentInventoryClient({ adminRoute, components, }: InventoryClientProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { formatAdminURL } from 'payload/shared';
|
|
5
|
+
function formatSourceLabel(slug) {
|
|
6
|
+
if (slug === 'nitrogen-templates') {
|
|
7
|
+
return 'Nitrogen Templates';
|
|
8
|
+
}
|
|
9
|
+
return slug
|
|
10
|
+
.split(/[-_]/g)
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
13
|
+
.join(' ');
|
|
14
|
+
}
|
|
15
|
+
function renderProp(name, prop, depth = 0) {
|
|
16
|
+
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
|
+
}
|
|
18
|
+
function renderGroup(group) {
|
|
19
|
+
return (_jsxs("div", { style: { marginTop: 12 }, children: [_jsx("h4", { style: { margin: '0 0 6px', fontSize: 14 }, children: group.label }), _jsx("div", { style: { display: 'grid', gap: 6 }, children: Object.entries(group.props).map(([propName, prop]) => renderProp(propName, prop)) })] }, group.key));
|
|
20
|
+
}
|
|
21
|
+
function renderCategory(category) {
|
|
22
|
+
return (_jsxs("div", { style: { marginTop: 16 }, children: [_jsx("h3", { style: { margin: '0 0 8px', fontSize: 16 }, children: category.label }), Object.values(category.groups).map((group) => renderGroup(group))] }, category.key));
|
|
23
|
+
}
|
|
24
|
+
function buttonStyle(kind) {
|
|
25
|
+
return {
|
|
26
|
+
appearance: 'none',
|
|
27
|
+
background: kind === 'primary' ? '#111827' : '#ffffff',
|
|
28
|
+
border: `1px solid ${kind === 'primary' ? '#111827' : '#d1d5db'}`,
|
|
29
|
+
borderRadius: 8,
|
|
30
|
+
color: kind === 'primary' ? '#ffffff' : '#111827',
|
|
31
|
+
cursor: 'pointer',
|
|
32
|
+
fontSize: 13,
|
|
33
|
+
fontWeight: 600,
|
|
34
|
+
padding: '8px 12px',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
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, }) {
|
|
76
|
+
const sourceFilterRef = useRef(null);
|
|
77
|
+
const allComponentNames = useMemo(() => components.map(({ componentName }) => componentName), [components]);
|
|
78
|
+
const [searchValue, setSearchValue] = useState('');
|
|
79
|
+
const [sourceSearchValue, setSourceSearchValue] = useState('');
|
|
80
|
+
const [selectedSources, setSelectedSources] = useState([]);
|
|
81
|
+
const [openNames, setOpenNames] = useState({});
|
|
82
|
+
const sourceOptions = useMemo(() => {
|
|
83
|
+
const collectionOptions = new Map();
|
|
84
|
+
const documentOptions = new Map();
|
|
85
|
+
components.forEach(({ usages }) => {
|
|
86
|
+
usages.forEach((usage) => {
|
|
87
|
+
const collectionLabel = formatSourceLabel(usage.sourceCollection);
|
|
88
|
+
const collectionKey = `collection:${usage.sourceCollection}`;
|
|
89
|
+
const documentLabel = usage.sourceTitle || usage.sourceSlug || usage.sourceDocumentId;
|
|
90
|
+
const documentKey = `document:${usage.sourceCollection}:${usage.sourceDocumentId}`;
|
|
91
|
+
if (!collectionOptions.has(collectionKey)) {
|
|
92
|
+
collectionOptions.set(collectionKey, {
|
|
93
|
+
description: usage.sourceCollection,
|
|
94
|
+
key: collectionKey,
|
|
95
|
+
kind: 'collection',
|
|
96
|
+
label: collectionLabel,
|
|
97
|
+
searchValue: `${collectionLabel} ${usage.sourceCollection}`.toLowerCase(),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (!documentOptions.has(documentKey)) {
|
|
101
|
+
documentOptions.set(documentKey, {
|
|
102
|
+
description: `${collectionLabel} (${usage.sourceCollection})`,
|
|
103
|
+
key: documentKey,
|
|
104
|
+
kind: 'document',
|
|
105
|
+
label: documentLabel,
|
|
106
|
+
searchValue: `${documentLabel} ${collectionLabel} ${usage.sourceCollection} ${usage.sourceSlug || ''}`.toLowerCase(),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
return [
|
|
112
|
+
...Array.from(collectionOptions.values()).sort((a, b) => a.label.localeCompare(b.label)),
|
|
113
|
+
...Array.from(documentOptions.values()).sort((a, b) => a.label.localeCompare(b.label)),
|
|
114
|
+
];
|
|
115
|
+
}, [components]);
|
|
116
|
+
const visibleSourceOptions = useMemo(() => {
|
|
117
|
+
const normalizedQuery = sourceSearchValue.trim().toLowerCase();
|
|
118
|
+
if (!normalizedQuery) {
|
|
119
|
+
return sourceOptions;
|
|
120
|
+
}
|
|
121
|
+
return sourceOptions.filter((option) => option.searchValue.includes(normalizedQuery));
|
|
122
|
+
}, [sourceOptions, sourceSearchValue]);
|
|
123
|
+
const sourceFilteredComponents = useMemo(() => {
|
|
124
|
+
const hasSourceFilter = selectedSources.length > 0;
|
|
125
|
+
return components
|
|
126
|
+
.map((component) => {
|
|
127
|
+
const filteredUsages = hasSourceFilter
|
|
128
|
+
? component.usages.filter((usage) => {
|
|
129
|
+
const collectionKey = `collection:${usage.sourceCollection}`;
|
|
130
|
+
const documentKey = `document:${usage.sourceCollection}:${usage.sourceDocumentId}`;
|
|
131
|
+
return selectedSources.includes(collectionKey) || selectedSources.includes(documentKey);
|
|
132
|
+
})
|
|
133
|
+
: component.usages;
|
|
134
|
+
return {
|
|
135
|
+
...component,
|
|
136
|
+
usages: filteredUsages,
|
|
137
|
+
};
|
|
138
|
+
})
|
|
139
|
+
.filter((component) => {
|
|
140
|
+
return !hasSourceFilter || component.usages.length > 0;
|
|
141
|
+
});
|
|
142
|
+
}, [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
|
+
const filteredComponents = useMemo(() => {
|
|
153
|
+
const normalizedQuery = searchValue.trim().toLowerCase();
|
|
154
|
+
return sourceFilteredComponents.filter((component) => {
|
|
155
|
+
return !normalizedQuery || component.componentName.toLowerCase().includes(normalizedQuery);
|
|
156
|
+
});
|
|
157
|
+
}, [searchValue, sourceFilteredComponents]);
|
|
158
|
+
const filteredComponentNames = useMemo(() => filteredComponents.map(({ componentName }) => componentName), [filteredComponents]);
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
function handlePointerDown(event) {
|
|
161
|
+
const details = sourceFilterRef.current;
|
|
162
|
+
const target = event.target;
|
|
163
|
+
if (!(target instanceof Node) || !details?.open || details.contains(target)) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
details.open = false;
|
|
167
|
+
}
|
|
168
|
+
document.addEventListener('mousedown', handlePointerDown);
|
|
169
|
+
return () => {
|
|
170
|
+
document.removeEventListener('mousedown', handlePointerDown);
|
|
171
|
+
};
|
|
172
|
+
}, []);
|
|
173
|
+
const expandAll = () => {
|
|
174
|
+
setOpenNames((current) => ({
|
|
175
|
+
...current,
|
|
176
|
+
...Object.fromEntries(filteredComponentNames.map((name) => [name, true])),
|
|
177
|
+
}));
|
|
178
|
+
};
|
|
179
|
+
const collapseAll = () => {
|
|
180
|
+
if (!filteredComponentNames.length) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
setOpenNames((current) => {
|
|
184
|
+
const next = { ...current };
|
|
185
|
+
filteredComponentNames.forEach((name) => {
|
|
186
|
+
delete next[name];
|
|
187
|
+
});
|
|
188
|
+
return next;
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
const selectedSourceLabels = useMemo(() => sourceOptions
|
|
192
|
+
.filter((option) => selectedSources.includes(option.key))
|
|
193
|
+
.map((option) => option.label), [selectedSources, sourceOptions]);
|
|
194
|
+
const sourceFilterLabel = selectedSourceLabels.length === 0
|
|
195
|
+
? 'All Sources'
|
|
196
|
+
: selectedSourceLabels.length === 1
|
|
197
|
+
? selectedSourceLabels[0]
|
|
198
|
+
: `${selectedSourceLabels.length} sources`;
|
|
199
|
+
return (_jsxs("div", { style: { display: 'grid', gap: 16 }, children: [_jsxs("div", { style: {
|
|
200
|
+
alignItems: 'center',
|
|
201
|
+
display: 'flex',
|
|
202
|
+
flexWrap: 'wrap',
|
|
203
|
+
gap: 8,
|
|
204
|
+
justifyContent: 'space-between',
|
|
205
|
+
}, children: [_jsxs("div", { style: { color: '#4b5563', fontSize: 13 }, children: [filteredComponents.length, " component", filteredComponents.length === 1 ? '' : 's', filteredComponents.length !== allComponentNames.length
|
|
206
|
+
? ` of ${allComponentNames.length}`
|
|
207
|
+
: ''] }), _jsxs("div", { style: { alignItems: 'center', display: 'flex', flexWrap: 'wrap', gap: 8 }, children: [_jsx("input", { onChange: (event) => setSearchValue(event.target.value), placeholder: "Search components...", style: {
|
|
208
|
+
background: '#ffffff',
|
|
209
|
+
border: '1px solid #d1d5db',
|
|
210
|
+
borderRadius: 8,
|
|
211
|
+
color: '#111827',
|
|
212
|
+
fontSize: 13,
|
|
213
|
+
minWidth: 240,
|
|
214
|
+
padding: '8px 12px',
|
|
215
|
+
}, type: "search", value: searchValue }), _jsxs("details", { ref: sourceFilterRef, style: { position: 'relative' }, children: [_jsxs("summary", { style: {
|
|
216
|
+
alignItems: 'center',
|
|
217
|
+
appearance: 'none',
|
|
218
|
+
background: '#ffffff',
|
|
219
|
+
border: '1px solid #d1d5db',
|
|
220
|
+
borderRadius: 8,
|
|
221
|
+
color: '#111827',
|
|
222
|
+
cursor: 'pointer',
|
|
223
|
+
display: 'flex',
|
|
224
|
+
fontSize: 13,
|
|
225
|
+
gap: 8,
|
|
226
|
+
listStyle: 'none',
|
|
227
|
+
minWidth: 220,
|
|
228
|
+
padding: '8px 12px',
|
|
229
|
+
userSelect: 'none',
|
|
230
|
+
}, children: [_jsx("span", { children: "Sources:" }), _jsx("span", { style: { color: '#4b5563', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: sourceFilterLabel }), _jsx("span", { style: { color: '#6b7280', marginLeft: 'auto' }, children: "\u25BE" })] }), _jsx("div", { style: {
|
|
231
|
+
background: '#ffffff',
|
|
232
|
+
border: '1px solid #d1d5db',
|
|
233
|
+
borderRadius: 10,
|
|
234
|
+
boxShadow: '0 12px 32px rgba(17, 24, 39, 0.12)',
|
|
235
|
+
marginTop: 8,
|
|
236
|
+
padding: 12,
|
|
237
|
+
position: 'absolute',
|
|
238
|
+
right: 0,
|
|
239
|
+
width: 320,
|
|
240
|
+
zIndex: 20,
|
|
241
|
+
}, children: _jsxs("div", { style: { display: 'grid', gap: 10 }, children: [_jsx("input", { onChange: (event) => setSourceSearchValue(event.target.value), placeholder: "Search collections or items...", style: {
|
|
242
|
+
background: '#ffffff',
|
|
243
|
+
border: '1px solid #d1d5db',
|
|
244
|
+
borderRadius: 8,
|
|
245
|
+
color: '#111827',
|
|
246
|
+
fontSize: 13,
|
|
247
|
+
padding: '8px 12px',
|
|
248
|
+
width: '100%',
|
|
249
|
+
}, type: "search", value: sourceSearchValue }), _jsxs("div", { style: { display: 'flex', gap: 8 }, children: [_jsx("button", { onClick: () => setSelectedSources(sourceOptions.map((option) => option.key)), style: buttonStyle('secondary'), type: "button", children: "Select All" }), _jsx("button", { onClick: () => setSelectedSources([]), style: buttonStyle('secondary'), type: "button", children: "Clear" })] }), _jsx("div", { style: { display: 'grid', gap: 8, maxHeight: 220, overflow: 'auto' }, children: visibleSourceOptions.length ? (visibleSourceOptions.map((option) => {
|
|
250
|
+
const checked = selectedSources.includes(option.key);
|
|
251
|
+
return (_jsxs("label", { style: {
|
|
252
|
+
cursor: 'pointer',
|
|
253
|
+
display: 'grid',
|
|
254
|
+
gap: 10,
|
|
255
|
+
fontSize: 13,
|
|
256
|
+
gridTemplateColumns: 'auto 1fr',
|
|
257
|
+
}, children: [_jsx("input", { checked: checked, onChange: () => setSelectedSources((current) => checked
|
|
258
|
+
? current.filter((value) => value !== option.key)
|
|
259
|
+
: [...current, option.key]), style: { marginTop: 2 }, type: "checkbox" }), _jsxs("div", { style: { display: 'grid', gap: 2 }, children: [_jsxs("div", { style: { alignItems: 'center', display: 'flex', gap: 8 }, children: [_jsx("span", { children: option.label }), _jsx("span", { style: {
|
|
260
|
+
background: option.kind === 'collection' ? '#eef2ff' : '#f3f4f6',
|
|
261
|
+
borderRadius: 999,
|
|
262
|
+
color: option.kind === 'collection' ? '#3730a3' : '#4b5563',
|
|
263
|
+
fontSize: 11,
|
|
264
|
+
fontWeight: 700,
|
|
265
|
+
padding: '2px 8px',
|
|
266
|
+
textTransform: 'uppercase',
|
|
267
|
+
}, children: option.kind === 'collection' ? 'Collection' : 'Item' })] }), _jsx("span", { style: { color: '#6b7280', fontSize: 12 }, children: option.description })] })] }, option.key));
|
|
268
|
+
})) : (_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
|
+
const isOpen = Boolean(openNames[componentName]);
|
|
270
|
+
const usedInside = buildRelationshipSummaries(usages, (usage) => usage.parentComponentName);
|
|
271
|
+
const contains = containsByComponent.get(componentName) ?? [];
|
|
272
|
+
return (_jsxs("section", { style: {
|
|
273
|
+
background: '#fff',
|
|
274
|
+
border: '1px solid #e5e7eb',
|
|
275
|
+
borderRadius: 10,
|
|
276
|
+
overflow: 'hidden',
|
|
277
|
+
}, children: [_jsxs("button", { onClick: () => setOpenNames((current) => ({
|
|
278
|
+
...current,
|
|
279
|
+
[componentName]: !current[componentName],
|
|
280
|
+
})), style: {
|
|
281
|
+
alignItems: 'center',
|
|
282
|
+
appearance: 'none',
|
|
283
|
+
background: 'transparent',
|
|
284
|
+
border: 0,
|
|
285
|
+
cursor: 'pointer',
|
|
286
|
+
display: 'flex',
|
|
287
|
+
gap: 12,
|
|
288
|
+
justifyContent: 'space-between',
|
|
289
|
+
padding: 16,
|
|
290
|
+
textAlign: 'left',
|
|
291
|
+
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: {
|
|
293
|
+
borderTop: '1px solid #e5e7eb',
|
|
294
|
+
display: 'grid',
|
|
295
|
+
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',
|
|
304
|
+
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));
|
|
308
|
+
})] }));
|
|
309
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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';
|
|
5
|
+
const NitrogenComponentInventoryField = async ({ payload, req, }) => {
|
|
6
|
+
const [catalogDocs, usageDocs] = await Promise.all([
|
|
7
|
+
findAllDocs(payload, NITROGEN_COMPONENT_CATALOG_COLLECTION),
|
|
8
|
+
findAllDocs(payload, NITROGEN_COMPONENT_USAGE_COLLECTION),
|
|
9
|
+
]);
|
|
10
|
+
const catalogByName = new Map(catalogDocs.map((doc) => [doc.componentName, doc.definition]));
|
|
11
|
+
const usageByName = new Map();
|
|
12
|
+
for (const usageDoc of usageDocs) {
|
|
13
|
+
const docs = usageByName.get(usageDoc.componentName) || [];
|
|
14
|
+
docs.push(usageDoc);
|
|
15
|
+
usageByName.set(usageDoc.componentName, docs);
|
|
16
|
+
}
|
|
17
|
+
const componentNames = Array.from(new Set([...catalogByName.keys(), ...usageByName.keys()])).sort((a, b) => a.localeCompare(b));
|
|
18
|
+
const components = componentNames.map((componentName) => ({
|
|
19
|
+
componentName,
|
|
20
|
+
definition: catalogByName.get(componentName),
|
|
21
|
+
isUnknown: !catalogByName.get(componentName),
|
|
22
|
+
usages: usageByName.get(componentName) || [],
|
|
23
|
+
}));
|
|
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 })] }));
|
|
25
|
+
};
|
|
26
|
+
export default NitrogenComponentInventoryField;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function NitrogenComponentInventoryNavLink(): null;
|