@nextsparkjs/core 0.1.0-beta.181 → 0.1.0-beta.182
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/components/devtools/api-explorer/ApiEndpointsSidebar.d.ts.map +1 -1
- package/dist/components/devtools/api-explorer/ApiEndpointsSidebar.js +4 -1
- package/dist/components/devtools/api-explorer/ApiExplorer.d.ts.map +1 -1
- package/dist/components/devtools/api-explorer/ApiExplorer.js +92 -48
- package/dist/components/devtools/api-explorer/ApiRequestPanel.d.ts.map +1 -1
- package/dist/components/devtools/api-explorer/ApiRequestPanel.js +36 -6
- package/dist/components/devtools/api-explorer/JsonViewer.d.ts.map +1 -1
- package/dist/components/devtools/api-explorer/JsonViewer.js +13 -8
- package/dist/components/devtools/api-explorer/PanelDivider.d.ts +26 -0
- package/dist/components/devtools/api-explorer/PanelDivider.d.ts.map +1 -0
- package/dist/components/devtools/api-explorer/PanelDivider.js +102 -0
- package/dist/components/devtools/api-explorer/PresetsTab.d.ts.map +1 -1
- package/dist/components/devtools/api-explorer/PresetsTab.js +35 -2
- package/dist/components/devtools/api-explorer/preset-placeholders.d.ts +52 -0
- package/dist/components/devtools/api-explorer/preset-placeholders.d.ts.map +1 -0
- package/dist/components/devtools/api-explorer/preset-placeholders.js +62 -0
- package/dist/lib/selectors/core-selectors.d.ts +3 -0
- package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/devtools.selectors.d.ts +3 -0
- package/dist/lib/selectors/domains/devtools.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/devtools.selectors.js +5 -2
- package/dist/lib/selectors/selectors.d.ts +6 -0
- package/dist/lib/selectors/selectors.d.ts.map +1 -1
- package/dist/styles/classes.json +10 -5
- package/dist/styles/ui.css +1 -1
- package/dist/types/api-presets.d.ts +19 -1
- package/dist/types/api-presets.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiEndpointsSidebar.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/ApiEndpointsSidebar.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAA;AAC5F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AA4CrD,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,CAAA;CACnB;AAED,UAAU,wBAAwB;IAChC,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,aAAa,EAAE,CAAC,CAAA;IAC9C,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACzC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAClF,WAAW,EAAE,OAAO,CAAA;IACpB,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B;AAED,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,gBAAgB,GACjB,EAAE,wBAAwB,
|
|
1
|
+
{"version":3,"file":"ApiEndpointsSidebar.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/ApiEndpointsSidebar.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAA;AAC5F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AA4CrD,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,CAAA;CACnB;AAED,UAAU,wBAAwB;IAChC,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,aAAa,EAAE,CAAC,CAAA;IAC9C,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACzC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAClF,WAAW,EAAE,OAAO,CAAA;IACpB,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B;AAED,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,gBAAgB,GACjB,EAAE,wBAAwB,2CAyQ1B"}
|
|
@@ -50,7 +50,10 @@ function ApiEndpointsSidebar({
|
|
|
50
50
|
});
|
|
51
51
|
const [expandedPrefixes, setExpandedPrefixes] = useState(() => {
|
|
52
52
|
const saved = loadState("sidebar:expandedPrefixes");
|
|
53
|
-
return new Set(saved
|
|
53
|
+
if (saved) return new Set(saved);
|
|
54
|
+
return new Set(
|
|
55
|
+
groupByPathPrefix(routes.theme ?? []).map((group) => `theme:${group.prefix}`)
|
|
56
|
+
);
|
|
54
57
|
});
|
|
55
58
|
useEffect(() => {
|
|
56
59
|
saveState("sidebar:expandedCategories", Array.from(expandedCategories));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiExplorer.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/ApiExplorer.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ApiExplorer.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/ApiExplorer.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAA;AA2B5F,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,aAAa,EAAE,CAAC,CAAA;IAC9C,eAAe,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC1D;AAwBD,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,gBAAgB,2CA4dxE"}
|
|
@@ -8,12 +8,17 @@ import { sel } from "../../../lib/test/index.js";
|
|
|
8
8
|
import { ApiEndpointsSidebar } from "./ApiEndpointsSidebar.js";
|
|
9
9
|
import { ApiRequestPanel } from "./ApiRequestPanel.js";
|
|
10
10
|
import { ApiResponsePanel } from "./ApiResponsePanel.js";
|
|
11
|
+
import { PanelDivider } from "./PanelDivider.js";
|
|
11
12
|
import { useApiRequest } from "../api-tester/hooks/useApiRequest.js";
|
|
12
13
|
import { useApiExplorerNavigation } from "../../../hooks/useApiExplorerNavigation.js";
|
|
13
14
|
import { extractPathParams, buildUrl } from "../api-tester/utils/url-builder.js";
|
|
14
15
|
import { ApiPresetsService } from "../../../lib/services/api-presets.service.js";
|
|
15
16
|
import { ApiDocsService } from "../../../lib/services/api-docs.service.js";
|
|
16
17
|
import { loadState, saveState, draftKey } from "./explorer-storage.js";
|
|
18
|
+
import {
|
|
19
|
+
createPresetPlaceholderValues,
|
|
20
|
+
resolvePresetPlaceholders
|
|
21
|
+
} from "./preset-placeholders.js";
|
|
17
22
|
function findRouteByPath(routes, path) {
|
|
18
23
|
for (const category of Object.keys(routes)) {
|
|
19
24
|
const found = routes[category].find((r) => r.path === path);
|
|
@@ -50,6 +55,24 @@ function ApiExplorer({ routes, initialEndpoint }) {
|
|
|
50
55
|
});
|
|
51
56
|
const [body, setBody] = useState("");
|
|
52
57
|
const [actAsUserId, setActAsUserId] = useState(() => loadState("actAsUserId") ?? "");
|
|
58
|
+
const [splitPercent, setSplitPercent] = useState(55);
|
|
59
|
+
const splitRowRef = useRef(null);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const saved = loadState("splitPercent");
|
|
62
|
+
if (typeof saved === "number") setSplitPercent(saved);
|
|
63
|
+
}, []);
|
|
64
|
+
const handleSplitChange = useCallback((percent) => {
|
|
65
|
+
setSplitPercent(percent);
|
|
66
|
+
saveState("splitPercent", percent);
|
|
67
|
+
}, []);
|
|
68
|
+
const [isSideBySide, setIsSideBySide] = useState(false);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
const query = window.matchMedia("(min-width: 1024px)");
|
|
71
|
+
const sync = () => setIsSideBySide(query.matches);
|
|
72
|
+
sync();
|
|
73
|
+
query.addEventListener("change", sync);
|
|
74
|
+
return () => query.removeEventListener("change", sync);
|
|
75
|
+
}, []);
|
|
53
76
|
const [tabsModifiedByPreset, setTabsModifiedByPreset] = useState(
|
|
54
77
|
() => /* @__PURE__ */ new Set()
|
|
55
78
|
);
|
|
@@ -126,7 +149,16 @@ function ApiExplorer({ routes, initialEndpoint }) {
|
|
|
126
149
|
apiKey: authType === "apiKey" ? apiKey : void 0
|
|
127
150
|
});
|
|
128
151
|
}, [selectedEndpoint, pathParams, queryParams, headers, method, body, authType, apiKey, bypassMode, selectedTeamId, actAsUserId, execute]);
|
|
129
|
-
const handleApplyPreset = useCallback((
|
|
152
|
+
const handleApplyPreset = useCallback((rawPreset) => {
|
|
153
|
+
const values = createPresetPlaceholderValues();
|
|
154
|
+
const preset = {
|
|
155
|
+
...rawPreset,
|
|
156
|
+
pathParams: resolvePresetPlaceholders(rawPreset.pathParams, values),
|
|
157
|
+
params: resolvePresetPlaceholders(rawPreset.params, values),
|
|
158
|
+
headers: resolvePresetPlaceholders(rawPreset.headers, values),
|
|
159
|
+
payload: resolvePresetPlaceholders(rawPreset.payload, values),
|
|
160
|
+
sessionConfig: resolvePresetPlaceholders(rawPreset.sessionConfig, values)
|
|
161
|
+
};
|
|
130
162
|
const modifiedTabs = /* @__PURE__ */ new Set();
|
|
131
163
|
if (preset.method) {
|
|
132
164
|
setMethod(preset.method);
|
|
@@ -172,11 +204,7 @@ function ApiExplorer({ routes, initialEndpoint }) {
|
|
|
172
204
|
setBypassMode(preset.sessionConfig.crossTeam);
|
|
173
205
|
}
|
|
174
206
|
if (preset.sessionConfig.teamId) {
|
|
175
|
-
|
|
176
|
-
if (teamId === "{{FIRST_TEAM_ID}}" && typeof window !== "undefined") {
|
|
177
|
-
teamId = localStorage.getItem("firstTeamId") || localStorage.getItem("activeTeamId") || "";
|
|
178
|
-
}
|
|
179
|
-
setSelectedTeamId(teamId);
|
|
207
|
+
setSelectedTeamId(preset.sessionConfig.teamId);
|
|
180
208
|
}
|
|
181
209
|
if (preset.sessionConfig.authType) {
|
|
182
210
|
setAuthType(preset.sessionConfig.authType);
|
|
@@ -258,7 +286,7 @@ function ApiExplorer({ routes, initialEndpoint }) {
|
|
|
258
286
|
}
|
|
259
287
|
}, [routes, initialEndpoint, urlPath, urlMethod, navigateToEndpoint, applyEndpointState]);
|
|
260
288
|
const totalRoutes = Object.values(routes).reduce((sum, arr) => sum + arr.length, 0);
|
|
261
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex h-
|
|
289
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full overflow-hidden", "data-cy": sel("devtools.apiExplorer.container"), children: [
|
|
262
290
|
/* @__PURE__ */ jsxs("div", { className: "lg:hidden fixed top-0 left-0 right-0 z-50 h-14 bg-background border-b flex items-center px-4 gap-3", children: [
|
|
263
291
|
/* @__PURE__ */ jsx(
|
|
264
292
|
Button,
|
|
@@ -307,49 +335,65 @@ function ApiExplorer({ routes, initialEndpoint }) {
|
|
|
307
335
|
)
|
|
308
336
|
}
|
|
309
337
|
),
|
|
310
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col lg:flex-row min-w-0 pt-14 lg:pt-0", children: selectedEndpoint ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
311
|
-
/* @__PURE__ */ jsx(
|
|
312
|
-
|
|
338
|
+
/* @__PURE__ */ jsx("div", { ref: splitRowRef, className: "flex-1 flex flex-col lg:flex-row min-w-0 pt-14 lg:pt-0", children: selectedEndpoint ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
339
|
+
/* @__PURE__ */ jsx(
|
|
340
|
+
"div",
|
|
313
341
|
{
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
342
|
+
className: "flex-1 min-h-0 min-w-0 border-b lg:border-b-0 overflow-hidden",
|
|
343
|
+
style: isSideBySide ? { flex: `0 0 ${splitPercent}%` } : void 0,
|
|
344
|
+
"data-split-panel": "request",
|
|
345
|
+
children: /* @__PURE__ */ jsx(
|
|
346
|
+
ApiRequestPanel,
|
|
347
|
+
{
|
|
348
|
+
route: selectedEndpoint.route,
|
|
349
|
+
basePath: selectedEndpoint.path,
|
|
350
|
+
method,
|
|
351
|
+
pathParams,
|
|
352
|
+
queryParams,
|
|
353
|
+
headers,
|
|
354
|
+
authType,
|
|
355
|
+
apiKey,
|
|
356
|
+
bypassMode,
|
|
357
|
+
selectedTeamId,
|
|
358
|
+
actAsUserId,
|
|
359
|
+
body,
|
|
360
|
+
status,
|
|
361
|
+
onMethodChange: setMethod,
|
|
362
|
+
onPathParamsChange: setPathParams,
|
|
363
|
+
onQueryParamsChange: setQueryParams,
|
|
364
|
+
onHeadersChange: setHeaders,
|
|
365
|
+
onAuthTypeChange: setAuthType,
|
|
366
|
+
onApiKeyChange: setApiKey,
|
|
367
|
+
onBypassModeChange: setBypassMode,
|
|
368
|
+
onTeamChange: setSelectedTeamId,
|
|
369
|
+
onActAsUserChange: setActAsUserId,
|
|
370
|
+
onBodyChange: setBody,
|
|
371
|
+
onSend: handleSend,
|
|
372
|
+
onCancel: cancel,
|
|
373
|
+
endpointPresets,
|
|
374
|
+
endpointDoc,
|
|
375
|
+
onApplyPreset: handleApplyPreset,
|
|
376
|
+
tabsModifiedByPreset,
|
|
377
|
+
onClearPresetIndicator: (tab) => {
|
|
378
|
+
setTabsModifiedByPreset((prev) => {
|
|
379
|
+
const next = new Set(prev);
|
|
380
|
+
next.delete(tab);
|
|
381
|
+
return next;
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
)
|
|
350
386
|
}
|
|
351
|
-
)
|
|
352
|
-
/* @__PURE__ */ jsx(
|
|
387
|
+
),
|
|
388
|
+
/* @__PURE__ */ jsx(
|
|
389
|
+
PanelDivider,
|
|
390
|
+
{
|
|
391
|
+
value: splitPercent,
|
|
392
|
+
onChange: handleSplitChange,
|
|
393
|
+
containerRef: splitRowRef
|
|
394
|
+
}
|
|
395
|
+
),
|
|
396
|
+
/* @__PURE__ */ jsx("div", { className: "h-[40vh] lg:h-auto lg:flex-1 min-h-[200px] min-w-0 overflow-hidden", children: /* @__PURE__ */ jsx(
|
|
353
397
|
ApiResponsePanel,
|
|
354
398
|
{
|
|
355
399
|
status,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiRequestPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/ApiRequestPanel.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACvG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAA;AAC7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"ApiRequestPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/ApiRequestPanel.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACvG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAA;AAC7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAc/E,UAAU,oBAAoB;IAC5B,KAAK,EAAE,aAAa,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,UAAU,CAAA;IAClB,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,WAAW,EAAE,YAAY,EAAE,CAAA;IAC3B,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,QAAQ,EAAE,QAAQ,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,OAAO,CAAA;IACnB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,aAAa,CAAA;IACrB,cAAc,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAA;IAC5C,kBAAkB,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IACjD,mBAAmB,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,IAAI,CAAA;IACrD,eAAe,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAA;IAClD,gBAAgB,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAA;IAC1C,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,kBAAkB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9C,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC7C,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3C,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,QAAQ,EAAE,MAAM,IAAI,CAAA;IAEpB,eAAe,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAC3C,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACpD,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;IAE3C,oBAAoB,CAAC,EAAE,GAAG,CAAC,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC,CAAA;IACzD,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,KAAK,IAAI,CAAA;CACtE;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,UAAU,EACV,WAAW,EACX,OAAO,EACP,QAAQ,EACR,MAAM,EACN,UAAU,EACV,cAAc,EACd,WAAW,EACX,IAAI,EACJ,MAAM,EACN,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,MAAM,EACN,QAAQ,EACR,eAAe,EACf,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,sBAAsB,GACvB,EAAE,oBAAoB,2CAiQtB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useMemo } from "react";
|
|
4
|
-
import { Send, X } from "lucide-react";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { ChevronRight, Send, X } from "lucide-react";
|
|
5
5
|
import { Button } from "../../ui/button.js";
|
|
6
6
|
import { Badge } from "../../ui/badge.js";
|
|
7
7
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs.js";
|
|
@@ -17,6 +17,7 @@ import { PayloadEditor } from "../api-tester/PayloadEditor.js";
|
|
|
17
17
|
import { buildUrl, validatePathParams, validateJsonBody } from "../api-tester/utils/url-builder.js";
|
|
18
18
|
import { PresetsTab } from "./PresetsTab.js";
|
|
19
19
|
import { ApiDocsModal } from "./ApiDocsModal.js";
|
|
20
|
+
import { loadState, saveState } from "./explorer-storage.js";
|
|
20
21
|
const methodColors = {
|
|
21
22
|
GET: "bg-emerald-500/20 text-emerald-600 dark:text-emerald-400 border-emerald-500/30",
|
|
22
23
|
POST: "bg-blue-500/20 text-blue-600 dark:text-blue-400 border-blue-500/30",
|
|
@@ -68,6 +69,20 @@ function ApiRequestPanel({
|
|
|
68
69
|
return pathErrors.length === 0 && !bodyError;
|
|
69
70
|
}, [pathParams, method, body]);
|
|
70
71
|
const showPayloadEditor = ["POST", "PATCH", "PUT"].includes(method);
|
|
72
|
+
const [sessionOpen, setSessionOpen] = useState(true);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
setSessionOpen(loadState("sessionOpen") ?? true);
|
|
75
|
+
}, []);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
saveState("sessionOpen", sessionOpen);
|
|
78
|
+
}, [sessionOpen]);
|
|
79
|
+
const sessionSummary = useMemo(() => {
|
|
80
|
+
const parts = [authType === "apiKey" ? "API key" : "session"];
|
|
81
|
+
if (bypassMode) parts.push("cross-team");
|
|
82
|
+
if (selectedTeamId) parts.push(selectedTeamId);
|
|
83
|
+
if (actAsUserId.trim()) parts.push(`as ${actAsUserId.trim()}`);
|
|
84
|
+
return parts.join(" \xB7 ");
|
|
85
|
+
}, [authType, bypassMode, selectedTeamId, actAsUserId]);
|
|
71
86
|
const handlePathParamsChange = (params) => {
|
|
72
87
|
onPathParamsChange(params);
|
|
73
88
|
onClearPresetIndicator == null ? void 0 : onClearPresetIndicator("params");
|
|
@@ -127,7 +142,22 @@ function ApiRequestPanel({
|
|
|
127
142
|
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground uppercase tracking-wide mb-1", children: t("urlPreview") }),
|
|
128
143
|
/* @__PURE__ */ jsx("code", { className: "font-mono text-xs break-all text-foreground", children: previewUrl })
|
|
129
144
|
] }),
|
|
130
|
-
/* @__PURE__ */
|
|
145
|
+
/* @__PURE__ */ jsxs(
|
|
146
|
+
"button",
|
|
147
|
+
{
|
|
148
|
+
type: "button",
|
|
149
|
+
onClick: () => setSessionOpen((open) => !open),
|
|
150
|
+
className: "flex w-full items-center gap-2 pt-2 border-t text-left text-xs text-muted-foreground hover:text-foreground",
|
|
151
|
+
"aria-expanded": sessionOpen,
|
|
152
|
+
"data-cy": sel("devtools.apiExplorer.request.toggleSession"),
|
|
153
|
+
children: [
|
|
154
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: cn("h-3.5 w-3.5 transition-transform", sessionOpen && "rotate-90") }),
|
|
155
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Session" }),
|
|
156
|
+
!sessionOpen && /* @__PURE__ */ jsx("span", { className: "truncate", children: sessionSummary })
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
/* @__PURE__ */ jsx("div", { className: cn(!sessionOpen && "hidden"), children: /* @__PURE__ */ jsx(
|
|
131
161
|
AuthSelector,
|
|
132
162
|
{
|
|
133
163
|
authType,
|
|
@@ -141,9 +171,9 @@ function ApiRequestPanel({
|
|
|
141
171
|
onTeamChange,
|
|
142
172
|
onActAsUserChange
|
|
143
173
|
}
|
|
144
|
-
)
|
|
174
|
+
) })
|
|
145
175
|
] }),
|
|
146
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsxs(Tabs, { defaultValue: "params", className: "flex-1 flex flex-col", children: [
|
|
176
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsxs(Tabs, { defaultValue: "params", className: "flex-1 flex flex-col min-h-0", children: [
|
|
147
177
|
/* @__PURE__ */ jsx("div", { className: "border-b px-4", children: /* @__PURE__ */ jsxs(TabsList, { className: "h-9 bg-transparent p-0 border-0", children: [
|
|
148
178
|
/* @__PURE__ */ jsxs(
|
|
149
179
|
TabsTrigger,
|
|
@@ -196,7 +226,7 @@ function ApiRequestPanel({
|
|
|
196
226
|
}
|
|
197
227
|
)
|
|
198
228
|
] }) }),
|
|
199
|
-
/* @__PURE__ */ jsxs(ScrollArea, { className: "flex-1", children: [
|
|
229
|
+
/* @__PURE__ */ jsxs(ScrollArea, { className: "flex-1 min-h-0", children: [
|
|
200
230
|
/* @__PURE__ */ jsxs(TabsContent, { value: "params", className: "m-0 p-4 space-y-4", children: [
|
|
201
231
|
pathParams.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
202
232
|
/* @__PURE__ */ jsx("h4", { className: "text-sm font-medium mb-2", children: "Path Parameters" }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JsonViewer.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/JsonViewer.tsx"],"names":[],"mappings":"AAIA,OAAO,qCAAqC,CAAA;AAM5C,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE,CAAA;AACpD,KAAK,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1C,UAAU,eAAe;IACvB,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,eAAmB,EAAE,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"JsonViewer.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/JsonViewer.tsx"],"names":[],"mappings":"AAIA,OAAO,qCAAqC,CAAA;AAM5C,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE,CAAA;AACpD,KAAK,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1C,UAAU,eAAe;IACvB,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,eAAmB,EAAE,EAAE,eAAe,2CA4BnF;AAED,UAAU,2BAA2B;IACnC,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,WAAW,CAAA;IAC7B,mCAAmC;IACnC,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,SAAS,EACT,eAAmB,EACnB,YAAmB,GACpB,EAAE,2BAA2B,2CAoF7B"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
4
|
import { JsonView, allExpanded, collapseAllNested, defaultStyles, darkStyles } from "react-json-view-lite";
|
|
5
5
|
import "react-json-view-lite/dist/index.css";
|
|
6
6
|
import { useTheme } from "next-themes";
|
|
@@ -10,7 +10,10 @@ import { Button } from "../../ui/button.js";
|
|
|
10
10
|
function JsonViewer({ data, className, initialExpanded = 2 }) {
|
|
11
11
|
const { resolvedTheme } = useTheme();
|
|
12
12
|
const isDark = resolvedTheme === "dark";
|
|
13
|
-
const shouldExpandNode =
|
|
13
|
+
const shouldExpandNode = useMemo(
|
|
14
|
+
() => initialExpanded === "all" ? allExpanded : initialExpanded === "none" ? collapseAllNested : (level) => level < initialExpanded,
|
|
15
|
+
[initialExpanded]
|
|
16
|
+
);
|
|
14
17
|
return /* @__PURE__ */ jsx("div", { className: cn("font-mono text-sm", className), children: /* @__PURE__ */ jsx(
|
|
15
18
|
JsonView,
|
|
16
19
|
{
|
|
@@ -29,19 +32,22 @@ function JsonViewerWithControls({
|
|
|
29
32
|
const { resolvedTheme } = useTheme();
|
|
30
33
|
const isDark = resolvedTheme === "dark";
|
|
31
34
|
const [expandState, setExpandState] = useState(initialExpanded);
|
|
32
|
-
const [
|
|
35
|
+
const [applyNonce, setApplyNonce] = useState(0);
|
|
33
36
|
const handleExpandAll = useCallback(() => {
|
|
34
37
|
setExpandState("all");
|
|
35
|
-
|
|
38
|
+
setApplyNonce((n) => n + 1);
|
|
36
39
|
}, []);
|
|
37
40
|
const handleCollapseAll = useCallback(() => {
|
|
38
41
|
setExpandState("none");
|
|
39
|
-
|
|
42
|
+
setApplyNonce((n) => n + 1);
|
|
40
43
|
}, []);
|
|
41
44
|
const handleCopy = useCallback(() => {
|
|
42
45
|
navigator.clipboard.writeText(JSON.stringify(data, null, 2));
|
|
43
46
|
}, [data]);
|
|
44
|
-
const shouldExpandNode =
|
|
47
|
+
const shouldExpandNode = useMemo(
|
|
48
|
+
() => expandState === "all" ? allExpanded : expandState === "none" ? collapseAllNested : (level) => level < expandState,
|
|
49
|
+
[expandState, applyNonce]
|
|
50
|
+
);
|
|
45
51
|
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
|
|
46
52
|
showControls && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 mb-2", "data-cy": "json-viewer-controls", children: [
|
|
47
53
|
/* @__PURE__ */ jsxs(
|
|
@@ -94,8 +100,7 @@ function JsonViewerWithControls({
|
|
|
94
100
|
data,
|
|
95
101
|
shouldExpandNode,
|
|
96
102
|
style: isDark ? darkStyles : defaultStyles
|
|
97
|
-
}
|
|
98
|
-
viewKey
|
|
103
|
+
}
|
|
99
104
|
) })
|
|
100
105
|
] });
|
|
101
106
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
interface PanelDividerProps {
|
|
2
|
+
/** Current split, as the percentage of the container the LEFT panel takes. */
|
|
3
|
+
value: number;
|
|
4
|
+
onChange: (percent: number) => void;
|
|
5
|
+
/** The element the percentage is measured against. */
|
|
6
|
+
containerRef: React.RefObject<HTMLElement | null>;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
label?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Drag handle between two horizontally-split panels.
|
|
13
|
+
*
|
|
14
|
+
* Hand-rolled rather than pulled from a panel library: it needs one number, and a devtools
|
|
15
|
+
* screen is not worth a dependency in every app the framework ships.
|
|
16
|
+
*
|
|
17
|
+
* It reports a PERCENTAGE, not pixels, so a split chosen on a wide monitor still looks
|
|
18
|
+
* deliberate on a laptop. Pointer capture means a fast drag that outruns the cursor keeps
|
|
19
|
+
* working instead of being dropped the moment the pointer leaves the 6px strip.
|
|
20
|
+
*
|
|
21
|
+
* It is also a real `separator` widget: focusable, moved with the arrow keys, and it reports
|
|
22
|
+
* its position — dragging cannot be the only way to reach a layout.
|
|
23
|
+
*/
|
|
24
|
+
export declare function PanelDivider({ value, onChange, containerRef, min, max, label, }: PanelDividerProps): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=PanelDivider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PanelDivider.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/PanelDivider.tsx"],"names":[],"mappings":"AAMA,UAAU,iBAAiB;IACzB,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,sDAAsD;IACtD,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IACjD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,GAAQ,EACR,GAAQ,EACR,KAAuB,GACxB,EAAE,iBAAiB,2CAiGnB"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
4
|
+
import { cn } from "../../../lib/utils/index.js";
|
|
5
|
+
import { sel } from "../../../lib/test/index.js";
|
|
6
|
+
function PanelDivider({
|
|
7
|
+
value,
|
|
8
|
+
onChange,
|
|
9
|
+
containerRef,
|
|
10
|
+
min = 25,
|
|
11
|
+
max = 75,
|
|
12
|
+
label = "Resize panels"
|
|
13
|
+
}) {
|
|
14
|
+
const draggingRef = useRef(false);
|
|
15
|
+
const clamp = useCallback((percent) => Math.min(max, Math.max(min, percent)), [min, max]);
|
|
16
|
+
const percentFromClientX = useCallback(
|
|
17
|
+
(clientX) => {
|
|
18
|
+
const container = containerRef.current;
|
|
19
|
+
if (!container) return null;
|
|
20
|
+
const rect = container.getBoundingClientRect();
|
|
21
|
+
if (rect.width === 0) return null;
|
|
22
|
+
return clamp((clientX - rect.left) / rect.width * 100);
|
|
23
|
+
},
|
|
24
|
+
[containerRef, clamp]
|
|
25
|
+
);
|
|
26
|
+
const handlePointerDown = useCallback((event) => {
|
|
27
|
+
draggingRef.current = true;
|
|
28
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
29
|
+
document.body.style.userSelect = "none";
|
|
30
|
+
document.body.style.cursor = "col-resize";
|
|
31
|
+
}, []);
|
|
32
|
+
const handlePointerMove = useCallback(
|
|
33
|
+
(event) => {
|
|
34
|
+
if (!draggingRef.current) return;
|
|
35
|
+
const next = percentFromClientX(event.clientX);
|
|
36
|
+
if (next !== null) onChange(next);
|
|
37
|
+
},
|
|
38
|
+
[percentFromClientX, onChange]
|
|
39
|
+
);
|
|
40
|
+
const endDrag = useCallback((event) => {
|
|
41
|
+
if (!draggingRef.current) return;
|
|
42
|
+
draggingRef.current = false;
|
|
43
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
44
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
45
|
+
}
|
|
46
|
+
document.body.style.userSelect = "";
|
|
47
|
+
document.body.style.cursor = "";
|
|
48
|
+
}, []);
|
|
49
|
+
useEffect(
|
|
50
|
+
() => () => {
|
|
51
|
+
document.body.style.userSelect = "";
|
|
52
|
+
document.body.style.cursor = "";
|
|
53
|
+
},
|
|
54
|
+
[]
|
|
55
|
+
);
|
|
56
|
+
const handleKeyDown = useCallback(
|
|
57
|
+
(event) => {
|
|
58
|
+
const step = event.shiftKey ? 10 : 2;
|
|
59
|
+
if (event.key === "ArrowLeft") {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
onChange(clamp(value - step));
|
|
62
|
+
} else if (event.key === "ArrowRight") {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
onChange(clamp(value + step));
|
|
65
|
+
} else if (event.key === "Home") {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
onChange(min);
|
|
68
|
+
} else if (event.key === "End") {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
onChange(max);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
[value, onChange, clamp, min, max]
|
|
74
|
+
);
|
|
75
|
+
return /* @__PURE__ */ jsx(
|
|
76
|
+
"div",
|
|
77
|
+
{
|
|
78
|
+
role: "separator",
|
|
79
|
+
"aria-orientation": "vertical",
|
|
80
|
+
"aria-label": label,
|
|
81
|
+
"aria-valuenow": Math.round(value),
|
|
82
|
+
"aria-valuemin": min,
|
|
83
|
+
"aria-valuemax": max,
|
|
84
|
+
tabIndex: 0,
|
|
85
|
+
onPointerDown: handlePointerDown,
|
|
86
|
+
onPointerMove: handlePointerMove,
|
|
87
|
+
onPointerUp: endDrag,
|
|
88
|
+
onPointerCancel: endDrag,
|
|
89
|
+
onKeyDown: handleKeyDown,
|
|
90
|
+
className: cn(
|
|
91
|
+
"hidden lg:flex shrink-0 w-1.5 cursor-col-resize items-center justify-center",
|
|
92
|
+
"bg-border/40 hover:bg-primary/60 focus-visible:bg-primary/60 transition-colors",
|
|
93
|
+
"focus-visible:outline-none"
|
|
94
|
+
),
|
|
95
|
+
"data-cy": sel("devtools.apiExplorer.panelDivider"),
|
|
96
|
+
children: /* @__PURE__ */ jsx("div", { className: "h-8 w-0.5 rounded-full bg-foreground/25" })
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
PanelDivider
|
|
102
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PresetsTab.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/PresetsTab.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"PresetsTab.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/PresetsTab.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AA0CrD,UAAU,eAAe;IACvB,eAAe,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAC1C,aAAa,EAAE,UAAU,CAAA;IACzB,aAAa,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CAC3C;AAED,wBAAgB,UAAU,CAAC,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,EAAE,eAAe,2CAgI5F"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { Play, Eye, BookOpen } from "lucide-react";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Play, Eye, BookOpen, RefreshCw } from "lucide-react";
|
|
5
5
|
import { Button } from "../../ui/button.js";
|
|
6
6
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../../ui/card.js";
|
|
7
7
|
import { Badge } from "../../ui/badge.js";
|
|
@@ -15,6 +15,38 @@ import {
|
|
|
15
15
|
} from "../../ui/dialog.js";
|
|
16
16
|
import { ScrollArea } from "../../ui/scroll-area.js";
|
|
17
17
|
import { sel } from "../../../lib/test/index.js";
|
|
18
|
+
import { readPresetRunToken, regeneratePresetRunToken } from "./preset-placeholders.js";
|
|
19
|
+
function RunTokenBar({ presets }) {
|
|
20
|
+
const [token, setToken] = useState(null);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setToken(readPresetRunToken());
|
|
23
|
+
}, []);
|
|
24
|
+
const usesRunToken = presets.some((preset) => JSON.stringify(preset).includes("{{RUN}}"));
|
|
25
|
+
if (!usesRunToken || !token) return null;
|
|
26
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-md border bg-muted/40 px-3 py-2", children: [
|
|
27
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Run" }),
|
|
28
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-xs text-foreground", children: token }),
|
|
29
|
+
/* @__PURE__ */ jsxs(
|
|
30
|
+
Button,
|
|
31
|
+
{
|
|
32
|
+
variant: "ghost",
|
|
33
|
+
size: "sm",
|
|
34
|
+
className: "h-6 px-2",
|
|
35
|
+
onClick: () => setToken(regeneratePresetRunToken()),
|
|
36
|
+
"data-cy": sel("devtools.apiExplorer.presets.regenerateRunToken"),
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: "h-3 w-3 mr-1" }),
|
|
39
|
+
"New run"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
),
|
|
43
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
44
|
+
"Every ",
|
|
45
|
+
"{{RUN}}",
|
|
46
|
+
" in these presets resolves to this. Start a new run to send values nothing has used before."
|
|
47
|
+
] })
|
|
48
|
+
] });
|
|
49
|
+
}
|
|
18
50
|
function PresetsTab({ endpointPresets, currentMethod, onApplyPreset }) {
|
|
19
51
|
const [selectedPreset, setSelectedPreset] = useState(null);
|
|
20
52
|
const [isDetailOpen, setIsDetailOpen] = useState(false);
|
|
@@ -44,6 +76,7 @@ function PresetsTab({ endpointPresets, currentMethod, onApplyPreset }) {
|
|
|
44
76
|
};
|
|
45
77
|
return /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-4", "data-cy": sel("devtools.apiExplorer.presets.tab"), children: [
|
|
46
78
|
endpointPresets.summary && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: endpointPresets.summary }),
|
|
79
|
+
/* @__PURE__ */ jsx(RunTokenBar, { presets: endpointPresets.presets }),
|
|
47
80
|
filteredPresets.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground p-4 bg-muted/30 rounded-lg", children: [
|
|
48
81
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
49
82
|
"No presets for ",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placeholders a preset payload can carry, resolved when the preset is APPLIED.
|
|
3
|
+
*
|
|
4
|
+
* A write preset is usually single-use: it creates something with an email, a reference or a
|
|
5
|
+
* slug that cannot be reused, so the second press either replays the first or is refused, and
|
|
6
|
+
* whoever is testing has to hand-edit the values before every run.
|
|
7
|
+
*
|
|
8
|
+
* Some presets need the opposite, though: one proving an idempotent replay has to send the value
|
|
9
|
+
* the previous request sent, and one proving that a reference cannot be reused across two buyers
|
|
10
|
+
* has to reuse it. Resolve a token per REQUEST and those stop testing anything while still
|
|
11
|
+
* answering 200, which reads as a pass.
|
|
12
|
+
*
|
|
13
|
+
* So there are two kinds of token, and which one a preset wants depends on what it is proving:
|
|
14
|
+
*
|
|
15
|
+
* - `{{UNIQUE}}` is fresh on every apply. This is the default for a preset that creates
|
|
16
|
+
* something: press play, get values nobody has used, press play again, get another set. Two
|
|
17
|
+
* people testing at the same time never collide with each other either.
|
|
18
|
+
* - `{{RUN}}` is stable until someone starts a new run, and shared by every preset of the
|
|
19
|
+
* endpoint. This is for the rarer case where two DIFFERENT presets have to meet on the same
|
|
20
|
+
* value — a reference one preset creates and another tries to reuse.
|
|
21
|
+
*
|
|
22
|
+
* Resolution happens when the preset is APPLIED, not when the request is sent, and that is what
|
|
23
|
+
* lets a single preset prove an idempotent replay even with `{{UNIQUE}}`: apply once, then send
|
|
24
|
+
* twice. The second send reuses the body already in the editor, which is exactly what a retried
|
|
25
|
+
* webhook does.
|
|
26
|
+
*
|
|
27
|
+
* Every token resolves once per apply, so an email and a reference in the same payload agree
|
|
28
|
+
* with each other.
|
|
29
|
+
*/
|
|
30
|
+
/** Token → value for a single apply. Built once so every occurrence resolves identically. */
|
|
31
|
+
export interface PresetPlaceholderValues {
|
|
32
|
+
UNIQUE: string;
|
|
33
|
+
RUN: string;
|
|
34
|
+
TIMESTAMP: string;
|
|
35
|
+
UUID: string;
|
|
36
|
+
FIRST_TEAM_ID: string;
|
|
37
|
+
}
|
|
38
|
+
export declare const PRESET_RUN_TOKEN_KEY = "devtools.presetRunToken";
|
|
39
|
+
/** The current run token, minting one on first use. Survives reloads: a run outlives a refresh. */
|
|
40
|
+
export declare function readPresetRunToken(): string;
|
|
41
|
+
/** Starts a new run. Presets applied from here on get values nothing has used before. */
|
|
42
|
+
export declare function regeneratePresetRunToken(): string;
|
|
43
|
+
export declare function createPresetPlaceholderValues(): PresetPlaceholderValues;
|
|
44
|
+
/**
|
|
45
|
+
* Replaces every known token inside a preset value, walking strings, arrays and objects.
|
|
46
|
+
*
|
|
47
|
+
* An unknown token is left exactly as written rather than blanked: a preset that names something
|
|
48
|
+
* this version does not provide should show that in the editor, where it can be read and fixed,
|
|
49
|
+
* instead of silently sending an empty string that the endpoint then rejects for the wrong reason.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolvePresetPlaceholders<T>(value: T, values?: PresetPlaceholderValues): T;
|
|
52
|
+
//# sourceMappingURL=preset-placeholders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preset-placeholders.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/preset-placeholders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,6FAA6F;AAC7F,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,eAAO,MAAM,oBAAoB,4BAA4B,CAAA;AAgB7D,mGAAmG;AACnG,wBAAgB,kBAAkB,IAAI,MAAM,CAO3C;AAED,yFAAyF;AACzF,wBAAgB,wBAAwB,IAAI,MAAM,CAMjD;AAOD,wBAAgB,6BAA6B,IAAI,uBAAuB,CASvE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EACzC,KAAK,EAAE,CAAC,EACR,MAAM,GAAE,uBAAyD,GAChE,CAAC,CAkBH"}
|