UNPKG

sanity

Version:

Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches

935 lines (934 loc) 42.2 kB
"use strict"; var jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), React = require("react"), sanity = require("sanity"), StructureToolProvider = require("./StructureToolProvider.js"), router = require("sanity/router"), omit = require("lodash/omit.js"), rxjs = require("rxjs"), nanoid = require("nanoid"), operators = require("rxjs/operators"), generateHelpUrl = require("@sanity/generate-help-url"), isEqual = require("lodash/isEqual.js"), uuid = require("@sanity/uuid"), icons = require("@sanity/icons"), styled = require("styled-components"), getJsonStream = require("./getJsonStream.js"); require("lodash/camelCase.js"); require("speakingurl"); require("lodash/uniqueId.js"); require("react-is"); require("lodash/uniq.js"); require("lodash/kebabCase.js"); require("lodash/find.js"); require("lodash/startCase.js"); var isHotkey = require("is-hotkey"), PathUtils = require("@sanity/util/paths"); function _interopDefaultCompat(e) { return e && typeof e == "object" && "default" in e ? e : { default: e }; } var omit__default = /* @__PURE__ */ _interopDefaultCompat(omit), isEqual__default = /* @__PURE__ */ _interopDefaultCompat(isEqual), styled__default = /* @__PURE__ */ _interopDefaultCompat(styled), isHotkey__default = /* @__PURE__ */ _interopDefaultCompat(isHotkey); const emptyArray = []; function PaneRouterProvider(props) { const { children, flatIndex, index, params, payload, siblingIndex } = props, { navigate, navigateIntent, resolvePathFromState } = router.useRouter(), routerState = router.useRouterState(), { panes, expand } = StructureToolProvider.usePaneLayout(), routerPaneGroups = React.useMemo( () => (routerState == null ? void 0 : routerState.panes) || emptyArray, [routerState == null ? void 0 : routerState.panes] ), lastPane = React.useMemo(() => panes == null ? void 0 : panes[panes.length - 2], [panes]), groupIndex = index - 1, createNextRouterState = React.useCallback( (modifier) => { const currentGroup = routerPaneGroups[groupIndex] || [], currentItem = currentGroup[siblingIndex], nextGroup = modifier(currentGroup, currentItem), nextPanes = [ ...routerPaneGroups.slice(0, groupIndex), nextGroup, ...routerPaneGroups.slice(groupIndex + 1) ]; return { ...routerState || {}, panes: nextPanes }; }, [groupIndex, routerPaneGroups, routerState, siblingIndex] ), modifyCurrentGroup = React.useCallback( (modifier) => { const nextRouterState = createNextRouterState(modifier); return setTimeout(() => navigate(nextRouterState), 0), nextRouterState; }, [createNextRouterState, navigate] ), createPathWithParams = React.useCallback( (nextParams) => { const nextRouterState = createNextRouterState((siblings, item) => [ ...siblings.slice(0, siblingIndex), { ...item, params: nextParams }, ...siblings.slice(siblingIndex + 1) ]); return resolvePathFromState(nextRouterState); }, [createNextRouterState, resolvePathFromState, siblingIndex] ), setPayload = React.useCallback( (nextPayload) => { modifyCurrentGroup((siblings, item) => [ ...siblings.slice(0, siblingIndex), { ...item, payload: nextPayload }, ...siblings.slice(siblingIndex + 1) ]); }, [modifyCurrentGroup, siblingIndex] ), setParams = React.useCallback( (nextParams) => { modifyCurrentGroup((siblings, item) => [ ...siblings.slice(0, siblingIndex), { ...item, params: nextParams }, ...siblings.slice(siblingIndex + 1) ]); }, [modifyCurrentGroup, siblingIndex] ), handleEditReference = React.useCallback( ({ id, parentRefPath, type, template }) => { navigate({ panes: [ ...routerPaneGroups.slice(0, groupIndex + 1), [ { id, params: { template: template.id, parentRefPath: PathUtils.toString(parentRefPath), type }, payload: template.params } ] ] }); }, [groupIndex, navigate, routerPaneGroups] ), ctx = React.useMemo( () => ({ // Zero-based index (position) of pane, visually index: flatIndex, // Zero-based index of pane group (within URL structure) groupIndex, // Zero-based index of pane within sibling group siblingIndex, // Payload of the current pane payload, // Params of the current pane params, // Whether or not the pane has any siblings (within the same group) hasGroupSiblings: routerPaneGroups[groupIndex] ? routerPaneGroups[groupIndex].length > 1 : !1, // The length of the current group groupLength: routerPaneGroups[groupIndex] ? routerPaneGroups[groupIndex].length : 0, // Current router state for the "panes" property routerPanesState: routerPaneGroups, // Curried StateLink that passes the correct state automatically ChildLink: StructureToolProvider.ChildLink, // Curried StateLink that pops off the last pane group // Only pass if this is not the first pane BackLink: flatIndex ? StructureToolProvider.BackLink : void 0, // A specialized `ChildLink` that takes in the needed props to open a // referenced document to the right ReferenceChildLink: StructureToolProvider.ReferenceChildLink, // Similar to `ReferenceChildLink` expect without the wrapping component handleEditReference, // Curried StateLink that passed the correct state, but merges params/payload ParameterizedLink: StructureToolProvider.ParameterizedLink, // Replaces the current pane with a new one replaceCurrent: (opts = {}) => { modifyCurrentGroup(() => [ { id: opts.id || "", payload: opts.payload, params: opts.params || {} } ]); }, // Removes the current pane from the group closeCurrent: () => { modifyCurrentGroup( (siblings, item) => siblings.length > 1 ? siblings.filter((sibling) => sibling !== item) : siblings ); }, // Removes all panes to the right including current closeCurrentAndAfter: (expandLast = !0) => { expandLast && lastPane && expand(lastPane.element), navigate( { panes: [...routerPaneGroups.slice(0, groupIndex)] }, { replace: !0 } ); }, // Duplicate the current pane, with optional overrides for payload, parameters duplicateCurrent: (options) => { modifyCurrentGroup((siblings, item) => { const duplicatedItem = { ...item, payload: (options == null ? void 0 : options.payload) || item.payload, params: (options == null ? void 0 : options.params) || item.params }; return [ ...siblings.slice(0, siblingIndex), duplicatedItem, ...siblings.slice(siblingIndex) ]; }); }, // Set the view for the current pane setView: (viewId) => { const restParams = omit__default.default(params, "view"); return setParams(viewId ? { ...restParams, view: viewId } : restParams); }, // Set the parameters for the current pane setParams, // Set the payload for the current pane setPayload, // A function that returns a path with the given parameters createPathWithParams, // Proxied navigation to a given intent. Consider just exposing `router` instead? navigateIntent }), [ flatIndex, groupIndex, siblingIndex, payload, params, routerPaneGroups, handleEditReference, setParams, setPayload, createPathWithParams, navigateIntent, modifyCurrentGroup, lastPane, navigate, expand ] ); return /* @__PURE__ */ jsxRuntime.jsx(StructureToolProvider.PaneRouterContext.Provider, { value: ctx, children }); } class PaneResolutionError extends Error { constructor({ message, context, helpId, cause }) { super(message), this.context = context, this.helpId = helpId, this.cause = cause; } } const randomIdCache = /* @__PURE__ */ new WeakMap(); function assignId(obj) { const cachedValue = randomIdCache.get(obj); if (cachedValue) return cachedValue; const id = nanoid.nanoid(); return randomIdCache.set(obj, id), id; } const isPromise = (thing) => !!thing && typeof (thing == null ? void 0 : thing.then) == "function", isSerializable = (thing) => sanity.isRecord(thing) ? typeof thing.serialize == "function" : !1, rethrowWithPaneResolutionErrors = (next) => (unresolvedPane, context, flatIndex) => { try { return next(unresolvedPane, context, flatIndex); } catch (e) { throw e instanceof PaneResolutionError ? e : new PaneResolutionError({ message: typeof (e == null ? void 0 : e.message) == "string" ? e.message : "", context, cause: e }); } }, wrapWithPublishReplay = (next) => (...args) => next(...args).pipe( // need to add publishReplay + refCount to ensure new subscribers always // get an emission. without this, memoized observables may get stuck // waiting for their first emissions resulting in a loading pane operators.publishReplay(1), operators.refCount() ); function createPaneResolver(middleware) { const resolvePane = rethrowWithPaneResolutionErrors( wrapWithPublishReplay( middleware((unresolvedPane, context, flatIndex) => { if (!unresolvedPane) throw new PaneResolutionError({ message: "Pane returned no child", context, helpId: "structure-item-returned-no-child" }); return isPromise(unresolvedPane) || rxjs.isObservable(unresolvedPane) ? rxjs.from(unresolvedPane).pipe( operators.switchMap((result) => resolvePane(result, context, flatIndex)) ) : isSerializable(unresolvedPane) ? resolvePane(unresolvedPane.serialize(context), context, flatIndex) : typeof unresolvedPane == "function" ? resolvePane(unresolvedPane(context.id, context), context, flatIndex) : rxjs.of(unresolvedPane); }) ) ); return resolvePane; } const bindCache = /* @__PURE__ */ new WeakMap(); function memoBind(obj, methodKey) { const boundMethods = bindCache.get(obj) || /* @__PURE__ */ new Map(); if (boundMethods) { const bound2 = boundMethods.get(methodKey); if (bound2) return bound2; } const method = obj[methodKey]; if (typeof method != "function") throw new Error( `Expected property \`${methodKey}\` to be a function but got ${typeof method} instead.` ); const bound = method.bind(obj); return boundMethods.set(methodKey, bound), bindCache.set(obj, boundMethods), bound; } async function resolveIntent(options) { const resolvedPaneCache = /* @__PURE__ */ new Map(), resolvePane = createPaneResolver((nextFn) => (unresolvedPane, context, flatIndex) => { const key = unresolvedPane && `${assignId(unresolvedPane)}-${context.path.join("__")}`, cachedResolvedPane = key && resolvedPaneCache.get(key); if (cachedResolvedPane) return cachedResolvedPane; const result = nextFn(unresolvedPane, context, flatIndex); return key && resolvedPaneCache.set(key, result), result; }), fallbackEditorPanes = [ [ { id: `__edit__${options.params.id}`, params: { ...omit__default.default(options.params, ["id"]), type: options.params.type }, payload: options.payload } ] ]; async function traverse({ currentId, flatIndex, intent, params, parent: parent2, path, payload, unresolvedPane, levelIndex, structureContext }) { var _a; if (!unresolvedPane) return []; const { id: targetId, type: schemaTypeName, ...otherParams } = params, resolvedPane = await rxjs.firstValueFrom(resolvePane(unresolvedPane, { id: currentId, splitIndex: 0, parent: parent2, path, index: flatIndex, params: {}, payload: void 0, structureContext }, flatIndex)); return resolvedPane.type === "document" && resolvedPane.id === targetId ? [ { panes: [ ...path.slice(0, path.length - 1).map((i) => [{ id: i }]), [{ id: targetId, params: otherParams, payload }] ], depthIndex: path.length, levelIndex } ] : ( // if the resolve pane's `canHandleIntent` returns true, then resolve (_a = resolvedPane.canHandleIntent) != null && _a.call(resolvedPane, intent, params, { pane: resolvedPane, index: flatIndex }) || // if the pane's `canHandleIntent` did not return true, then match against // this default case. we will resolve the intent if: resolvedPane.type === "documentList" && // 1. the schema type matches (this required for the document to render) resolvedPane.schemaTypeName === schemaTypeName && // 2. the filter is the default filter. // // NOTE: this case is to prevent false positive matches where the user // has configured a more specific filter for a particular type. In that // case, the user can implement their own `canHandleIntent` function resolvedPane.options.filter === "_type == $type" ? [ { panes: [ // map the current path to router panes ...path.map((id) => [{ id }]), // then augment with the intents IDs and params [{ id: params.id, params: otherParams, payload }] ], depthIndex: path.length, levelIndex } ] : resolvedPane.type === "list" && resolvedPane.child && resolvedPane.items ? (await Promise.all( resolvedPane.items.map((item, nextLevelIndex) => item.type === "divider" ? Promise.resolve([]) : traverse({ currentId: item._id || item.id, flatIndex: flatIndex + 1, intent, params, parent: resolvedPane, path: [...path, item.id], payload, unresolvedPane: typeof resolvedPane.child == "function" ? memoBind(resolvedPane, "child") : resolvedPane.child, levelIndex: nextLevelIndex, structureContext })) )).flat() : [] ); } const closestPaneToRoot = (await traverse({ currentId: "root", flatIndex: 0, levelIndex: 0, intent: options.intent, params: options.params, parent: null, path: [], payload: options.payload, unresolvedPane: options.rootPaneNode, structureContext: options.structureContext })).sort((a, b) => a.depthIndex === b.depthIndex ? a.levelIndex - b.levelIndex : a.depthIndex - b.depthIndex)[0]; return closestPaneToRoot ? closestPaneToRoot.panes : fallbackEditorPanes; } const fallbackEditorChild = (nodeId, context) => { const id = nodeId.replace(/^__edit__/, ""), { params, payload, structureContext: { resolveDocumentNode } } = context, { type, template } = params; if (!type) throw new Error( `Document type for document with ID ${id} was not provided in the router params.` ); let defaultDocumentBuilder = resolveDocumentNode({ schemaType: type, documentId: id }).id("editor"); return template && (defaultDocumentBuilder = defaultDocumentBuilder.initialValueTemplate( template, payload )), defaultDocumentBuilder.serialize(); }; function hashContext(context) { var _a, _b; return `contextHash(${JSON.stringify({ id: context.id, parentId: parent && assignId(parent), path: context.path, index: context.index, splitIndex: context.splitIndex, serializeOptionsIndex: (_a = context.serializeOptions) == null ? void 0 : _a.index, serializeOptionsPath: (_b = context.serializeOptions) == null ? void 0 : _b.path })})`; } const hashResolvedPaneMeta = (meta) => { const normalized = { type: meta.type, id: meta.routerPaneSibling.id, params: meta.routerPaneSibling.params || {}, payload: meta.routerPaneSibling.payload || null, flatIndex: meta.flatIndex, groupIndex: meta.groupIndex, siblingIndex: meta.siblingIndex, path: meta.path, paneNode: meta.type === "resolvedMeta" ? assignId(meta.paneNode) : null }; return `metaHash(${JSON.stringify(normalized)})`; }; function resolvePaneTree({ unresolvedPane, flattenedRouterPanes, parent: parent2, path, resolvePane, structureContext }) { const [current, ...rest] = flattenedRouterPanes, next = rest[0], context = { id: current.routerPaneSibling.id, splitIndex: current.siblingIndex, parent: parent2, path: [...path, current.routerPaneSibling.id], index: current.flatIndex, params: current.routerPaneSibling.params || {}, payload: current.routerPaneSibling.payload, structureContext }; try { return resolvePane(unresolvedPane, context, current.flatIndex).pipe( // this switch map receives a resolved pane operators.switchMap((paneNode) => { const resolvedPaneMeta = { type: "resolvedMeta", ...current, paneNode, path: context.path }, loadingPanes = rest.map((i, restIndex) => ({ type: "loading", path: [ ...context.path, ...rest.slice(restIndex).map((_, currentIndex) => `[${i.flatIndex + currentIndex}]`) ], paneNode: null, ...i })); if (!rest.length) return rxjs.of([resolvedPaneMeta]); let nextStream; return ( /* the fallback editor case */ next != null && next.routerPaneSibling.id.startsWith("__edit__") ? nextStream = resolvePaneTree({ unresolvedPane: fallbackEditorChild, flattenedRouterPanes: rest, parent: parent2, path: context.path, resolvePane, structureContext }) : current.groupIndex === (next == null ? void 0 : next.groupIndex) ? nextStream = resolvePaneTree({ unresolvedPane, flattenedRouterPanes: rest, parent: parent2, path, resolvePane, structureContext }) : nextStream = resolvePaneTree({ unresolvedPane: typeof paneNode.child == "function" ? memoBind(paneNode, "child") : paneNode.child, flattenedRouterPanes: rest, parent: paneNode, path: context.path, resolvePane, structureContext }), rxjs.concat( // we emit the loading panes first in a concat (this emits immediately) rxjs.of([resolvedPaneMeta, ...loadingPanes]), // then whenever the next stream is done, the results will be combined. nextStream.pipe(operators.map((nextResolvedPanes) => [resolvedPaneMeta, ...nextResolvedPanes])) ) ); }) ); } catch (e) { if (e instanceof PaneResolutionError && (e.context && console.warn( `Pane resolution error at index ${e.context.index}${e.context.splitIndex > 0 ? ` for split pane index ${e.context.splitIndex}` : ""}: ${e.message}${e.helpId ? ` - see ${generateHelpUrl.generateHelpUrl(e.helpId)}` : ""}`, e ), e.helpId === "structure-item-returned-no-child")) return rxjs.of([]); throw e; } } function createResolvedPaneNodeStream({ routerPanesStream, rootPaneNode, initialCacheState = { cacheKeysByFlatIndex: [], flattenedRouterPanes: [], resolvedPaneCache: /* @__PURE__ */ new Map(), resolvePane: () => rxjs.NEVER }, structureContext }) { return routerPanesStream.pipe( // add in implicit "root" router pane operators.map((rawRouterPanes) => [[{ id: "root" }], ...rawRouterPanes]), // create flattened router panes operators.map((routerPanes) => routerPanes.flatMap( (routerPaneGroup, groupIndex) => routerPaneGroup.map((routerPaneSibling, siblingIndex) => ({ routerPaneSibling, groupIndex, siblingIndex })) ).map((i, index) => ({ ...i, flatIndex: index }))), // calculate a "diffIndex" used for clearing the memo cache operators.startWith([]), operators.pairwise(), operators.map(([prev, curr]) => { for (let i = 0; i < curr.length; i++) { const prevValue = prev[i], currValue = curr[i]; if (!isEqual__default.default(prevValue, currValue)) return { flattenedRouterPanes: curr, diffIndex: i }; } return { flattenedRouterPanes: curr, diffIndex: curr.length }; }), // create the memoized `resolvePane` function and manage the memo cache operators.scan((acc, next) => { const { cacheKeysByFlatIndex, resolvedPaneCache } = acc, { flattenedRouterPanes, diffIndex } = next, beforeDiffIndex = cacheKeysByFlatIndex.slice(0, diffIndex + 1), afterDiffIndex = cacheKeysByFlatIndex.slice(diffIndex + 1), keysToKeep = new Set(beforeDiffIndex.flatMap((keySet) => Array.from(keySet))), keysToDelete = afterDiffIndex.flatMap((keySet) => Array.from(keySet)).filter((key) => !keysToKeep.has(key)); for (const key of keysToDelete) resolvedPaneCache.delete(key); return { flattenedRouterPanes, cacheKeysByFlatIndex, resolvedPaneCache, resolvePane: createPaneResolver((nextFn) => (unresolvedPane, context, flatIndex) => { const key = unresolvedPane && `${assignId(unresolvedPane)}-${hashContext(context)}`, cachedResolvedPane = key && resolvedPaneCache.get(key); if (cachedResolvedPane) return cachedResolvedPane; const result = nextFn(unresolvedPane, context, flatIndex); if (!key) return result; const cacheKeySet = cacheKeysByFlatIndex[flatIndex] || /* @__PURE__ */ new Set(); return cacheKeySet.add(key), cacheKeysByFlatIndex[flatIndex] = cacheKeySet, resolvedPaneCache.set(key, result), result; }) }; }, initialCacheState), // run the memoized, recursive resolving operators.switchMap( ({ flattenedRouterPanes, resolvePane }) => resolvePaneTree({ unresolvedPane: rootPaneNode, flattenedRouterPanes, parent: null, path: [], resolvePane, structureContext }) ) ).pipe( // this diffs the previous emission with the current one. if there is a new // loading pane at the same position where a previous pane already had a // resolved value (looking at the IDs to compare), then return the previous // pane instead of the loading pane operators.scan( (prev, next) => next.map((nextPane, index) => { const prevPane = prev[index]; return !prevPane || nextPane.type !== "loading" ? nextPane : prevPane.routerPaneSibling.id === nextPane.routerPaneSibling.id ? prevPane : nextPane; }), [] ), // this prevents duplicate emissions operators.distinctUntilChanged((prev, next) => { if (prev.length !== next.length) return !1; for (let i = 0; i < next.length; i++) { const prevValue = prev[i], nextValue = next[i]; if (hashResolvedPaneMeta(prevValue) !== hashResolvedPaneMeta(nextValue)) return !1; } return !0; }) ); } function useRouterPanesStream() { const routerStateSubject = React.useMemo(() => new rxjs.ReplaySubject(1), []), routerPanes$ = React.useMemo( () => routerStateSubject.asObservable().pipe(operators.map((_routerState) => (_routerState == null ? void 0 : _routerState.panes) || [])), [routerStateSubject] ), { state: routerState } = router.useRouter(); return React.useEffect(() => { routerStateSubject.next(routerState); }, [routerState, routerStateSubject]), routerPanes$; } function useResolvedPanes() { const [error, setError] = React.useState(); if (error) throw error; const { structureContext, rootPaneNode } = StructureToolProvider.useStructureTool(), [data, setData] = React.useState({ paneDataItems: [], resolvedPanes: [], routerPanes: [] }), routerPanesStream = useRouterPanesStream(); return React.useEffect(() => { const subscription = createResolvedPaneNodeStream({ rootPaneNode, routerPanesStream, structureContext }).pipe( operators.map((resolvedPanes) => { const routerPanes = resolvedPanes.reduce((acc, next) => { const currentGroup = acc[next.groupIndex] || []; return currentGroup[next.siblingIndex] = next.routerPaneSibling, acc[next.groupIndex] = currentGroup, acc; }, []), groupsLen = routerPanes.length, paneDataItems = resolvedPanes.map((pane) => { var _a; const { groupIndex, flatIndex, siblingIndex, routerPaneSibling, path } = pane, itemId = routerPaneSibling.id, nextGroup = routerPanes[groupIndex + 1]; return { active: groupIndex === groupsLen - 2, childItemId: (_a = nextGroup == null ? void 0 : nextGroup[0].id) != null ? _a : null, index: flatIndex, itemId: routerPaneSibling.id, groupIndex, key: `${pane.type === "loading" ? "unknown" : pane.paneNode.id}-${itemId}-${siblingIndex}`, pane: pane.type === "loading" ? StructureToolProvider.LOADING_PANE : pane.paneNode, params: routerPaneSibling.params || {}, path: path.join(";"), payload: routerPaneSibling.payload, selected: flatIndex === resolvedPanes.length - 1, siblingIndex }; }); return { paneDataItems, routerPanes, resolvedPanes: paneDataItems.map((pane) => pane.pane) }; }) ).subscribe({ next: (result) => setData(result), error: (e) => setError(e) }); return () => subscription.unsubscribe(); }, [rootPaneNode, routerPanesStream, structureContext]), data; } async function ensureDocumentIdAndType(documentStore, id, type) { if (id && type) return { id, type }; if (!id && type) return { id: uuid.uuid(), type }; if (id && !type) { const resolvedType = await rxjs.firstValueFrom( documentStore.resolveTypeForDocument(id) ); return { id, type: resolvedType }; } throw new PaneResolutionError({ message: "Neither document `id` or `type` was provided when trying to resolve intent." }); } const EMPTY_RECORD = {}, IntentResolver = React.memo(function() { const { navigate } = router.useRouter(), maybeIntent = router.useRouterState( React.useCallback((routerState) => { const intentName = typeof routerState.intent == "string" ? routerState.intent : void 0; return intentName ? { intent: intentName, params: sanity.isRecord(routerState.params) ? routerState.params : EMPTY_RECORD, payload: routerState.payload } : void 0; }, []) ), { rootPaneNode, structureContext } = StructureToolProvider.useStructureTool(), documentStore = sanity.useDocumentStore(), [error, setError] = React.useState(null); if (error) throw error; return React.useEffect(() => { if (maybeIntent) { const { intent, params, payload } = maybeIntent; let cancelled = !1; async function effect() { const { id, type } = await ensureDocumentIdAndType( documentStore, typeof params.id == "string" ? params.id : void 0, typeof params.type == "string" ? params.type : void 0 ); if (cancelled) return; const panes = await resolveIntent({ intent, params: { ...params, id, type }, payload, rootPaneNode, structureContext }); cancelled || navigate({ panes }, { replace: !0 }); } return effect().catch(setError), () => { cancelled = !0; }; } }, [documentStore, maybeIntent, navigate, rootPaneNode, structureContext]), null; }), PathSegment = styled__default.default.span` &:not(:last-child)::after { content: ' ➝ '; opacity: 0.5; } `; function formatStack(stack) { return stack.replace(/\(\.\.\.\)\./g, `(...) .`).replace(/__WEBPACK_IMPORTED_MODULE_\d+_+/g, "").replace(/___default\./g, ".").replace(new RegExp(` \\(https?:\\/\\/${window.location.host}`, "g"), " ("); } function StructureError({ error }) { if (!(error instanceof PaneResolutionError)) throw error; const { cause } = error, { t } = sanity.useTranslation(StructureToolProvider.structureLocaleNamespace), stack = (cause == null ? void 0 : cause.stack) || error.stack, showStack = stack && !(cause instanceof StructureToolProvider.SerializeError) && !error.message.includes("Module build failed:"), path = cause instanceof StructureToolProvider.SerializeError ? cause.path : [], helpId = cause instanceof StructureToolProvider.SerializeError && cause.helpId || error.helpId, handleReload = React.useCallback(() => { window.location.reload(); }, []); return /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { height: "fill", overflow: "auto", padding: 4, sizing: "border", tone: "critical", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { as: "h2", children: t("structure-error.header.text") }), /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { marginTop: 4, padding: 4, radius: 2, overflow: "auto", shadow: 1, tone: "inherit", children: [ path.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "medium", children: t("structure-error.structure-path.label") }), /* @__PURE__ */ jsxRuntime.jsx(ui.Code, { children: path.slice(1).map((segment, i) => ( // eslint-disable-next-line react/no-array-index-key /* @__PURE__ */ jsxRuntime.jsx(PathSegment, { children: segment }, `${segment}-${i}`) )) }) ] }), /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { marginTop: 4, space: 2, children: [ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "medium", children: t("structure-error.error.label") }), /* @__PURE__ */ jsxRuntime.jsx(ui.Code, { children: showStack ? formatStack(stack) : error.message }) ] }), helpId && /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: generateHelpUrl.generateHelpUrl(helpId), rel: "noopener noreferrer", target: "_blank", children: t("structure-error.docs-link.text") }) }) }), /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx( getJsonStream.Button, { text: t("structure-error.reload-button.text"), icon: icons.SyncIcon, tone: "primary", onClick: handleReload } ) }) ] }) ] }) }); } function UnknownPane(props) { const { isSelected, pane, paneKey } = props, type = sanity.isRecord(pane) && pane.type || null, { t } = sanity.useTranslation(StructureToolProvider.structureLocaleNamespace); return /* @__PURE__ */ jsxRuntime.jsxs(StructureToolProvider.Pane, { id: paneKey, selected: isSelected, children: [ /* @__PURE__ */ jsxRuntime.jsx(StructureToolProvider.PaneHeader, { title: t("panes.unknown-pane-type.title") }), /* @__PURE__ */ jsxRuntime.jsx(StructureToolProvider.PaneContent, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { padding: 4, children: typeof type == "string" ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "p", muted: !0, children: /* @__PURE__ */ jsxRuntime.jsx( sanity.Translate, { t, i18nKey: "panes.unknown-pane-type.unknown-type.text", values: { type } } ) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "p", muted: !0, children: /* @__PURE__ */ jsxRuntime.jsx(sanity.Translate, { t, i18nKey: "panes.unknown-pane-type.missing-type.text" }) }) }) }) ] }); } const paneMap = { component: React.lazy(() => Promise.resolve().then(function() { return require("./index.js"); })), document: React.lazy(() => Promise.resolve().then(function() { return require("./pane.js"); })), documentList: React.lazy(() => Promise.resolve().then(function() { return require("./pane2.js"); })), list: React.lazy(() => Promise.resolve().then(function() { return require("./index2.js"); })) }, StructureToolPane = React.memo( function(props) { const { active, childItemId, groupIndex, index, itemId, pane, paneKey, params, payload, path, selected, siblingIndex } = props, PaneComponent = paneMap[pane.type] || UnknownPane; return /* @__PURE__ */ jsxRuntime.jsx( PaneRouterProvider, { flatIndex: index, index: groupIndex, params, payload, siblingIndex, children: /* @__PURE__ */ jsxRuntime.jsx(React.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(StructureToolProvider.LoadingPane, { paneKey, path, selected }), children: /* @__PURE__ */ jsxRuntime.jsx( PaneComponent, { childItemId: childItemId || "", index, itemId, isActive: active, isSelected: selected, paneKey, pane } ) }) } ); }, ({ params: prevParams = {}, payload: prevPayload = null, ...prev }, { params: nextParams = {}, payload: nextPayload = null, ...next }) => { if (!isEqual__default.default(prevParams, nextParams) || !isEqual__default.default(prevPayload, nextPayload)) return !1; const keys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]); for (const key of keys) if (prev[key] !== next[key]) return !1; return !0; } ); function NoDocumentTypesScreen() { const { t } = sanity.useTranslation(StructureToolProvider.structureLocaleNamespace); return /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { height: "fill", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { align: "center", height: "fill", justify: "center", padding: 4, sizing: "border", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { width: 0, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: 4, radius: 2, shadow: 1, tone: "caution", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { children: [ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, children: /* @__PURE__ */ jsxRuntime.jsx(icons.WarningOutlineIcon, {}) }) }), /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { flex: 1, marginLeft: 3, space: 3, children: [ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "h1", size: 1, weight: "medium", children: t("no-document-types-screen.title") }), /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "p", muted: !0, size: 1, children: t("no-document-types-screen.subtitle") }), /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "p", muted: !0, size: 1, children: /* @__PURE__ */ jsxRuntime.jsx( "a", { href: "https://www.sanity.io/docs/create-a-schema-and-configure-sanity-studio", target: "_blank", rel: "noreferrer", children: t("no-document-types-screen.link-text") } ) }) ] }) ] }) }) }) }) }); } const DocumentTitle = (props) => { const { documentId, documentType } = props, editState = sanity.useEditState(documentId, documentType), schema = sanity.useSchema(), { t } = sanity.useTranslation(StructureToolProvider.structureLocaleNamespace), isNewDocument = !(editState != null && editState.published) && !(editState != null && editState.draft), documentValue = (editState == null ? void 0 : editState.draft) || (editState == null ? void 0 : editState.published), schemaType = schema.get(documentType), { value, isLoading: previewValueIsLoading } = sanity.unstable_useValuePreview({ enabled: !0, schemaType, value: documentValue }), documentTitle = isNewDocument ? t("browser-document-title.new-document", { schemaType: (schemaType == null ? void 0 : schemaType.title) || (schemaType == null ? void 0 : schemaType.name) }) : (value == null ? void 0 : value.title) || t("browser-document-title.untitled-document"), settled = editState.ready && !previewValueIsLoading, newTitle = useConstructDocumentTitle(documentTitle); return React.useEffect(() => { settled && (document.title = newTitle); }, [documentTitle, settled, newTitle]), null; }, PassthroughTitle = (props) => { const { title } = props, newTitle = useConstructDocumentTitle(title); return React.useEffect(() => { document.title = newTitle; }, [newTitle, title]), null; }, StructureTitle = (props) => { const { resolvedPanes } = props; if (!(resolvedPanes != null && resolvedPanes.length)) return null; const lastPane = resolvedPanes[resolvedPanes.length - 1]; return isLoadingPane(lastPane) ? /* @__PURE__ */ jsxRuntime.jsx(PassthroughTitle, {}) : isDocumentPane(lastPane) ? lastPane != null && lastPane.title ? /* @__PURE__ */ jsxRuntime.jsx(PassthroughTitle, { title: lastPane.title }) : /* @__PURE__ */ jsxRuntime.jsx(DocumentTitle, { documentId: lastPane.options.id, documentType: lastPane.options.type }) : /* @__PURE__ */ jsxRuntime.jsx(PassthroughTitle, { title: lastPane == null ? void 0 : lastPane.title }); }; function useConstructDocumentTitle(activeTitle) { const structureToolBaseTitle = StructureToolProvider.useStructureTool().structureContext.title; return [activeTitle, structureToolBaseTitle].filter((title) => title).join(" | "); } function isDocumentPane(pane) { return pane !== StructureToolProvider.LOADING_PANE && pane.type === "document"; } function isLoadingPane(pane) { return pane === StructureToolProvider.LOADING_PANE; } const StyledPaneLayout = styled__default.default(StructureToolProvider.PaneLayout)` min-height: 100%; min-width: 320px; `, isSaveHotkey = isHotkey__default.default("mod+s"), StructureTool = React.memo(function({ onPaneChange }) { var _a; const { push: pushToast } = ui.useToast(), schema = sanity.useSchema(), { layoutCollapsed, setLayoutCollapsed } = StructureToolProvider.useStructureTool(), { paneDataItems, resolvedPanes } = useResolvedPanes(), isResolvingIntent = router.useRouterState( React.useCallback((routerState) => typeof routerState.intent == "string", []) ), { sanity: { media } } = ui.useTheme(), [portalElement, setPortalElement] = React.useState(null), handleRootCollapse = React.useCallback(() => setLayoutCollapsed(!0), [setLayoutCollapsed]), handleRootExpand = React.useCallback(() => setLayoutCollapsed(!1), [setLayoutCollapsed]); return React.useEffect(() => { resolvedPanes.length && onPaneChange(resolvedPanes); }, [onPaneChange, resolvedPanes]), React.useEffect(() => { const handleGlobalKeyDown = (event) => { isSaveHotkey(event) && (event.preventDefault(), pushToast({ closable: !0, id: "auto-save-message", status: "info", title: "Your work is automatically saved!", duration: 4e3 })); }; return window.addEventListener("keydown", handleGlobalKeyDown), () => window.removeEventListener("keydown", handleGlobalKeyDown); }, [pushToast]), (_a = schema._original) != null && _a.types.some(sanity._isCustomDocumentTypeDefinition) ? /* @__PURE__ */ jsxRuntime.jsxs(ui.PortalProvider, { element: portalElement || null, children: [ /* @__PURE__ */ jsxRuntime.jsxs( StyledPaneLayout, { flex: 1, height: layoutCollapsed ? void 0 : "fill", minWidth: media[1], onCollapse: handleRootCollapse, onExpand: handleRootExpand, children: [ paneDataItems.map( ({ active, childItemId, groupIndex, itemId, key: paneKey, pane, index: paneIndex, params: paneParams, path, payload, siblingIndex, selected }) => /* @__PURE__ */ jsxRuntime.jsx(React.Fragment, { children: pane === StructureToolProvider.LOADING_PANE ? /* @__PURE__ */ jsxRuntime.jsx(StructureToolProvider.LoadingPane, { paneKey, path, selected }) : /* @__PURE__ */ jsxRuntime.jsx( StructureToolPane, { active, groupIndex, index: paneIndex, pane, childItemId, itemId, paneKey, params: paneParams, payload, path, selected, siblingIndex } ) }, `${pane === StructureToolProvider.LOADING_PANE ? "loading" : pane.type}-${paneIndex}`) ), paneDataItems.length <= 1 && isResolvingIntent && /* @__PURE__ */ jsxRuntime.jsx(StructureToolProvider.LoadingPane, { paneKey: "intent-resolver" }) ] } ), /* @__PURE__ */ jsxRuntime.jsx(StructureTitle, { resolvedPanes }), /* @__PURE__ */ jsxRuntime.jsx("div", { "data-portal": "", ref: setPortalElement }) ] }) : /* @__PURE__ */ jsxRuntime.jsx(NoDocumentTypesScreen, {}); }); function StructureToolBoundary({ tool: { options } }) { const { unstable_sources: sources } = sanity.useWorkspace(), [firstSource] = sources, { source, defaultDocumentNode, structure } = options || {}; React.useEffect(() => (StructureToolProvider.setActivePanes([]), () => StructureToolProvider.setActivePanes([])), []); const [{ error }, setError] = React.useState({ error: null }); return error ? /* @__PURE__ */ jsxRuntime.jsx(StructureError, { error }) : /* @__PURE__ */ jsxRuntime.jsx(ui.ErrorBoundary, { onCatch: setError, children: /* @__PURE__ */ jsxRuntime.jsx(sanity.SourceProvider, { name: source || firstSource.name, children: /* @__PURE__ */ jsxRuntime.jsxs(StructureToolProvider.StructureToolProvider, { defaultDocumentNode, structure, children: [ /* @__PURE__ */ jsxRuntime.jsx(StructureTool, { onPaneChange: StructureToolProvider.setActivePanes }), /* @__PURE__ */ jsxRuntime.jsx(IntentResolver, {}) ] }) }) }); } exports.default = StructureToolBoundary; //# sourceMappingURL=index3.js.map