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

492 lines (491 loc) • 20.3 kB
import { jsx, jsxs } from "react/jsx-runtime"; import { c } from "react/compiler-runtime"; import { useTranslation, useResourceCache, useActiveReleases, useClient, useFeatureEnabled, FEATURES, useUpsellData, UpsellDialog, isCardinalityOneRelease, useReleasesToolAvailable } from "./index2.js"; import { ReleasesMetadataProvider } from "./ReleasesMetadataProvider.js"; import { Stack, Text, useToast } from "@sanity/ui"; import { useState } from "react"; import { catchError, of, map, BehaviorSubject, switchMap, tap, timer, shareReplay, firstValueFrom } from "rxjs"; import { ReleasesUpsellContext } from "sanity/_singletons"; import "history"; import "@sanity/icons/ArrowLeft"; import "@sanity/icons/Launch"; import "styled-components"; import { Dialog } from "./index.js"; import "react-i18next"; import "@sanity/util/paths"; import "react-fast-compare"; import "@sanity/id-utils"; import "@sanity/icons/Document"; import "@sanity/icons/Image"; import "@sanity/uuid"; import "rxjs/operators"; import "react-rx"; import "@sanity/client/csm"; import "quick-lru"; import "lodash-es/throttle.js"; import "lodash-es/isEqual.js"; import "@sanity/color"; import "color2k"; import "lodash-es/isString.js"; import "@sanity/icons/Copy"; import "@sanity/icons/Clipboard"; import "./createAgentBundlesStore.js"; import "@sanity/telemetry/react"; import "./version.js"; import "@sanity/comlink"; import "@sanity/mutator"; import "lodash-es/omit.js"; import "@sanity/client"; import "lodash-es/groupBy.js"; import "@isaacs/ttlcache"; import "lodash-es/partition.js"; import "shallow-equals"; import "@sanity/schema"; import "lodash-es/get.js"; import "@sanity/types"; import "lodash-es/isPlainObject.js"; import "@sanity/util/client"; import "@sanity/util/concurrency-limiter"; import "lodash-es/flatten.js"; import "lodash-es/uniqBy.js"; import "json-reduce"; import "rxjs-exhaustmap-with-trailing"; import "@sanity/util/content"; import "dataloader"; import "raf"; import "groq-js"; import "mendoza"; import "@sanity/diff"; import "use-sync-external-store/with-selector"; import "@portabletext/editor"; import "@sanity/icons/ChevronRight"; import "@sanity/icons/Revert"; import "@sanity/ui/theme"; import "date-fns/differenceInDays"; import "date-fns/differenceInHours"; import "date-fns/differenceInMinutes"; import "date-fns/differenceInMonths"; import "date-fns/differenceInSeconds"; import "date-fns/differenceInWeeks"; import "date-fns/differenceInYears"; import "@sanity/icons/AddCircle"; import "@sanity/icons/Calendar"; import "@sanity/icons/Circle"; import "@sanity/icons/Close"; import "@sanity/icons/Edit"; import "@sanity/icons/Publish"; import "@sanity/icons/Timeline"; import "@sanity/icons/Trash"; import "@sanity/icons/Unpublish"; import "@sanity/icons/ArrowDown"; import "@sanity/icons/ArrowRight"; import "@sanity/util/legacyDateFormat"; import "@sanity/diff-patch"; import "@sanity/icons/ErrorOutline"; import "@sanity/icons/InfoOutline"; import "@sanity/icons/WarningOutline"; import "@portabletext/sanity-bridge"; import "motion/react"; import "lodash-es/pick.js"; import "@algorithm.ts/lcs"; import "lodash-es/intersection.js"; import "json-stable-stringify"; import "scroll-into-view-if-needed"; import "@sanity/schema/_internal"; import "lodash-es/sortBy.js"; import "lodash-es/compact.js"; import "lodash-es/keyBy.js"; import "lodash-es/toLower.js"; import "lodash-es/union.js"; import "lodash-es/uniq.js"; import "lodash-es/words.js"; import "lodash-es/isFinite.js"; import "@sanity/icons/Add"; import "observable-callback"; import "@sanity/icons/BulbOutline"; import "@sanity/icons/Unknown"; import "@sanity/icons/ToggleArrowRight"; import "@sanity/insert-menu"; import "@dnd-kit/sortable"; import "@sanity/icons/DragHandle"; import "@sanity/icons/AddDocument"; import "@sanity/icons/InsertAbove"; import "@sanity/icons/InsertBelow"; import "@tanstack/react-virtual"; import "lodash-es/startCase.js"; import "date-fns/getMinutes"; import "date-fns/parseISO"; import "date-fns/setMinutes"; import "@sanity/icons/EarthAmericas"; import "lodash-es/noop.js"; import "@sanity/icons/Search"; import "speakingurl"; import "@sanity/icons/Reset"; import "@sanity/icons/Sync"; import "@sanity/asset-utils"; import "@sanity/icons/AccessDenied"; import "@sanity/icons/HelpCircle"; import "@sanity/image-url"; import "react-is"; import "lodash-es/pickBy.js"; import "swr"; import "@sanity/icons/ChevronDown"; import "@sanity/icons/Download"; import "@sanity/icons/Upload"; import "@sanity/icons/BinaryDocument"; import "@sanity/icons/Lock"; import "sanity/router"; import "lodash-es/castArray.js"; import "nanoid"; import "use-hot-module-reload"; import "@sanity/icons/EllipsisHorizontal"; import "@sanity/icons/EyeOpen"; import "@sanity/icons/Link"; import "rxjs-mergemap-array"; import "react-focus-lock"; import "lodash-es/memoize.js"; import "lodash-es/sortedIndex.js"; import "lodash-es/difference.js"; import "lodash-es/identity.js"; import "lodash-es/values.js"; import "date-fns/addHours"; import "date-fns/startOfHour"; import "@sanity/icons/Bolt"; import "@sanity/icons/Clock"; import "@sanity/icons/Dot"; import "@sanity/icons/EarthGlobe"; import "date-fns/format"; import "date-fns/isValid"; import "date-fns/parse"; import "lodash-es/debounce.js"; import "clsx"; import "@portabletext/react"; import "@sanity/icons/LinkRemoved"; import "lodash-es/findIndex.js"; import "@sanity/diff-match-patch"; import "@sanity/icons"; import "@sanity/icons/ChevronUp"; import "@sanity/icons/Feedback"; import "@xstate/react"; import "xstate"; import "@sanity/icons/FaceHappy"; import "@sanity/icons/FaceIndifferent"; import "@sanity/icons/FaceSad"; import "@sentry/react"; import "date-fns/isBefore"; import "date-fns/startOfMinute"; import "@sanity/icons/Documents"; import "@portabletext/toolkit"; import "@sanity/icons/User"; import "@sanity/icons/CheckmarkCircle"; import "lodash-es/deburr.js"; import "@sanity/icons/ChevronLeft"; import "@sanity/icons/Checkmark"; import "@sanity/logos"; import "is-network-error"; import "lodash-es/escapeRegExp.js"; import "@sanity/icons/Desktop"; import "@sanity/icons/Moon"; import "@sanity/icons/Sun"; import "lodash-es/isEmpty.js"; import "react-dom/server"; import "@sanity/icons/Controls"; import "@sanity/icons/Sort"; import "lodash-es/capitalize.js"; import "@sanity/icons/Undo"; import "@sanity/icons/Menu"; import "@sanity/icons/AddUser"; import "@sanity/icons/Cog"; import "@sanity/icons/Users"; import "semver"; import "@sanity/icons/Github"; import "@sanity/icons/Refresh"; import "@sanity/icons/Leave"; import "lodash-es/last.js"; import "react-dom/client"; import "date-fns/addWeeks"; import "date-fns/isAfter"; function ReleaseLimitsMisconfigurationDialog(props) { const $ = c(18), { onClose } = props, { t } = useTranslation(); let t0; $[0] !== onClose ? (t0 = () => { window.open("https://www.sanity.io/contact/support", "_blank", "noopener,noreferrer"), onClose(); }, $[0] = onClose, $[1] = t0) : t0 = $[1]; const handleContactSupport = t0; let t1; $[2] !== t ? (t1 = t("releases.upsell.misconfiguration.header"), $[2] = t, $[3] = t1) : t1 = $[3]; let t2; $[4] !== t ? (t2 = t("releases.upsell.misconfiguration.contact-support"), $[4] = t, $[5] = t2) : t2 = $[5]; let t3; $[6] !== handleContactSupport || $[7] !== t2 ? (t3 = { confirmButton: { text: t2, onClick: handleContactSupport, tone: "primary" } }, $[6] = handleContactSupport, $[7] = t2, $[8] = t3) : t3 = $[8]; let t4; $[9] !== t ? (t4 = t("releases.upsell.misconfiguration.message"), $[9] = t, $[10] = t4) : t4 = $[10]; let t5; $[11] !== t4 ? (t5 = /* @__PURE__ */ jsx(Stack, { space: 4, children: /* @__PURE__ */ jsx(Text, { children: t4 }) }), $[11] = t4, $[12] = t5) : t5 = $[12]; let t6; return $[13] !== onClose || $[14] !== t1 || $[15] !== t3 || $[16] !== t5 ? (t6 = /* @__PURE__ */ jsx(Dialog, { id: "releases-misconfiguration-dialog", header: t1, width: 1, onClose, footer: t3, children: t5 }), $[13] = onClose, $[14] = t1, $[15] = t3, $[16] = t5, $[17] = t6) : t6 = $[17], t6; } function isErrorAtLimits(error) { return error.statusCode === 403 && typeof error.response.body != "string" && "data" in error.response.body; } function fetchReleaseLimits(client, storeOrigin) { const { projectId } = client.config(); return client.withConfig({ apiVersion: "vX" }).observable.request({ uri: `projects/${projectId}/new-content-release-allowed`, /** * In a particular case when both releaseLimits * and orgActiveReleaseCount stores are empty, 2 fetch calls are made * to guard the create/archive/revert actions - browsers behave differently. * * Chromium based browsers will stall the second network request until the first * resolves by attaching the store name to the tag, this spoofs 2 unique requests * allowing both the fire in parallel. */ tag: `new-content-release-allowed-${storeOrigin}` }).pipe(catchError((error) => { if (console.error(error), isErrorAtLimits(error)) return of(error.response.body); throw error; }), map(({ data }) => data)); } const STATE_TTL_MS = 6e4, ORG_ACTIVE_RELEASE_COUNT_RESOURCE_CACHE_NAMESPACE = "orgActiveReleaseCount"; function createOrgActiveReleaseCountStore(client, activeReleasesCount) { const latestFetchState = new BehaviorSubject(null), staleFlag$ = new BehaviorSubject(!1), activeReleaseCountAtFetch = new BehaviorSubject(null); return { orgActiveReleaseCount$: latestFetchState.pipe(switchMap((state) => state === null || // oxlint-disable-next-line no-unnecessary-boolean-literal-compare staleFlag$.getValue() === !0 || activeReleaseCountAtFetch.getValue() !== activeReleasesCount ? (staleFlag$.next(!1), fetchReleaseLimits(client, "orgActiveReleaseCount").pipe(tap(() => activeReleaseCountAtFetch.next(activeReleasesCount)), map((data) => data.orgMeteredActiveReleaseCount), catchError((error) => { if (console.error("Failed to fetch org release count", error), !state) throw error; return of(state); }), switchMap((nextState) => (latestFetchState.next(nextState), timer(STATE_TTL_MS).subscribe(() => { staleFlag$.next(!0), activeReleaseCountAtFetch.next(null); }), of(nextState))))) : of(state))) }; } const useOrgActiveReleaseCount = () => { const $ = c(7), resourceCache = useResourceCache(), { data: activeReleases } = useActiveReleases(); let t0; $[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t0 = { apiVersion: "v2025-02-19" }, $[0] = t0) : t0 = $[0]; const client = useClient(t0), activeReleasesCount = activeReleases?.length || 0; let t1; $[1] !== activeReleasesCount ? (t1 = { activeReleasesCount }, $[1] = activeReleasesCount, $[2] = t1) : t1 = $[2]; const count = t1; let releaseLimitsStore; return $[3] !== client || $[4] !== count || $[5] !== resourceCache ? (releaseLimitsStore = resourceCache.get({ dependencies: [client, count], namespace: ORG_ACTIVE_RELEASE_COUNT_RESOURCE_CACHE_NAMESPACE }) || createOrgActiveReleaseCountStore(client, count.activeReleasesCount), resourceCache.set({ namespace: ORG_ACTIVE_RELEASE_COUNT_RESOURCE_CACHE_NAMESPACE, value: releaseLimitsStore, dependencies: [client, count] }), $[3] = client, $[4] = count, $[5] = resourceCache, $[6] = releaseLimitsStore) : releaseLimitsStore = $[6], releaseLimitsStore; }, RELEASE_LIMITS_RESOURCE_CACHE_NAMESPACE = "ReleaseLimits"; function createReleaseLimitsStore(client) { return { releaseLimits$: fetchReleaseLimits(client, "releaseLimits").pipe(map((data) => ({ datasetReleaseLimit: data.datasetReleaseLimit, orgActiveReleaseLimit: data.orgActiveReleaseLimit })), shareReplay(1), catchError((error) => (console.error("Failed to fetch release limits", error), of(null)))) }; } const useReleaseLimits = () => { const $ = c(4), resourceCache = useResourceCache(); let t0; $[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t0 = { apiVersion: "v2025-02-19" }, $[0] = t0) : t0 = $[0]; const client = useClient(t0); let releaseLimitsStore; return $[1] !== client || $[2] !== resourceCache ? (releaseLimitsStore = resourceCache.get({ dependencies: [client], namespace: RELEASE_LIMITS_RESOURCE_CACHE_NAMESPACE }) || createReleaseLimitsStore(client), resourceCache.set({ namespace: RELEASE_LIMITS_RESOURCE_CACHE_NAMESPACE, value: releaseLimitsStore, dependencies: [client] }), $[1] = client, $[2] = resourceCache, $[3] = releaseLimitsStore) : releaseLimitsStore = $[3], releaseLimitsStore; }; class StudioReleaseLimitExceededError extends Error { constructor() { super("StudioReleaseLimitExceeded"), this.name = "StudioReleaseLimitExceededError", this.details = { type: "releaseLimitExceededError" }; } } function ReleasesUpsellProvider(props) { const $ = c(47), [upsellDialogOpen, setUpsellDialogOpen] = useState(!1), { data: allActiveReleases } = useActiveReleases(), { enabled: isReleasesFeatureEnabled } = useFeatureEnabled(FEATURES.contentReleases); let t0; $[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t0 = { dataUri: "/journey/content-releases", feature: "content-releases" }, $[0] = t0) : t0 = $[0]; const { upsellData, telemetryLogs, hasError } = useUpsellData(t0), toast = useToast(), { t } = useTranslation(); let t1; $[1] !== allActiveReleases ? (t1 = allActiveReleases.filter(_temp), $[1] = allActiveReleases, $[2] = t1) : t1 = $[2]; const meteredActiveReleases = t1; let t2; bb0: { if (!isReleasesFeatureEnabled) { t2 = "upsell"; break bb0; } t2 = "default"; } const mode = t2; let t3; $[3] !== telemetryLogs ? (t3 = () => { telemetryLogs.dialogPrimaryClicked(); }, $[3] = telemetryLogs, $[4] = t3) : t3 = $[4]; const handlePrimaryButtonClick = t3; let t4; $[5] !== telemetryLogs ? (t4 = () => { telemetryLogs.dialogSecondaryClicked(); }, $[5] = telemetryLogs, $[6] = t4) : t4 = $[6]; const handleSecondaryButtonClick = t4; let t5; $[7] !== telemetryLogs ? (t5 = () => { setUpsellDialogOpen(!1), telemetryLogs.dialogDismissed(); }, $[7] = telemetryLogs, $[8] = t5) : t5 = $[8]; const handleClose = t5, [releaseLimit, setReleaseLimit] = useState(null), [showMisconfigurationDialog, setShowMisconfigurationDialog] = useState(!1); let t6; $[9] !== hasError || $[10] !== t || $[11] !== telemetryLogs || $[12] !== toast ? (t6 = (t72) => { const source = t72 === void 0 ? "navbar" : t72; if (hasError) { toast.push({ status: "error", title: t("errors.unable-to-perform-action"), closable: !0 }); return; } setUpsellDialogOpen(!0), telemetryLogs.dialogViewed(source); }, $[9] = hasError, $[10] = t, $[11] = telemetryLogs, $[12] = toast, $[13] = t6) : t6 = $[13]; const handleOpenDialog = t6, { releaseLimits$ } = useReleaseLimits(), { orgActiveReleaseCount$ } = useOrgActiveReleaseCount(); let t7; $[14] !== allActiveReleases.length || $[15] !== handleOpenDialog || $[16] !== meteredActiveReleases.length || $[17] !== mode || $[18] !== orgActiveReleaseCount$ || $[19] !== releaseLimits$ ? (t7 = async (cb, t82, whenResolved) => { const throwError = t82 === void 0 ? !1 : t82, doUpsell = () => { if (handleOpenDialog(), throwError) throw new StudioReleaseLimitExceededError(); return !1; }; if (mode === "upsell") return whenResolved?.(!1), doUpsell(); const result = await (async () => { try { return await Promise.all([firstValueFrom(orgActiveReleaseCount$), firstValueFrom(releaseLimits$.pipe(tap((limit) => setReleaseLimit(limit?.orgActiveReleaseLimit || null))))]); } catch (t92) { return console.error("Error fetching release limits and org count for upsell:", t92), null; } })(); if (result === null) return whenResolved?.(!0), cb(); const [orgMeteredActiveReleaseCount, releaseLimits] = result; if (releaseLimits === null || orgMeteredActiveReleaseCount === null) return whenResolved?.(!0), cb(); const { orgActiveReleaseLimit, datasetReleaseLimit } = releaseLimits; if (orgActiveReleaseLimit === 0) { if (whenResolved?.(!1), setShowMisconfigurationDialog(!0), throwError) throw new StudioReleaseLimitExceededError(); return !1; } if (orgMeteredActiveReleaseCount === null) return whenResolved?.(!0), cb(); const meteredActiveReleaseCount = meteredActiveReleases?.length || 0, isCurrentDatasetAtAboveDatasetLimit = (allActiveReleases?.length || 0) >= datasetReleaseLimit, isCurrentDatasetAtAboveOrgLimit = orgActiveReleaseLimit !== null && meteredActiveReleaseCount >= orgActiveReleaseLimit, isOrgAtAboveOrgLimit = orgActiveReleaseLimit !== null && orgMeteredActiveReleaseCount >= orgActiveReleaseLimit; return isCurrentDatasetAtAboveDatasetLimit || isCurrentDatasetAtAboveOrgLimit || isOrgAtAboveOrgLimit ? (whenResolved?.(!1), doUpsell()) : (whenResolved?.(!0), cb()); }, $[14] = allActiveReleases.length, $[15] = handleOpenDialog, $[16] = meteredActiveReleases.length, $[17] = mode, $[18] = orgActiveReleaseCount$, $[19] = releaseLimits$, $[20] = t7) : t7 = $[20]; const guardWithReleaseLimitUpsell = t7; let t8; $[21] !== handleOpenDialog ? (t8 = (limit_0) => { setReleaseLimit(limit_0), handleOpenDialog(); }, $[21] = handleOpenDialog, $[22] = t8) : t8 = $[22]; const onReleaseLimitReached = t8; let t9; $[23] !== guardWithReleaseLimitUpsell || $[24] !== handleOpenDialog || $[25] !== mode || $[26] !== onReleaseLimitReached || $[27] !== telemetryLogs || $[28] !== upsellData || $[29] !== upsellDialogOpen ? (t9 = { mode, upsellDialogOpen, guardWithReleaseLimitUpsell, onReleaseLimitReached, telemetryLogs, upsellData, handleOpenDialog }, $[23] = guardWithReleaseLimitUpsell, $[24] = handleOpenDialog, $[25] = mode, $[26] = onReleaseLimitReached, $[27] = telemetryLogs, $[28] = upsellData, $[29] = upsellDialogOpen, $[30] = t9) : t9 = $[30]; const ctxValue = t9; let t10; $[31] !== releaseLimit ? (t10 = releaseLimit === null ? void 0 : { releaseLimit }, $[31] = releaseLimit, $[32] = t10) : t10 = $[32]; const interpolation = t10; let t11; $[33] !== handleClose || $[34] !== handlePrimaryButtonClick || $[35] !== handleSecondaryButtonClick || $[36] !== interpolation || $[37] !== upsellData || $[38] !== upsellDialogOpen ? (t11 = { data: upsellData, open: upsellDialogOpen, interpolation, onClose: handleClose, onPrimaryClick: handlePrimaryButtonClick, onSecondaryClick: handleSecondaryButtonClick }, $[33] = handleClose, $[34] = handlePrimaryButtonClick, $[35] = handleSecondaryButtonClick, $[36] = interpolation, $[37] = upsellData, $[38] = upsellDialogOpen, $[39] = t11) : t11 = $[39]; const dialogProps = t11; let t12; $[40] !== dialogProps || $[41] !== showMisconfigurationDialog ? (t12 = showMisconfigurationDialog ? /* @__PURE__ */ jsx(ReleaseLimitsMisconfigurationDialog, { onClose: () => setShowMisconfigurationDialog(!1) }) : /* @__PURE__ */ jsx(UpsellDialog, { ...dialogProps }), $[40] = dialogProps, $[41] = showMisconfigurationDialog, $[42] = t12) : t12 = $[42]; let t13; return $[43] !== ctxValue || $[44] !== props.children || $[45] !== t12 ? (t13 = /* @__PURE__ */ jsxs(ReleasesUpsellContext.Provider, { value: ctxValue, children: [ props.children, t12 ] }), $[43] = ctxValue, $[44] = props.children, $[45] = t12, $[46] = t13) : t13 = $[46], t13; } function _temp(release) { return !isCardinalityOneRelease(release); } function ReleasesStudioLayout(props) { const $ = c(6); if (!useReleasesToolAvailable()) { let t02; return $[0] !== props ? (t02 = props.renderDefault(props), $[0] = props, $[1] = t02) : t02 = $[1], t02; } let t0; $[2] !== props ? (t0 = props.renderDefault(props), $[2] = props, $[3] = t0) : t0 = $[3]; let t1; return $[4] !== t0 ? (t1 = /* @__PURE__ */ jsx(ReleasesUpsellProvider, { children: /* @__PURE__ */ jsx(ReleasesMetadataProvider, { children: t0 }) }), $[4] = t0, $[5] = t1) : t1 = $[5], t1; } export { ReleasesStudioLayout }; //# sourceMappingURL=ReleasesStudioLayout.js.map