@nitrogenbuilder/connector-payload 0.1.34 → 0.1.36
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 +7 -2
- package/dist/components/NitrogenDataViewerRuntime.js +171 -93
- 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/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,75 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { usePathname } from 'next/navigation';
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
const groupSelector = '#nav-group-Nitrogen .nav-group__content';
|
|
5
|
+
export default function NitrogenComponentInventoryNavLink() {
|
|
6
|
+
const pathname = usePathname();
|
|
7
|
+
const isActive = pathname === '/admin/nitrogen/components' || pathname?.startsWith('/admin/nitrogen/components/');
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const linkId = 'nitrogen-components-nav-link';
|
|
10
|
+
const renderLink = () => {
|
|
11
|
+
const target = document.querySelector(groupSelector);
|
|
12
|
+
if (!target) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const existing = document.getElementById(linkId);
|
|
16
|
+
if (existing) {
|
|
17
|
+
existing.remove();
|
|
18
|
+
}
|
|
19
|
+
const link = document.createElement('a');
|
|
20
|
+
link.id = linkId;
|
|
21
|
+
link.href = '/admin/nitrogen/components';
|
|
22
|
+
link.textContent = 'Nitrogen Components';
|
|
23
|
+
link.style.alignItems = 'center';
|
|
24
|
+
link.style.color = 'inherit';
|
|
25
|
+
link.style.display = 'flex';
|
|
26
|
+
link.style.fontSize = '18px';
|
|
27
|
+
link.style.fontWeight = isActive ? '700' : '500';
|
|
28
|
+
link.style.gap = '12px';
|
|
29
|
+
link.style.minHeight = '40px';
|
|
30
|
+
link.style.opacity = isActive ? '1' : '0.92';
|
|
31
|
+
link.style.padding = '6px 0';
|
|
32
|
+
link.style.textDecoration = 'none';
|
|
33
|
+
const icon = document.createElement('span');
|
|
34
|
+
icon.ariaHidden = 'true';
|
|
35
|
+
icon.style.color = '#6b7280';
|
|
36
|
+
icon.style.display = 'inline-flex';
|
|
37
|
+
icon.style.fontSize = '16px';
|
|
38
|
+
icon.style.justifyContent = 'center';
|
|
39
|
+
icon.style.width = '20px';
|
|
40
|
+
icon.innerHTML = `
|
|
41
|
+
<svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg">
|
|
42
|
+
<path
|
|
43
|
+
d="M4 5.5C4 4.67157 4.67157 4 5.5 4H14.5C15.3284 4 16 4.67157 16 5.5V14.5C16 15.3284 15.3284 16 14.5 16H5.5C4.67157 16 4 15.3284 4 14.5V5.5Z"
|
|
44
|
+
stroke="currentColor"
|
|
45
|
+
stroke-width="1.5"
|
|
46
|
+
/>
|
|
47
|
+
<path d="M7 8H13" stroke="currentColor" stroke-linecap="round" stroke-width="1.5" />
|
|
48
|
+
<path d="M7 10.5H13" stroke="currentColor" stroke-linecap="round" stroke-width="1.5" />
|
|
49
|
+
<path d="M7 13H10.5" stroke="currentColor" stroke-linecap="round" stroke-width="1.5" />
|
|
50
|
+
</svg>
|
|
51
|
+
`;
|
|
52
|
+
const label = document.createElement('span');
|
|
53
|
+
label.textContent = 'Nitrogen Components';
|
|
54
|
+
link.textContent = '';
|
|
55
|
+
link.append(icon, label);
|
|
56
|
+
if (isActive) {
|
|
57
|
+
link.setAttribute('aria-current', 'page');
|
|
58
|
+
}
|
|
59
|
+
target.append(link);
|
|
60
|
+
};
|
|
61
|
+
renderLink();
|
|
62
|
+
const observer = new MutationObserver(() => {
|
|
63
|
+
renderLink();
|
|
64
|
+
});
|
|
65
|
+
observer.observe(document.body, {
|
|
66
|
+
childList: true,
|
|
67
|
+
subtree: true,
|
|
68
|
+
});
|
|
69
|
+
return () => {
|
|
70
|
+
observer.disconnect();
|
|
71
|
+
document.getElementById(linkId)?.remove();
|
|
72
|
+
};
|
|
73
|
+
}, [isActive]);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function NitrogenComponentInventoryReindexButton(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
const statusStorageKey = 'nitrogen-component-inventory:last-reindex-status';
|
|
5
|
+
export default function NitrogenComponentInventoryReindexButton() {
|
|
6
|
+
const [loading, setLoading] = useState(false);
|
|
7
|
+
const [status, setStatus] = useState(null);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const rawValue = window.sessionStorage.getItem(statusStorageKey);
|
|
10
|
+
if (!rawValue) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
setStatus(JSON.parse(rawValue));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
window.sessionStorage.removeItem(statusStorageKey);
|
|
18
|
+
}
|
|
19
|
+
}, []);
|
|
20
|
+
function persistStatus(nextStatus) {
|
|
21
|
+
setStatus(nextStatus);
|
|
22
|
+
window.sessionStorage.setItem(statusStorageKey, JSON.stringify(nextStatus));
|
|
23
|
+
}
|
|
24
|
+
async function handleClick() {
|
|
25
|
+
try {
|
|
26
|
+
setLoading(true);
|
|
27
|
+
const response = await fetch('/api/nitrogen/v1/component-inventory/reindex', {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
});
|
|
30
|
+
const data = await response.json();
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
persistStatus({
|
|
33
|
+
kind: 'error',
|
|
34
|
+
message: data?.error || 'Failed to reindex component inventory.',
|
|
35
|
+
timestamp: new Date().toISOString(),
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
persistStatus({
|
|
40
|
+
kind: 'success',
|
|
41
|
+
message: `Reindexed ${data.documentCount} documents and ${data.usageCount} component usages.`,
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
});
|
|
44
|
+
window.location.reload();
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error(error);
|
|
48
|
+
persistStatus({
|
|
49
|
+
kind: 'error',
|
|
50
|
+
message: 'Failed to reindex component inventory.',
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
setLoading(false);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function clearStatus() {
|
|
59
|
+
setStatus(null);
|
|
60
|
+
window.sessionStorage.removeItem(statusStorageKey);
|
|
61
|
+
}
|
|
62
|
+
return (_jsxs("div", { style: { display: 'grid', gap: 12 }, children: [_jsx("div", { style: { display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }, children: _jsx("button", { type: "button", onClick: handleClick, disabled: loading, style: {
|
|
63
|
+
border: 'none',
|
|
64
|
+
borderRadius: 6,
|
|
65
|
+
background: '#171717',
|
|
66
|
+
color: '#fff',
|
|
67
|
+
cursor: loading ? 'wait' : 'pointer',
|
|
68
|
+
fontSize: 14,
|
|
69
|
+
fontWeight: 600,
|
|
70
|
+
padding: '10px 14px',
|
|
71
|
+
}, children: loading ? 'Reindexing...' : 'Reindex Components' }) }), status ? (_jsxs("div", { style: {
|
|
72
|
+
alignItems: 'start',
|
|
73
|
+
background: status.kind === 'success' ? '#ecfdf5' : '#fef2f2',
|
|
74
|
+
border: `1px solid ${status.kind === 'success' ? '#a7f3d0' : '#fecaca'}`,
|
|
75
|
+
borderRadius: 10,
|
|
76
|
+
color: status.kind === 'success' ? '#065f46' : '#991b1b',
|
|
77
|
+
display: 'grid',
|
|
78
|
+
gap: 6,
|
|
79
|
+
gridTemplateColumns: '1fr auto',
|
|
80
|
+
padding: '12px 14px',
|
|
81
|
+
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 13, fontWeight: 700 }, children: status.kind === 'success' ? 'Last Reindex Completed' : 'Reindex Failed' }), _jsx("div", { style: { fontSize: 13, marginTop: 2 }, children: status.message }), _jsx("div", { style: { fontSize: 12, marginTop: 4, opacity: 0.75 }, children: new Date(status.timestamp).toLocaleString() })] }), _jsx("button", { type: "button", onClick: clearStatus, style: {
|
|
82
|
+
appearance: 'none',
|
|
83
|
+
background: 'transparent',
|
|
84
|
+
border: 'none',
|
|
85
|
+
color: 'inherit',
|
|
86
|
+
cursor: 'pointer',
|
|
87
|
+
fontSize: 12,
|
|
88
|
+
fontWeight: 700,
|
|
89
|
+
padding: 0,
|
|
90
|
+
}, children: "Dismiss" })] })) : null] }));
|
|
91
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// @ts-expect-error The consuming Payload app provides @payloadcms/next at runtime.
|
|
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';
|
|
7
|
+
export default async function NitrogenComponentInventoryView({ clientConfig, initPageResult, i18n, locale, params, searchParams, viewActions, viewType, }) {
|
|
8
|
+
const payload = initPageResult.req.payload;
|
|
9
|
+
const [catalogDocs, usageDocs] = await Promise.all([
|
|
10
|
+
findAllDocs(payload, NITROGEN_COMPONENT_CATALOG_COLLECTION),
|
|
11
|
+
findAllDocs(payload, NITROGEN_COMPONENT_USAGE_COLLECTION),
|
|
12
|
+
]);
|
|
13
|
+
const catalogByName = new Map(catalogDocs.map((doc) => [doc.componentName, doc.definition]));
|
|
14
|
+
const usageByName = new Map();
|
|
15
|
+
for (const usageDoc of usageDocs) {
|
|
16
|
+
const docs = usageByName.get(usageDoc.componentName) || [];
|
|
17
|
+
docs.push(usageDoc);
|
|
18
|
+
usageByName.set(usageDoc.componentName, docs);
|
|
19
|
+
}
|
|
20
|
+
const componentNames = Array.from(new Set([...catalogByName.keys(), ...usageByName.keys()])).sort((a, b) => a.localeCompare(b));
|
|
21
|
+
const components = componentNames.map((componentName) => ({
|
|
22
|
+
componentName,
|
|
23
|
+
definition: catalogByName.get(componentName),
|
|
24
|
+
isUnknown: !catalogByName.get(componentName),
|
|
25
|
+
usages: usageByName.get(componentName) || [],
|
|
26
|
+
}));
|
|
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 })] }) }));
|
|
28
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
declare const NitrogenDataViewer:
|
|
1
|
+
import type { JSONFieldServerComponent } from 'payload';
|
|
2
|
+
declare const NitrogenDataViewer: JSONFieldServerComponent;
|
|
3
3
|
export default NitrogenDataViewer;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
const NitrogenDataViewer =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
});
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import NitrogenDataViewerRuntime from './NitrogenDataViewerRuntime';
|
|
3
|
+
const NitrogenDataViewer = ({ field, path, 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(NitrogenDataViewerRuntime, { description: description, initialValue: value, label: label, path: path }));
|
|
7
|
+
};
|
|
7
8
|
export default NitrogenDataViewer;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
declare const NitrogenDataViewerRuntime:
|
|
1
|
+
import React from 'react';
|
|
2
|
+
declare const NitrogenDataViewerRuntime: React.FC<{
|
|
3
|
+
description?: string;
|
|
4
|
+
initialValue?: unknown;
|
|
5
|
+
label: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
}>;
|
|
3
8
|
export default NitrogenDataViewerRuntime;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { useField } from '@payloadcms/ui';
|
|
4
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
5
5
|
function updateAtPath(root, path, newValue) {
|
|
6
6
|
if (path.length === 0)
|
|
7
7
|
return newValue;
|
|
@@ -74,10 +74,10 @@ function renameKeyAtPath(root, path, newKey) {
|
|
|
74
74
|
return navigate(root, parentPath);
|
|
75
75
|
}
|
|
76
76
|
function addChildAtPath(root, path) {
|
|
77
|
-
function getAt(node,
|
|
78
|
-
if (
|
|
77
|
+
function getAt(node, currentPath) {
|
|
78
|
+
if (currentPath.length === 0)
|
|
79
79
|
return node;
|
|
80
|
-
const [head, ...rest] =
|
|
80
|
+
const [head, ...rest] = currentPath;
|
|
81
81
|
if (Array.isArray(node))
|
|
82
82
|
return getAt(node[head], rest);
|
|
83
83
|
if (typeof node === 'object' && node !== null) {
|
|
@@ -92,88 +92,117 @@ function addChildAtPath(root, path) {
|
|
|
92
92
|
if (typeof target === 'object' && target !== null) {
|
|
93
93
|
const obj = target;
|
|
94
94
|
let key = 'newKey';
|
|
95
|
-
let
|
|
96
|
-
while (key in obj)
|
|
97
|
-
key = `newKey${
|
|
95
|
+
let index = 1;
|
|
96
|
+
while (key in obj) {
|
|
97
|
+
key = `newKey${index++}`;
|
|
98
|
+
}
|
|
98
99
|
return updateAtPath(root, path, { ...obj, [key]: '' });
|
|
99
100
|
}
|
|
100
101
|
return root;
|
|
101
102
|
}
|
|
102
|
-
function
|
|
103
|
+
function coerceDisplayValue(value) {
|
|
104
|
+
if (typeof value === 'string') {
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
return value != null ? JSON.stringify(value, null, 2) : '[]';
|
|
108
|
+
}
|
|
109
|
+
function parseJsonValue(value) {
|
|
110
|
+
if (typeof value === 'string') {
|
|
111
|
+
try {
|
|
112
|
+
return JSON.parse(value);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return value ?? [];
|
|
119
|
+
}
|
|
120
|
+
function InlineEdit({ colorClass, display, onCommit, }) {
|
|
103
121
|
const [draft, setDraft] = useState(display);
|
|
104
122
|
const commit = () => onCommit(draft);
|
|
105
|
-
return (_jsx("input", { autoFocus: true,
|
|
106
|
-
if (
|
|
107
|
-
|
|
123
|
+
return (_jsx("input", { autoFocus: true, className: colorClass, onBlur: commit, onChange: (event) => setDraft(event.target.value), onClick: (event) => event.stopPropagation(), onKeyDown: (event) => {
|
|
124
|
+
if (event.key === 'Enter') {
|
|
125
|
+
event.preventDefault();
|
|
108
126
|
commit();
|
|
109
127
|
}
|
|
110
|
-
if (
|
|
128
|
+
if (event.key === 'Escape') {
|
|
111
129
|
onCommit(display);
|
|
112
|
-
|
|
130
|
+
}
|
|
131
|
+
}, style: {
|
|
113
132
|
background: 'transparent',
|
|
114
133
|
border: 'none',
|
|
115
134
|
borderBottom: '1px solid currentColor',
|
|
116
135
|
color: 'inherit',
|
|
117
136
|
font: 'inherit',
|
|
118
|
-
|
|
119
|
-
outline: 'none',
|
|
137
|
+
maxWidth: '500px',
|
|
120
138
|
minWidth: '4ch',
|
|
139
|
+
outline: 'none',
|
|
140
|
+
padding: '0 2px',
|
|
121
141
|
width: `${Math.max(draft.length + 2, 4)}ch`,
|
|
122
|
-
|
|
123
|
-
} }));
|
|
142
|
+
}, value: draft }));
|
|
124
143
|
}
|
|
125
|
-
function
|
|
126
|
-
return (_jsx("button", {
|
|
127
|
-
marginLeft: 6,
|
|
128
|
-
padding: '0 5px',
|
|
129
|
-
fontSize: '10px',
|
|
130
|
-
lineHeight: '16px',
|
|
144
|
+
function ActionButton({ color, label, onClick, }) {
|
|
145
|
+
return (_jsx("button", { onClick: onClick, style: {
|
|
131
146
|
background: color,
|
|
132
|
-
color: '#fff',
|
|
133
147
|
border: 'none',
|
|
134
|
-
borderRadius:
|
|
148
|
+
borderRadius: 3,
|
|
149
|
+
color: '#fff',
|
|
135
150
|
cursor: 'pointer',
|
|
136
151
|
flexShrink: 0,
|
|
152
|
+
fontSize: 10,
|
|
153
|
+
lineHeight: '16px',
|
|
154
|
+
marginLeft: 6,
|
|
137
155
|
opacity: 0.85,
|
|
138
|
-
|
|
156
|
+
padding: '0 5px',
|
|
157
|
+
}, type: "button", children: label }));
|
|
139
158
|
}
|
|
140
|
-
function JsonNode({
|
|
159
|
+
function JsonNode({ callbacks, depth, forceOpen, k, path, unlocked, value, }) {
|
|
141
160
|
const defaultOpen = forceOpen !== null ? forceOpen : depth < 2;
|
|
142
161
|
const [open, setOpen] = useState(defaultOpen);
|
|
143
|
-
const [editingValue, setEditingValue] = useState(false);
|
|
144
162
|
const [editingKey, setEditingKey] = useState(false);
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
const
|
|
163
|
+
const [editingValue, setEditingValue] = useState(false);
|
|
164
|
+
const { onAddChild, onDelete, onRenameKey, onUpdate } = callbacks;
|
|
165
|
+
const deleteButton = unlocked ? (_jsx(ActionButton, { color: "#ef4444", label: "\u00D7", onClick: (event) => {
|
|
166
|
+
event.stopPropagation();
|
|
167
|
+
onDelete(path);
|
|
168
|
+
} })) : null;
|
|
169
|
+
const keyElement = k === undefined ? null : (_jsxs(_Fragment, { children: [_jsxs("span", { className: "njv-key", children: ["\"", unlocked && editingKey ? (_jsx(InlineEdit, { colorClass: "njv-key", display: k, onCommit: (newKey) => {
|
|
148
170
|
setEditingKey(false);
|
|
149
|
-
if (newKey !== k)
|
|
171
|
+
if (newKey !== k) {
|
|
150
172
|
onRenameKey(path, newKey);
|
|
151
|
-
|
|
173
|
+
}
|
|
174
|
+
} })) : (_jsx("span", { onClick: unlocked ? (event) => { event.stopPropagation(); setEditingKey(true); } : undefined, style: unlocked ? { borderBottom: '1px dotted currentColor', cursor: 'text' } : undefined, children: k })), "\""] }), _jsx("span", { className: "njv-brace", children: ": " })] }));
|
|
152
175
|
if (value === null) {
|
|
153
|
-
return (_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20 }, children: [
|
|
176
|
+
return (_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20 }, children: [keyElement, _jsx("span", { className: "njv-null", onClick: unlocked ? (event) => { event.stopPropagation(); onUpdate(path, ''); } : undefined, style: unlocked ? { borderBottom: '1px dotted currentColor', cursor: 'pointer' } : undefined, title: unlocked ? 'Click to convert to empty string' : undefined, children: "null" })] }), deleteButton] }));
|
|
154
177
|
}
|
|
155
178
|
if (typeof value === 'string') {
|
|
156
|
-
return (_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20 }, children: [
|
|
179
|
+
return (_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20 }, children: [keyElement, _jsxs("span", { className: "njv-string", children: ["\"", unlocked && editingValue ? (_jsx(InlineEdit, { colorClass: "njv-string", display: value, onCommit: (nextValue) => {
|
|
157
180
|
setEditingValue(false);
|
|
158
|
-
onUpdate(path,
|
|
159
|
-
} })) : (_jsx("span", { onClick: unlocked ? (
|
|
181
|
+
onUpdate(path, nextValue);
|
|
182
|
+
} })) : (_jsx("span", { onClick: unlocked ? (event) => { event.stopPropagation(); setEditingValue(true); } : undefined, style: unlocked ? { borderBottom: '1px dotted currentColor', cursor: 'text' } : undefined, children: value.length > 120 ? `${value.slice(0, 120)}…` : value })), "\""] })] }), deleteButton] }));
|
|
160
183
|
}
|
|
161
184
|
if (typeof value === 'number') {
|
|
162
|
-
return (_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20 }, children: [
|
|
185
|
+
return (_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20 }, children: [keyElement, _jsx("span", { className: "njv-number", children: unlocked && editingValue ? (_jsx(InlineEdit, { colorClass: "njv-number", display: String(value), onCommit: (nextValue) => {
|
|
163
186
|
setEditingValue(false);
|
|
164
|
-
const
|
|
165
|
-
onUpdate(path, Number.isNaN(
|
|
166
|
-
} })) : (_jsx("span", { onClick: unlocked ? (
|
|
187
|
+
const numericValue = Number(nextValue);
|
|
188
|
+
onUpdate(path, Number.isNaN(numericValue) ? nextValue : numericValue);
|
|
189
|
+
} })) : (_jsx("span", { onClick: unlocked ? (event) => { event.stopPropagation(); setEditingValue(true); } : undefined, style: unlocked ? { borderBottom: '1px dotted currentColor', cursor: 'text' } : undefined, children: value })) })] }), deleteButton] }));
|
|
167
190
|
}
|
|
168
191
|
if (typeof value === 'boolean') {
|
|
169
|
-
return (_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20 }, children: [
|
|
192
|
+
return (_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", style: { paddingLeft: depth * 20 }, children: [keyElement, _jsx("span", { className: "njv-boolean", onClick: unlocked ? (event) => { event.stopPropagation(); onUpdate(path, !value); } : undefined, style: unlocked ? { borderBottom: '1px dotted currentColor', cursor: 'pointer' } : undefined, title: unlocked ? 'Click to toggle' : undefined, children: String(value) })] }), deleteButton] }));
|
|
170
193
|
}
|
|
171
194
|
if (Array.isArray(value)) {
|
|
172
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code",
|
|
195
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", onClick: () => setOpen((current) => !current), style: { cursor: 'pointer', paddingLeft: depth * 20, userSelect: 'none' }, children: [_jsx("span", { className: "njv-arrow", children: open ? '▼' : '▶' }), keyElement, _jsx("span", { className: "njv-brace", children: "[" }), !open ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "njv-summary", children: [" ", value.length, " items "] }), _jsx("span", { className: "njv-brace", children: "]" })] })) : null] }), deleteButton] }), open ? (_jsxs(_Fragment, { children: [value.map((item, index) => (_jsx(JsonNode, { callbacks: callbacks, depth: depth + 1, forceOpen: forceOpen, path: [...path, index], unlocked: unlocked, value: item }, index))), _jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsx("span", { className: "njv-code", style: { paddingLeft: (depth + 1) * 20 }, children: _jsx("span", { className: "njv-brace", children: "]" }) }), unlocked ? (_jsx(ActionButton, { color: "#22c55e", label: "+ item", onClick: (event) => {
|
|
196
|
+
event.stopPropagation();
|
|
197
|
+
onAddChild(path);
|
|
198
|
+
} })) : null] })] })) : null] }));
|
|
173
199
|
}
|
|
174
200
|
if (typeof value === 'object') {
|
|
175
201
|
const entries = Object.entries(value);
|
|
176
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code",
|
|
202
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsxs("span", { className: "njv-code", onClick: () => setOpen((current) => !current), style: { cursor: 'pointer', paddingLeft: depth * 20, userSelect: 'none' }, children: [_jsx("span", { className: "njv-arrow", children: open ? '▼' : '▶' }), keyElement, _jsx("span", { className: "njv-brace", children: '{' }), !open ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "njv-summary", children: [" ", entries.length, " keys "] }), _jsx("span", { className: "njv-brace", children: '}' })] })) : null] }), deleteButton] }), open ? (_jsxs(_Fragment, { children: [entries.map(([entryKey, entryValue]) => (_jsx(JsonNode, { callbacks: callbacks, depth: depth + 1, forceOpen: forceOpen, k: entryKey, path: [...path, entryKey], unlocked: unlocked, value: entryValue }, entryKey))), _jsxs("div", { className: "njv-line", style: { alignItems: 'center' }, children: [_jsx("span", { className: "njv-code", style: { paddingLeft: (depth + 1) * 20 }, children: _jsx("span", { className: "njv-brace", children: '}' }) }), unlocked ? (_jsx(ActionButton, { color: "#22c55e", label: "+ key", onClick: (event) => {
|
|
203
|
+
event.stopPropagation();
|
|
204
|
+
onAddChild(path);
|
|
205
|
+
} })) : null] })] })) : null] }));
|
|
177
206
|
}
|
|
178
207
|
return null;
|
|
179
208
|
}
|
|
@@ -281,73 +310,122 @@ function ensureStyles() {
|
|
|
281
310
|
`;
|
|
282
311
|
document.head.appendChild(style);
|
|
283
312
|
}
|
|
284
|
-
const NitrogenDataViewerRuntime = ({
|
|
285
|
-
const
|
|
313
|
+
const NitrogenDataViewerRuntime = ({ description, initialValue, label, path }) => {
|
|
314
|
+
const field = path ? useField({ path }) : null;
|
|
315
|
+
const value = field?.value ?? initialValue;
|
|
316
|
+
const setValue = field?.setValue;
|
|
317
|
+
const [collapsed, setCollapsed] = useState(true);
|
|
286
318
|
const [copied, setCopied] = useState(false);
|
|
287
|
-
const [pasted, setPasted] = useState(false);
|
|
288
319
|
const [forceOpen, setForceOpen] = useState(null);
|
|
320
|
+
const [manualPasteValue, setManualPasteValue] = useState('');
|
|
321
|
+
const [pasteError, setPasteError] = useState('');
|
|
322
|
+
const [pasted, setPasted] = useState(false);
|
|
323
|
+
const [showPastePanel, setShowPastePanel] = useState(false);
|
|
289
324
|
const [treeKey, setTreeKey] = useState(0);
|
|
290
325
|
const [unlocked, setUnlocked] = useState(false);
|
|
291
326
|
ensureStyles();
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
parsed = typeof value === 'string' ? JSON.parse(value) : value;
|
|
295
|
-
}
|
|
296
|
-
catch {
|
|
297
|
-
parsed = [];
|
|
298
|
-
}
|
|
299
|
-
const jsonString = value != null ? JSON.stringify(value, null, 2) : '[]';
|
|
327
|
+
const parsed = useMemo(() => parseJsonValue(value), [value]);
|
|
328
|
+
const jsonString = useMemo(() => coerceDisplayValue(value), [value]);
|
|
300
329
|
const moduleCount = Array.isArray(parsed) ? parsed.length : null;
|
|
330
|
+
const applyValue = useCallback((nextValue) => {
|
|
331
|
+
if (!setValue)
|
|
332
|
+
return;
|
|
333
|
+
setValue(nextValue);
|
|
334
|
+
}, [setValue]);
|
|
335
|
+
const callbacks = useMemo(() => ({
|
|
336
|
+
onAddChild: (targetPath) => {
|
|
337
|
+
applyValue(addChildAtPath(parsed, targetPath));
|
|
338
|
+
},
|
|
339
|
+
onDelete: (targetPath) => {
|
|
340
|
+
applyValue(deleteAtPath(parsed, targetPath));
|
|
341
|
+
},
|
|
342
|
+
onRenameKey: (targetPath, newKey) => {
|
|
343
|
+
applyValue(renameKeyAtPath(parsed, targetPath, newKey));
|
|
344
|
+
},
|
|
345
|
+
onUpdate: (targetPath, nextValue) => {
|
|
346
|
+
applyValue(updateAtPath(parsed, targetPath, nextValue));
|
|
347
|
+
},
|
|
348
|
+
}), [applyValue, parsed]);
|
|
301
349
|
const handleCopy = useCallback(() => {
|
|
302
350
|
navigator.clipboard.writeText(jsonString).then(() => {
|
|
303
351
|
setCopied(true);
|
|
304
352
|
setTimeout(() => setCopied(false), 2000);
|
|
305
353
|
});
|
|
306
354
|
}, [jsonString]);
|
|
307
|
-
const
|
|
355
|
+
const applyPastedJson = useCallback((rawValue) => {
|
|
356
|
+
const trimmed = rawValue.trim();
|
|
357
|
+
if (!trimmed) {
|
|
358
|
+
setPasteError('Paste some JSON first.');
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
const parsedValue = JSON.parse(trimmed);
|
|
363
|
+
applyValue(parsedValue);
|
|
364
|
+
setManualPasteValue('');
|
|
365
|
+
setPasteError('');
|
|
366
|
+
setPasted(true);
|
|
367
|
+
setShowPastePanel(false);
|
|
368
|
+
setTreeKey((current) => current + 1);
|
|
369
|
+
setTimeout(() => setPasted(false), 2000);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
setPasteError('That is not valid JSON.');
|
|
373
|
+
}
|
|
374
|
+
}, [applyValue]);
|
|
375
|
+
const handlePasteFromClipboard = useCallback(() => {
|
|
308
376
|
navigator.clipboard.readText().then((text) => {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
setTimeout(() => setPasted(false), 2000);
|
|
314
|
-
}
|
|
315
|
-
catch {
|
|
316
|
-
// ignore invalid JSON
|
|
317
|
-
}
|
|
377
|
+
applyPastedJson(text);
|
|
378
|
+
}, () => {
|
|
379
|
+
setPasteError('Clipboard access was blocked. Paste manually below.');
|
|
380
|
+
setShowPastePanel(true);
|
|
318
381
|
});
|
|
319
|
-
}, [
|
|
382
|
+
}, [applyPastedJson]);
|
|
320
383
|
const expandAll = useCallback(() => {
|
|
321
384
|
setForceOpen(true);
|
|
322
|
-
setTreeKey((
|
|
385
|
+
setTreeKey((current) => current + 1);
|
|
323
386
|
}, []);
|
|
324
387
|
const collapseAll = useCallback(() => {
|
|
325
388
|
setForceOpen(false);
|
|
326
|
-
setTreeKey((
|
|
389
|
+
setTreeKey((current) => current + 1);
|
|
327
390
|
}, []);
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
391
|
+
const header = (_jsxs("span", { children: [label, moduleCount !== null ? (_jsxs("span", { style: { fontWeight: 400, marginLeft: 8, opacity: 0.5 }, children: ["(", moduleCount, " item", moduleCount !== 1 ? 's' : '', ")"] })) : null] }));
|
|
392
|
+
return (_jsxs("div", { className: "njv-root field-type", style: { marginBottom: '1.5rem' }, children: [_jsxs("div", { style: { border: '1px solid var(--njv-border)', borderRadius: 4, overflow: 'hidden' }, children: [_jsxs("button", { onClick: () => setCollapsed((current) => !current), style: {
|
|
393
|
+
alignItems: 'center',
|
|
394
|
+
background: 'var(--theme-elevation-50, #f8f8f8)',
|
|
395
|
+
border: 'none',
|
|
396
|
+
borderBottom: collapsed ? 'none' : '1px solid var(--njv-border)',
|
|
397
|
+
cursor: 'pointer',
|
|
398
|
+
display: 'flex',
|
|
399
|
+
justifyContent: 'space-between',
|
|
400
|
+
padding: '12px 16px',
|
|
401
|
+
width: '100%',
|
|
402
|
+
}, type: "button", children: [header, _jsx("span", { style: { fontSize: 12, opacity: 0.65 }, children: collapsed ? 'Show' : 'Hide' })] }), !collapsed ? (_jsxs("div", { children: [_jsxs("div", { className: "njv-toolbar", children: [_jsx("button", { className: "btn btn--size-small btn--style-secondary", onClick: handleCopy, type: "button", children: copied ? '✓ Copied' : 'Copy' }), _jsx("button", { className: "btn btn--size-small btn--style-secondary", onClick: () => setUnlocked((current) => !current), type: "button", children: unlocked ? 'Lock' : 'Unlock' }), unlocked ? (_jsxs(_Fragment, { children: [_jsx("button", { className: "btn btn--size-small btn--style-secondary", onClick: handlePasteFromClipboard, type: "button", children: pasted ? '✓ Pasted' : 'Paste' }), _jsx("button", { className: "btn btn--size-small btn--style-secondary", onClick: () => {
|
|
403
|
+
setShowPastePanel((current) => !current);
|
|
404
|
+
setPasteError('');
|
|
405
|
+
}, type: "button", children: showPastePanel ? 'Hide Paste Box' : 'Paste Manually' })] })) : null, _jsx("button", { className: "btn btn--size-small btn--style-secondary", onClick: expandAll, type: "button", children: "Expand All" }), _jsx("button", { className: "btn btn--size-small btn--style-secondary", onClick: collapseAll, type: "button", children: "Collapse All" })] }), unlocked && showPastePanel ? (_jsxs("div", { style: {
|
|
406
|
+
background: 'var(--theme-elevation-50, #f8f8f8)',
|
|
407
|
+
borderBottom: '1px solid var(--njv-border)',
|
|
408
|
+
display: 'grid',
|
|
409
|
+
gap: 8,
|
|
410
|
+
padding: 12,
|
|
411
|
+
}, children: [_jsx("textarea", { onChange: (event) => setManualPasteValue(event.target.value), placeholder: "Paste Nitrogen JSON here...", style: {
|
|
412
|
+
border: '1px solid var(--theme-elevation-150, #ddd)',
|
|
413
|
+
borderRadius: 4,
|
|
414
|
+
fontFamily: 'var(--font-mono, \"SF Mono\", Menlo, Consolas, monospace)',
|
|
415
|
+
fontSize: 12,
|
|
416
|
+
minHeight: 160,
|
|
417
|
+
padding: 12,
|
|
418
|
+
resize: 'vertical',
|
|
419
|
+
width: '100%',
|
|
420
|
+
}, value: manualPasteValue }), _jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 8 }, children: [_jsx("button", { className: "btn btn--size-small btn--style-primary", onClick: () => applyPastedJson(manualPasteValue), type: "button", children: "Apply Pasted JSON" }), _jsx("button", { className: "btn btn--size-small btn--style-secondary", onClick: () => {
|
|
421
|
+
setManualPasteValue('');
|
|
422
|
+
setPasteError('');
|
|
423
|
+
}, type: "button", children: "Clear" })] }), pasteError ? (_jsx("p", { style: { color: 'var(--theme-error-500, #dc2626)', fontSize: 12, margin: 0 }, children: pasteError })) : null] })) : null, _jsx("div", { className: "njv-body", style: {
|
|
424
|
+
backgroundColor: 'var(--njv-bg)',
|
|
425
|
+
fontFamily: 'var(--font-mono, "SF Mono", Menlo, Consolas, monospace)',
|
|
426
|
+
fontSize: 'var(--font-body-size, 13px)',
|
|
427
|
+
maxHeight: '50vh',
|
|
428
|
+
overflow: 'auto',
|
|
429
|
+
}, children: _jsx(JsonNode, { callbacks: callbacks, depth: 0, forceOpen: forceOpen, path: [], unlocked: unlocked, value: parsed }, treeKey) })] })) : null] }), typeof description === 'string' ? (_jsx("p", { style: { color: '#888', fontSize: 12, marginTop: 4 }, children: description })) : null] }));
|
|
352
430
|
};
|
|
353
431
|
export default NitrogenDataViewerRuntime;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function NitrogenNavGroup(): import("react/jsx-runtime").JSX.Element;
|