UNPKG

@sanity/orderable-document-list

Version:

Drag-and-drop Document Ordering without leaving the Editing surface

670 lines (669 loc) 32.3 kB
import { defineField, useSchema, useDocumentVersionInfo, PreviewCard, DocumentStatus, Preview, DocumentStatusIndicator, usePerspective, useClient, isVersionId, getVersionFromId, isPublishedId, isDraftId, getPublishedId } from "sanity"; import { LexoRank } from "lexorank"; import { DragHandleIcon, ChevronUpIcon, ChevronDownIcon, SortIcon, GenerateIcon } from "@sanity/icons"; import { jsx, jsxs, Fragment } from "react/jsx-runtime"; import { createContext, useContext, useState, useCallback, useEffect, useMemo, Component } from "react"; import { c } from "react/compiler-runtime"; import { Box, Text, Flex, Button, Tooltip, Card, AvatarCounter, useToast, Spinner, Container, Stack } from "@sanity/ui"; import { useListeningQuery, Feedback } from "sanity-plugin-utils"; import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd"; import { usePaneRouter } from "sanity/structure"; const ORDER_FIELD_NAME = "orderRank", API_VERSION = "v2025-06-27"; function parseOrderRank(value, fallback) { if (typeof value != "string") return console.warn("[orderable-document-list] Invalid orderRank value (expected string):", value), fallback; try { return LexoRank.parse(value); } catch (err) { return console.warn("[orderable-document-list] Failed to parse orderRank value:", value, "Error:", err instanceof Error ? err.message : String(err)), fallback; } } function initialRank(compareRankValue = "", newItemPosition = "after") { const compareRank = parseOrderRank(compareRankValue, LexoRank.min()); return (newItemPosition === "before" ? compareRank.genPrev().genPrev() : compareRank.genNext().genNext()).toString(); } const orderRankField = (config) => { if (!config?.type) throw new Error(` type must be provided. Example: orderRankField({type: 'category'}) `); const { type, newItemPosition = "after", ...rest } = config; return defineField({ title: "Order Rank", readOnly: !0, hidden: !0, ...rest, name: ORDER_FIELD_NAME, type: "string", initialValue: async (value, { getClient }) => { const direction = newItemPosition === "before" ? "asc" : "desc", lastDocOrderRank = await getClient({ apiVersion: API_VERSION }).fetch(`*[_type == $type]|order(@[$order] ${direction})[0][$order]`, { type, order: ORDER_FIELD_NAME }, { tag: "orderable-document-list.last-doc-order-rank" }); return initialRank(lastDocOrderRank, newItemPosition); } }); }, orderRankOrdering = { title: "Ordered", name: "ordered", by: [{ field: ORDER_FIELD_NAME, direction: "asc" }] }, OrderableContext = createContext({}); function Document(t0) { const $ = c(49), { doc, increment, entities, index, isFirst, isLast, dragBadge } = t0, { showIncrements } = useContext(OrderableContext), schema = useSchema(), router = usePaneRouter(), versionsInfo = useDocumentVersionInfo(doc._id), { ChildLink, groupIndex, routerPanesState } = router, currentDoc = routerPanesState[groupIndex + 1]?.[0]?.id || !1; let t1; $[0] !== currentDoc || $[1] !== doc._id ? (t1 = currentDoc === doc._id || currentDoc === doc._id.replace("drafts.", ""), $[0] = currentDoc, $[1] = doc._id, $[2] = t1) : t1 = $[2]; const pressed = t1, selected = pressed && routerPanesState.length === groupIndex + 2; let t2; $[3] !== doc._type || $[4] !== schema ? (t2 = schema.get(doc._type), $[3] = doc._type, $[4] = schema, $[5] = t2) : t2 = $[5]; const schemaType = t2; let t3; $[6] !== ChildLink || $[7] !== doc._id ? (t3 = function(linkProps) { return /* @__PURE__ */ jsx(ChildLink, { ...linkProps, childId: doc._id }); }, $[6] = ChildLink, $[7] = doc._id, $[8] = t3) : t3 = $[8]; const Link = t3; if (!schemaType) return null; let t4; $[9] !== versionsInfo.draft || $[10] !== versionsInfo.published || $[11] !== versionsInfo.versions ? (t4 = /* @__PURE__ */ jsx(DocumentStatus, { draft: versionsInfo.draft, published: versionsInfo.published, versions: versionsInfo.versions }), $[9] = versionsInfo.draft, $[10] = versionsInfo.published, $[11] = versionsInfo.versions, $[12] = t4) : t4 = $[12]; const tooltip = t4; let t5; $[13] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t5 = { flexShrink: 0 }, $[13] = t5) : t5 = $[13]; let t6; $[14] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t6 = /* @__PURE__ */ jsx(Box, { paddingX: 2, style: t5, children: /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(DragHandleIcon, { cursor: "grab" }) }) }), $[14] = t6) : t6 = $[14]; let t7; $[15] !== doc._id || $[16] !== entities || $[17] !== increment || $[18] !== index || $[19] !== isFirst || $[20] !== isLast || $[21] !== showIncrements ? (t7 = showIncrements && /* @__PURE__ */ jsxs(Flex, { style: { flexShrink: 0 }, align: "center", gap: 1, paddingRight: 1, children: [ /* @__PURE__ */ jsx(Button, { padding: 2, mode: "ghost", onClick: () => increment(index, index - 1, doc._id, entities), disabled: isFirst, icon: ChevronUpIcon }), /* @__PURE__ */ jsx(Button, { padding: 2, mode: "ghost", disabled: isLast, onClick: () => increment(index, index + 1, doc._id, entities), icon: ChevronDownIcon }) ] }), $[15] = doc._id, $[16] = entities, $[17] = increment, $[18] = index, $[19] = isFirst, $[20] = isLast, $[21] = showIncrements, $[22] = t7) : t7 = $[22]; let t8; $[23] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t8 = { width: "100%" }, $[23] = t8) : t8 = $[23]; let t9; $[24] !== doc || $[25] !== schemaType ? (t9 = /* @__PURE__ */ jsx(Preview, { layout: "default", value: doc, schemaType }), $[24] = doc, $[25] = schemaType, $[26] = t9) : t9 = $[26]; let t10; $[27] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t10 = { flexShrink: 0 }, $[27] = t10) : t10 = $[27]; let t11; $[28] !== versionsInfo.draft || $[29] !== versionsInfo.published || $[30] !== versionsInfo.versions ? (t11 = /* @__PURE__ */ jsx(Flex, { align: "center", style: t10, children: /* @__PURE__ */ jsx(DocumentStatusIndicator, { draft: versionsInfo.draft, published: versionsInfo.published, versions: versionsInfo.versions }) }), $[28] = versionsInfo.draft, $[29] = versionsInfo.published, $[30] = versionsInfo.versions, $[31] = t11) : t11 = $[31]; let t12; $[32] !== t11 || $[33] !== tooltip ? (t12 = /* @__PURE__ */ jsx(Tooltip, { content: tooltip, portal: !0, placement: "right", boundaryElement: null, children: t11 }), $[32] = t11, $[33] = tooltip, $[34] = t12) : t12 = $[34]; let t13; $[35] !== t12 || $[36] !== t9 ? (t13 = /* @__PURE__ */ jsx(Box, { style: t8, children: /* @__PURE__ */ jsxs(Flex, { flex: 1, align: "center", justify: "space-between", paddingRight: 3, children: [ t9, t12 ] }) }), $[35] = t12, $[36] = t9, $[37] = t13) : t13 = $[37]; let t14; $[38] !== dragBadge ? (t14 = dragBadge && /* @__PURE__ */ jsx(Card, { tone: "default", marginRight: 4, radius: 5, children: /* @__PURE__ */ jsx(AvatarCounter, { count: dragBadge }) }), $[38] = dragBadge, $[39] = t14) : t14 = $[39]; let t15; $[40] !== t13 || $[41] !== t14 || $[42] !== t7 ? (t15 = /* @__PURE__ */ jsxs(Flex, { align: "center", children: [ t6, t7, t13, t14 ] }), $[40] = t13, $[41] = t14, $[42] = t7, $[43] = t15) : t15 = $[43]; let t16; return $[44] !== Link || $[45] !== pressed || $[46] !== selected || $[47] !== t15 ? (t16 = /* @__PURE__ */ jsx(PreviewCard, { __unstable_focusRing: !0, as: Link, "data-as": "a", "data-ui": "PaneItem", radius: 2, pressed, selected, sizing: "border", tabIndex: -1, tone: "inherit", width: "100%", flex: 1, children: t15 }), $[44] = Link, $[45] = pressed, $[46] = selected, $[47] = t15, $[48] = t16) : t16 = $[48], t16; } function useSanityClient() { const $ = c(4), { perspectiveStack } = usePerspective(); let t0; $[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t0 = { apiVersion: API_VERSION }, $[0] = t0) : t0 = $[0]; const t1 = useClient(t0); let t2; return $[1] !== perspectiveStack || $[2] !== t1 ? (t2 = t1.withConfig({ perspective: perspectiveStack }), $[1] = perspectiveStack, $[2] = t1, $[3] = t2) : t2 = $[3], t2; } function lexicographicalSort(a, b) { return !a[ORDER_FIELD_NAME] || !b[ORDER_FIELD_NAME] ? 0 : a[ORDER_FIELD_NAME] < b[ORDER_FIELD_NAME] ? -1 : a[ORDER_FIELD_NAME] > b[ORDER_FIELD_NAME] ? 1 : 0; } const reorderDocuments = ({ entities, selectedIds, source, destination }) => { const startIndex = source.index, endIndex = destination.index, isMovingUp = startIndex > endIndex, selectedItems = entities.filter((item) => selectedIds.includes(item._id)), message = ["Moved", selectedItems.length === 1 ? "1 document" : `${selectedItems.length} documents`, isMovingUp ? "up" : "down", "from position", `${startIndex + 1} to ${endIndex + 1}`].join(" "), { all, selected } = entities.reduce((acc, cur, curIndex) => { if (selectedIds.includes(cur._id)) return { all: acc.all, selected: acc.selected }; if (curIndex === endIndex) { const prevIndex = curIndex - 1, prevRank = parseOrderRank(entities[prevIndex]?.[ORDER_FIELD_NAME], LexoRank.min()), curRank = parseOrderRank(cur[ORDER_FIELD_NAME], LexoRank.min()), nextIndex = curIndex + 1, nextRank = parseOrderRank(entities[nextIndex]?.[ORDER_FIELD_NAME], LexoRank.max()); let betweenRank = isMovingUp ? prevRank.between(curRank) : curRank.between(nextRank); for (const selectedItem of selectedItems) selectedItem[ORDER_FIELD_NAME] = betweenRank.toString(), betweenRank = isMovingUp ? betweenRank.between(curRank) : betweenRank.between(nextRank); return { // The `all` array gets sorted by order field later anyway // so that this probably isn't necessary ¯\_(ツ)_/¯ all: isMovingUp ? [...acc.all, ...selectedItems, cur] : [...acc.all, cur, ...selectedItems], selected: selectedItems }; } return { all: [...acc.all, cur], selected: acc.selected }; }, { all: [], selected: [] }), patches = selected.flatMap((doc) => { const docPatches = [[doc._id, { set: { [ORDER_FIELD_NAME]: doc[ORDER_FIELD_NAME] } }]]; return doc._id.startsWith("drafts.") && doc.hasPublished && docPatches.push([doc._id.replace("drafts.", ""), { set: { [ORDER_FIELD_NAME]: doc[ORDER_FIELD_NAME] } }]), docPatches; }); return { newOrder: all.sort(lexicographicalSort), patches, message }; }, getItemStyle = (draggableStyle, itemIsUpdating) => ({ userSelect: "none", transition: "opacity 500ms ease-in-out", opacity: itemIsUpdating ? 0.2 : 1, pointerEvents: itemIsUpdating ? "none" : void 0, ...draggableStyle }), cardTone = (settings) => { const { isDuplicate, isGhosting, isDragging, isSelected } = settings; if (isGhosting) return "transparent"; if (isDragging || isSelected) return "primary"; if (isDuplicate) return "caution"; }; function DraggableList({ data, listIsUpdating, setListIsUpdating }) { const toast = useToast(), router = usePaneRouter(), { groupIndex, routerPanesState } = router, currentDoc = routerPanesState[groupIndex + 1]?.[0]?.id || !1, [orderedData, setOrderedData] = useState(data), displayedData = listIsUpdating ? orderedData : data, [draggingId, setDraggingId] = useState(""), [selectedIds, setSelectedIds] = useState(currentDoc ? [currentDoc] : []), clearSelected = useCallback(() => setSelectedIds([]), [setSelectedIds]), handleSelect = useCallback((clickedId, index, nativeEvent) => { const isSelected = selectedIds.includes(clickedId), selectMultiple = nativeEvent.shiftKey, selectAdditional = navigator.appVersion.indexOf("Win") !== -1 ? nativeEvent.ctrlKey : nativeEvent.metaKey; let updatedIds = []; if (!selectMultiple && !selectAdditional) return setSelectedIds([clickedId]); if (selectMultiple && nativeEvent.preventDefault(), selectMultiple && !isSelected) { const lastSelectedId = selectedIds[selectedIds.length - 1] ?? clickedId, lastSelectedIndex = displayedData.findIndex((item) => item._id === lastSelectedId), firstSelected = index < lastSelectedIndex ? index : lastSelectedIndex, lastSelected = index > lastSelectedIndex ? index : lastSelectedIndex, betweenIds = displayedData.filter((_, itemIndex) => itemIndex > firstSelected && itemIndex < lastSelected).map((item_0) => item_0._id); updatedIds = [...selectedIds, ...betweenIds, clickedId]; } else isSelected ? updatedIds = selectedIds.filter((id) => id !== clickedId) : updatedIds = [...selectedIds, clickedId]; return setSelectedIds(updatedIds); }, [displayedData, selectedIds]), client = useSanityClient(), transactPatches = useCallback(async (patches, message) => { const transaction = client.transaction(); patches.forEach(([docId, ops]) => { transaction.patch(docId, ops); }); try { const updated = await transaction.commit({ visibility: "sync", tag: "orderable-document-list.reorder" }); clearSelected(), setDraggingId(""), setListIsUpdating(!1), toast.push({ title: `${updated.results.length === 1 ? "1 document" : `${updated.results.length} documents`} reordered`, status: "success", description: message }); } catch { setDraggingId(""), setListIsUpdating(!1), toast.push({ title: "Reordering failed", status: "error" }); } }, [client, clearSelected, setListIsUpdating, toast]), handleDragEnd = useCallback((result, entities) => { setDraggingId(""); const { source, destination, draggableId } = result ?? {}; if (source?.index === destination?.index || !entities.length || !draggableId || !source || !destination) return; const effectedIds = selectedIds.length > 0 ? selectedIds : [draggableId]; if (effectedIds.length === 0) return; setListIsUpdating(!0), setSelectedIds(effectedIds); const { newOrder, patches: patches_0, message: message_0 } = reorderDocuments({ entities, selectedIds: effectedIds, source, destination }); newOrder.length > 0 && setOrderedData(newOrder), patches_0.length > 0 && transactPatches(patches_0, message_0); }, [selectedIds, transactPatches, setListIsUpdating]), handleDragStart = useCallback((start) => { const id_0 = start.draggableId; selectedIds.includes(id_0) || clearSelected(), setDraggingId(id_0); }, [selectedIds, clearSelected]), incrementIndex = useCallback((shiftFrom, shiftTo, id_1, entities_0) => handleDragEnd({ draggableId: id_1, source: { droppableId: "documentSortZone", index: shiftFrom }, destination: { droppableId: "documentSortZone", index: shiftTo } }, entities_0), [handleDragEnd]), onWindowKeyDown = useCallback((event) => { event.key === "Escape" && clearSelected(); }, [clearSelected]); useEffect(() => (window.addEventListener("keydown", onWindowKeyDown), () => { window.removeEventListener("keydown", onWindowKeyDown); }), [onWindowKeyDown]); const duplicateOrders = useMemo(() => { if (displayedData.length === 0) return []; const orderField = displayedData.map((item_1) => item_1[ORDER_FIELD_NAME]); return orderField.filter((item_2, index_0) => orderField.indexOf(item_2) !== index_0); }, [displayedData]), onDragEnd = useCallback((result_1) => handleDragEnd(result_1, displayedData), [displayedData, handleDragEnd]); return /* @__PURE__ */ jsx(DragDropContext, { onDragStart: handleDragStart, onDragEnd, children: /* @__PURE__ */ jsx(Droppable, { droppableId: "documentSortZone", children: (provided) => /* @__PURE__ */ jsxs("div", { ...provided.droppableProps, ref: provided.innerRef, children: [ displayedData.map((item_3, index_1) => /* @__PURE__ */ jsx(Draggable, { draggableId: item_3._id, index: index_1, children: (innerProvided, innerSnapshot) => { const isSelected_0 = selectedIds.includes(item_3._id), isDragging = innerSnapshot.isDragging, isGhosting = !!(!isDragging && draggingId && isSelected_0), isUpdating = listIsUpdating && isSelected_0, isDisabled = !item_3[ORDER_FIELD_NAME], isDuplicate = duplicateOrders.includes(item_3[ORDER_FIELD_NAME]), tone = cardTone({ isDuplicate, isGhosting, isDragging, isSelected: isSelected_0 }), selectedCount = selectedIds.length, dragBadge = isDragging && selectedCount > 1 ? selectedCount : !1; return /* @__PURE__ */ jsx("div", { ref: innerProvided.innerRef, ...innerProvided.draggableProps, ...innerProvided.dragHandleProps, style: isDisabled ? { opacity: 0.2, pointerEvents: "none" } : getItemStyle(innerProvided.draggableProps.style, isUpdating), children: /* @__PURE__ */ jsx(Box, { paddingBottom: 1, children: /* @__PURE__ */ jsx(Card, { tone, shadow: isDragging ? 2 : void 0, radius: 2, onClick: (e) => handleSelect(item_3._id, index_1, e.nativeEvent), children: /* @__PURE__ */ jsx(Document, { doc: item_3, entities: displayedData, increment: incrementIndex, index: index_1, isFirst: index_1 === 0, isLast: index_1 === displayedData.length - 1, dragBadge }) }) }) }); } }, `${item_3._id}-${item_3[ORDER_FIELD_NAME]}`)), provided.placeholder ] }) }) }); } const isVersionForCurrentPerspective = (document, perspectiveName, publishedId) => document._id && isVersionId(document._id) && getVersionFromId(document._id) === perspectiveName && getPublishedId(document._id) === publishedId, getFilteredDedupedDocs = (documents, perspectiveName) => { const flatDocuments = documents.flat(), dedupedDocuments = []; for (const cur of flatDocuments) if (cur._id) { if (isVersionId(cur._id)) { const isCorrectVersion = getVersionFromId(cur._id) === perspectiveName; perspectiveName && perspectiveName !== "drafts" && perspectiveName !== "published" && isCorrectVersion && dedupedDocuments.push(cur); continue; } if (perspectiveName === "published") { isPublishedId(cur._id) && dedupedDocuments.push(cur); continue; } if (!isDraftId(cur._id)) { const publishedId = getPublishedId(cur._id), hasMatchingVersion = perspectiveName && perspectiveName !== "drafts" && perspectiveName !== "published" ? flatDocuments.some((doc) => isVersionForCurrentPerspective(doc, perspectiveName, publishedId)) : !1, hasDraft = flatDocuments.some((doc) => doc._id === `drafts.${cur._id}`), hasDuplicatePublished = flatDocuments.some((doc) => doc !== cur && doc._id === cur._id); !hasMatchingVersion && !hasDraft && !hasDuplicatePublished && dedupedDocuments.push(cur); continue; } if (perspectiveName && perspectiveName !== "drafts" && perspectiveName !== "published") { const baseId = getPublishedId(cur._id); if (flatDocuments.some((doc) => isVersionForCurrentPerspective(doc, perspectiveName, baseId))) continue; } cur.hasPublished = flatDocuments.some((doc) => doc._id === cur._id.replace("drafts.", "")), dedupedDocuments.push(cur); } return dedupedDocuments; }, DEFAULT_PARAMS = {}; function getDocumentQuery({ type, filter, params = DEFAULT_PARAMS, currentVersion }) { let perspectiveFilter = null; currentVersion === "published" ? perspectiveFilter = '!(_id in path("drafts.**")) && !(_id in path("versions.**"))' : currentVersion === "drafts" ? perspectiveFilter = ` (_id in path("drafts.**") || (!(_id in path("drafts.**")) && !(_id in path("versions.**")))) ` : perspectiveFilter = '(sanity::partOfRelease($currentVersion) || (!(_id in path("drafts.**")) && !(_id in path("versions.**"))) || (_id in path("drafts.**")))'; const querySelect = `*[_type == $type ${perspectiveFilter ? `&& ${perspectiveFilter}` : ""}${filter ? `&& ${filter}` : ""}]`, queryOrder = "|order(@[$order] asc)", queryFields = `{_id, _type, ${ORDER_FIELD_NAME}}`, query = `${querySelect}${queryOrder}${queryFields}`, queryParams = { ...params, type, order: ORDER_FIELD_NAME, ...currentVersion && { currentVersion } }; return { query, queryParams }; } function DocumentListQuery(props) { const $ = c(26), [listIsUpdating, setListIsUpdating] = useState(!1); let t0; $[0] !== props ? (t0 = getDocumentQuery(props), $[0] = props, $[1] = t0) : t0 = $[1]; const { query, queryParams } = t0; let t1; $[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t1 = [], $[2] = t1) : t1 = $[2]; let t2; $[3] !== queryParams ? (t2 = { params: queryParams, initialValue: t1 }, $[3] = queryParams, $[4] = t2) : t2 = $[4]; const { data: _queryData, loading, error } = useListeningQuery(query, t2); let t3; $[5] !== _queryData ? (t3 = Array.isArray(_queryData) ? _queryData : [], $[5] = _queryData, $[6] = t3) : t3 = $[6]; const queryData = t3; let t4; $[7] !== props.currentVersion || $[8] !== queryData ? (t4 = getFilteredDedupedDocs(queryData, props.currentVersion), $[7] = props.currentVersion, $[8] = queryData, $[9] = t4) : t4 = $[9]; const data = t4; let t5; $[10] !== data ? (t5 = data.filter(_temp$1), $[10] = data, $[11] = t5) : t5 = $[11]; const unorderedDataCount = t5.length; if (loading) { let t62; $[12] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t62 = { width: "100%", height: "100%" }, $[12] = t62) : t62 = $[12]; let t72; return $[13] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t72 = /* @__PURE__ */ jsx(Flex, { style: t62, align: "center", justify: "center", children: /* @__PURE__ */ jsx(Spinner, {}) }), $[13] = t72) : t72 = $[13], t72; } if (error) { let t62; return $[14] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t62 = /* @__PURE__ */ jsx(Box, { padding: 2, children: /* @__PURE__ */ jsx(Feedback, { tone: "critical", title: "There was an error", description: "Please try again later" }) }), $[14] = t62) : t62 = $[14], t62; } if (data.length === 0) { let t62; return $[15] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t62 = /* @__PURE__ */ jsx(Flex, { align: "center", direction: "column", height: "fill", justify: "center", children: /* @__PURE__ */ jsx(Container, { width: 1, children: /* @__PURE__ */ jsx(Box, { paddingX: 4, paddingY: 5, children: /* @__PURE__ */ jsx(Text, { align: "center", muted: !0, children: "No documents of this type" }) }) }) }), $[15] = t62) : t62 = $[15], t62; } let t6; $[16] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t6 = { overflow: "auto", height: "100%" }, $[16] = t6) : t6 = $[16]; let t7; $[17] !== data.length || $[18] !== unorderedDataCount ? (t7 = unorderedDataCount > 0 && /* @__PURE__ */ jsx(Box, { marginBottom: 2, children: /* @__PURE__ */ jsx(Feedback, { tone: "caution", description: /* @__PURE__ */ jsxs(Fragment, { children: [ unorderedDataCount, "/", data.length, " documents have no order. Select", " ", /* @__PURE__ */ jsx("strong", { children: "Reset Order" }), " from the menu above to fix." ] }) }) }), $[17] = data.length, $[18] = unorderedDataCount, $[19] = t7) : t7 = $[19]; let t8; $[20] !== data || $[21] !== listIsUpdating ? (t8 = /* @__PURE__ */ jsx(DraggableList, { data, listIsUpdating, setListIsUpdating }), $[20] = data, $[21] = listIsUpdating, $[22] = t8) : t8 = $[22]; let t9; return $[23] !== t7 || $[24] !== t8 ? (t9 = /* @__PURE__ */ jsx(Stack, { gap: 1, style: t6, children: /* @__PURE__ */ jsxs(Box, { padding: 2, children: [ t7, t8 ] }) }), $[23] = t7, $[24] = t8, $[25] = t9) : t9 = $[25], t9; } function _temp$1(doc) { return !doc[ORDER_FIELD_NAME]; } function DocumentListWrapper(t0) { const $ = c(34), { type, showIncrements, resetOrderTransaction, filter, params, currentVersion } = t0, toast = useToast(), schema = useSchema(); let t1; $[0] !== showIncrements ? (t1 = { showIncrements }, $[0] = showIncrements, $[1] = t1) : t1 = $[1]; const orderableContextValue = t1; let t2, t3; $[2] !== resetOrderTransaction || $[3] !== toast ? (t2 = () => { resetOrderTransaction?.title && resetOrderTransaction?.status && toast.push(resetOrderTransaction); }, t3 = [resetOrderTransaction, toast], $[2] = resetOrderTransaction, $[3] = toast, $[4] = t2, $[5] = t3) : (t2 = $[4], t3 = $[5]), useEffect(t2, t3); let t4; if ($[6] !== schema || $[7] !== type) { bb0: { if (!type) { let t52; $[9] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t52 = /* @__PURE__ */ jsxs(Fragment, { children: [ "No ", /* @__PURE__ */ jsx("code", { children: "type" }), " was configured" ] }), $[9] = t52) : t52 = $[9], t4 = t52; break bb0; } const typeSchema = schema.get(type); if (!typeSchema) { let t52; $[10] !== type ? (t52 = /* @__PURE__ */ jsxs(Fragment, { children: [ "Schema ", /* @__PURE__ */ jsx("code", { children: type }), " not found" ] }), $[10] = type, $[11] = t52) : t52 = $[11], t4 = t52; break bb0; } if (!("fields" in typeSchema) || !typeSchema.fields.some(_temp)) { let t52; $[12] !== type ? (t52 = /* @__PURE__ */ jsx("code", { children: type }), $[12] = type, $[13] = t52) : t52 = $[13]; let t62; $[14] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t62 = /* @__PURE__ */ jsx("code", { children: ORDER_FIELD_NAME }), $[14] = t62) : t62 = $[14]; let t7; $[15] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t7 = /* @__PURE__ */ jsx("code", { children: "string" }), $[15] = t7) : t7 = $[15]; let t8; $[16] !== t52 ? (t8 = /* @__PURE__ */ jsxs(Fragment, { children: [ "Schema ", t52, " must have an ", t62, " field of type", " ", t7 ] }), $[16] = t52, $[17] = t8) : t8 = $[17], t4 = t8; break bb0; } if ("fields" in typeSchema && typeSchema.fields.some(_temp2)) { let t52; $[18] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t52 = /* @__PURE__ */ jsx("code", { children: ORDER_FIELD_NAME }), $[18] = t52) : t52 = $[18]; let t62; $[19] !== type ? (t62 = /* @__PURE__ */ jsx("code", { children: type }), $[19] = type, $[20] = t62) : t62 = $[20]; let t7; $[21] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t7 = /* @__PURE__ */ jsx("code", { children: "string" }), $[21] = t7) : t7 = $[21]; let t8; $[22] !== t62 ? (t8 = /* @__PURE__ */ jsxs(Fragment, { children: [ t52, " field on Schema ", t62, " must be", " ", t7, " type" ] }), $[22] = t62, $[23] = t8) : t8 = $[23], t4 = t8; break bb0; } t4 = ""; } $[6] = schema, $[7] = type, $[8] = t4; } else t4 = $[8]; const schemaIsInvalid = t4; if (schemaIsInvalid) { let t52; return $[24] !== schemaIsInvalid ? (t52 = /* @__PURE__ */ jsx(Box, { padding: 2, children: /* @__PURE__ */ jsx(Feedback, { description: schemaIsInvalid, tone: "caution" }) }), $[24] = schemaIsInvalid, $[25] = t52) : t52 = $[25], t52; } let t5; $[26] !== currentVersion || $[27] !== filter || $[28] !== params || $[29] !== type ? (t5 = /* @__PURE__ */ jsx(DocumentListQuery, { type, filter, params, currentVersion }), $[26] = currentVersion, $[27] = filter, $[28] = params, $[29] = type, $[30] = t5) : t5 = $[30]; let t6; return $[31] !== orderableContextValue || $[32] !== t5 ? (t6 = /* @__PURE__ */ jsx(OrderableContext.Provider, { value: orderableContextValue, children: t5 }), $[31] = orderableContextValue, $[32] = t5, $[33] = t6) : t6 = $[33], t6; } function _temp2(field_0) { return field_0?.name === ORDER_FIELD_NAME && field_0?.type?.name !== "string"; } function _temp(field) { return field?.name === ORDER_FIELD_NAME; } async function resetOrder(params) { const { client, currentVersion, ...queryProps } = params, { query, queryParams } = getDocumentQuery({ ...queryProps, currentVersion }), documents = await client.fetch(query, queryParams, { tag: "orderable-document-list.reset-order" }); if (documents.length === 0) return null; let aLexoRank = LexoRank.min(); return documents.map((doc) => doc._id).reduce((trx, documentId) => (aLexoRank = aLexoRank.genNext().genNext(), trx.patch(documentId, { set: { [ORDER_FIELD_NAME]: aLexoRank.toString() } })), client.transaction()).commit({ visibility: "async", tag: "orderable-document-list.reset-order" }); } class OrderableDocumentList extends Component { constructor(props) { super(props), this.state = { showIncrements: !1, resetOrderTransaction: {} }; } actionHandlers = { showIncrements: () => { this.setState((state) => ({ showIncrements: !state.showIncrements })); }, resetOrder: async () => { this.setState(() => ({ resetOrderTransaction: { status: "info", title: "Reordering started...", closable: !0 } })); const update = await resetOrder(this.props.options), reorderWasSuccessful = update?.results?.length; this.setState(() => ({ resetOrderTransaction: { status: reorderWasSuccessful ? "success" : "info", title: reorderWasSuccessful ? `Reordered ${update.results.length === 1 ? "Document" : "Documents"}` : "Reordering failed", closable: !0 } })); } }; render() { const { options } = this.props, { type, filter, params, currentVersion } = options; return type ? /* @__PURE__ */ jsx(DocumentListWrapper, { filter, params, type, showIncrements: this.state.showIncrements, resetOrderTransaction: this.state.resetOrderTransaction, currentVersion }) : null; } } function orderableDocumentListDeskItem(config) { if (!config?.type || !config.context || !config.S) throw new Error(` type, context and S (StructureBuilder) must be provided. context and S are available when configuring structure. Example: orderableDocumentListDeskItem({type: 'category'}) `); const { type, filter, menuItems = [], createIntent, params, title, icon, id, context, S } = config, { schema, getClient } = context, maybePerspectiveStack = Reflect.get(context, "perspectiveStack"), perspectiveStack = Array.isArray(maybePerspectiveStack) && maybePerspectiveStack.every((item) => typeof item == "string") ? maybePerspectiveStack : [], client = getClient({ apiVersion: API_VERSION }), currentVersion = perspectiveStack[0], listTitle = title ?? `Orderable ${type}`, listId = id ?? `orderable-${type}`, listIcon = icon ?? SortIcon, typeTitle = schema.get(type)?.title ?? type, defaultMenuItems = [...menuItems]; return createIntent !== !1 && defaultMenuItems.push(S.menuItem().title(`Create new ${typeTitle}`).intent({ type: "create", params: { type } }).serialize()), S.listItem().title(listTitle).id(listId).icon(listIcon).schemaType(type).child(Object.assign(S.documentTypeList(type).canHandleIntent(() => createIntent !== !1).serialize(), { // Prevents the component from re-rendering when switching documents __preserveInstance: !0, // Prevents the component from NOT re-rendering when switching listItems key: listId, type: "component", component: OrderableDocumentList, options: { type, filter, params, client, currentVersion }, menuItems: [...defaultMenuItems, S.menuItem().title("Reset Order").icon(GenerateIcon).action("resetOrder").serialize(), S.menuItem().title("Toggle Increments").icon(SortIcon).action("showIncrements").serialize()] })).serialize(); } export { OrderableDocumentList, orderRankField, orderRankOrdering, orderableDocumentListDeskItem }; //# sourceMappingURL=index.js.map