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
JavaScript
;
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