From 5a1fd101113aa10b4f3f9f177e4bfd51867f3b0a Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Fri, 18 Oct 2024 12:23:00 +0200 Subject: [PATCH 01/43] chore: start-in-create provider --- .../context/StartInCreateContext.tsx | 13 ++++++++++ .../src/core/config/configPropertyReducers.ts | 23 ++++++++++++++++++ .../sanity/src/core/config/prepareConfig.ts | 4 ++++ packages/sanity/src/core/config/types.ts | 12 ++++++++++ .../start-in-create/StartInCreateProvider.tsx | 21 ++++++++++++++++ .../useStartInCreateEnabled.ts | 24 +++++++++++++++++++ .../structure/panes/document/DocumentPane.tsx | 5 +++- 7 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 packages/sanity/src/_singletons/context/StartInCreateContext.tsx create mode 100644 packages/sanity/src/core/form/create/start-in-create/StartInCreateProvider.tsx create mode 100644 packages/sanity/src/core/form/create/start-in-create/useStartInCreateEnabled.ts diff --git a/packages/sanity/src/_singletons/context/StartInCreateContext.tsx b/packages/sanity/src/_singletons/context/StartInCreateContext.tsx new file mode 100644 index 00000000000..95a28b5e677 --- /dev/null +++ b/packages/sanity/src/_singletons/context/StartInCreateContext.tsx @@ -0,0 +1,13 @@ +import {createContext} from 'sanity/_createContext' + +import type {StartInCreateEnabledContextValue} from '../../core/form/create/start-in-create/useStartInCreateEnabled' + +/** + * @internal + */ +export const StartInCreateContext = createContext( + 'sanity/_singletons/context/start-in-create-enabled', + { + enabled: false, + }, +) diff --git a/packages/sanity/src/core/config/configPropertyReducers.ts b/packages/sanity/src/core/config/configPropertyReducers.ts index c2b6a66205a..90eba5d9e6a 100644 --- a/packages/sanity/src/core/config/configPropertyReducers.ts +++ b/packages/sanity/src/core/config/configPropertyReducers.ts @@ -391,3 +391,26 @@ export const legacySearchEnabledReducer: ConfigPropertyReducer { + const {config, initialValue} = opts + const flattenedConfig = flattenConfig(config, []) + + const result = flattenedConfig.reduce((acc, {config: innerConfig}) => { + const resolver = innerConfig.beta?.create?.startInCreateEnabled + + if (!resolver && typeof resolver !== 'boolean') return acc + if (typeof resolver === 'boolean') return resolver + + throw new Error( + `Expected \`beta.create.startInCreateEnabled\` to be a boolean, but received ${getPrintableType( + resolver, + )}`, + ) + }, initialValue) + + return result +} diff --git a/packages/sanity/src/core/config/prepareConfig.ts b/packages/sanity/src/core/config/prepareConfig.ts index 054250acc5b..6f7a180ecb6 100644 --- a/packages/sanity/src/core/config/prepareConfig.ts +++ b/packages/sanity/src/core/config/prepareConfig.ts @@ -42,6 +42,7 @@ import { partialIndexingEnabledReducer, resolveProductionUrlReducer, schemaTemplatesReducer, + startInCreateEnabledReducer, toolsReducer, } from './configPropertyReducers' import {ConfigResolutionError} from './ConfigResolutionError' @@ -648,6 +649,9 @@ function resolveSource({ // This beta feature is no longer available. enabled: false, }, + create: { + startInCreateEnabled: startInCreateEnabledReducer({config, initialValue: true}), + }, }, } diff --git a/packages/sanity/src/core/config/types.ts b/packages/sanity/src/core/config/types.ts index 5903b68b763..47f99596eae 100644 --- a/packages/sanity/src/core/config/types.ts +++ b/packages/sanity/src/core/config/types.ts @@ -388,10 +388,12 @@ export interface PluginOptions { */ enableLegacySearch?: boolean } + /** Configuration for studio beta features. * @internal */ beta?: BetaFeatures + /** Configuration for error handling. * @beta */ @@ -917,4 +919,14 @@ interface BetaFeatures { */ enabled: boolean } + + /** + * @beta + */ + create?: { + /** + * When true, a "Start in Create" action will be shown for all new documents, in place of regular document actions. + */ + startInCreateEnabled: boolean + } } diff --git a/packages/sanity/src/core/form/create/start-in-create/StartInCreateProvider.tsx b/packages/sanity/src/core/form/create/start-in-create/StartInCreateProvider.tsx new file mode 100644 index 00000000000..56940c12f98 --- /dev/null +++ b/packages/sanity/src/core/form/create/start-in-create/StartInCreateProvider.tsx @@ -0,0 +1,21 @@ +import {useMemo} from 'react' + +import {StartInCreateContext} from '../../../../_singletons/context/StartInCreateContext' +import {useSource} from '../../../studio' +import {type StartInCreateEnabledContextValue} from './useStartInCreateEnabled' + +interface StartInCreateProviderProps { + children: React.ReactNode +} + +export function StartInCreateProvider(props: StartInCreateProviderProps): JSX.Element { + const {children} = props + const {beta} = useSource() + const value = useMemo((): StartInCreateEnabledContextValue => { + return { + enabled: !!beta?.create?.startInCreateEnabled, + } + }, [beta?.create]) + + return {children} +} diff --git a/packages/sanity/src/core/form/create/start-in-create/useStartInCreateEnabled.ts b/packages/sanity/src/core/form/create/start-in-create/useStartInCreateEnabled.ts new file mode 100644 index 00000000000..4499d629a0d --- /dev/null +++ b/packages/sanity/src/core/form/create/start-in-create/useStartInCreateEnabled.ts @@ -0,0 +1,24 @@ +import {useContext} from 'react' + +import {StartInCreateContext} from '../../../../_singletons/context/StartInCreateContext' + +/** + * @internal + */ +export interface StartInCreateEnabledContextValue { + /** + * A boolean indicating whether "Start in Create" new document pane footer should be shown, when available. + */ + enabled: boolean +} + +/** + * @internal + */ +export function useStartInCreateEnabled(): StartInCreateEnabledContextValue { + const context = useContext(StartInCreateContext) + if (!context) { + throw new Error('useStartInCreateEnabled must be used within a StartInCreateProvider') + } + return context +} diff --git a/packages/sanity/src/structure/panes/document/DocumentPane.tsx b/packages/sanity/src/structure/panes/document/DocumentPane.tsx index 9516dad2dfd..4bd3b72a386 100644 --- a/packages/sanity/src/structure/panes/document/DocumentPane.tsx +++ b/packages/sanity/src/structure/panes/document/DocumentPane.tsx @@ -14,6 +14,7 @@ import { useTranslation, } from 'sanity' +import {StartInCreateProvider} from '../../../core/form/create/start-in-create/StartInCreateProvider' import {usePaneRouter} from '../../components' import {structureLocaleNamespace} from '../../i18n' import {type DocumentPaneNode} from '../../types' @@ -35,7 +36,9 @@ export const DocumentPane = memo(function DocumentPane(props: DocumentPaneProvid return ( - + + + ) From a0c864e8ab451167d9614abfc6d84d9ab36bd06a Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Sat, 19 Oct 2024 15:17:41 +0200 Subject: [PATCH 02/43] feat: first pass Sanity Create form ui --- dev/strict-studio/sanity.config.ts | 6 + packages/@sanity/types/src/user/types.ts | 3 + .../context/SanityCreateConfigContext.tsx | 13 +++ .../context/StartInCreateContext.tsx | 13 --- packages/sanity/src/_singletons/index.ts | 1 + .../src/core/config/configPropertyReducers.ts | 22 ++++ .../sanity/src/core/config/prepareConfig.ts | 11 +- .../src/core/config/resolveDefaultPlugins.ts | 3 +- packages/sanity/src/core/config/types.ts | 31 ++++- .../components/CreateIntegrationWrapper.tsx | 6 + .../components/CreateLearnMoreButton.tsx | 22 ++++ .../create/components/CreateLinkedActions.tsx | 47 ++++++++ .../components/CreateLinkedDocumentBanner.tsx | 88 ++++++++++++++ .../components/CreateUnlinkConfirmDialog.tsx | 60 ++++++++++ .../context/SanityCreateConfigProvider.tsx | 32 ++++++ .../sanity/src/core/create/context/index.ts | 2 + .../create/context/useSanityCreateConfig.ts | 32 ++++++ .../src/core/create/createIntegration.ts | 27 +++++ .../sanity/src/core/create/createUtils.ts | 26 +++++ .../create/getStartInCreateSortedActions.ts | 18 +++ packages/sanity/src/core/create/i18n/index.ts | 27 +++++ .../sanity/src/core/create/i18n/resources.ts | 77 +++++++++++++ packages/sanity/src/core/create/index.ts | 3 + .../start-in-create/CreateLinkingDialog.tsx | 28 +++++ .../start-in-create/StartInCreateAction.tsx | 80 +++++++++++++ .../start-in-create/StartInCreateDialog.tsx | 103 +++++++++++++++++ .../start-in-create/StartInCreateSvg.tsx | 5 + .../src/core/create/studio-app/appIdCache.ts | 39 +++++++ .../src/core/create/studio-app/manifest.ts | 28 +++++ .../core/create/studio-app/manifestTypes.ts | 16 +++ .../create/studio-app/useStudioAppIdStore.ts | 107 ++++++++++++++++++ packages/sanity/src/core/create/types.ts | 29 +++++ .../src/core/create/useCreateDocumentUrl.ts | 41 +++++++ .../start-in-create/StartInCreateProvider.tsx | 21 ---- .../useStartInCreateEnabled.ts | 24 ---- packages/sanity/src/core/index.ts | 1 + .../src/core/store/_legacy/user/userStore.ts | 6 +- .../structure/panes/document/DocumentPane.tsx | 5 +- .../panes/document/DocumentPaneProvider.tsx | 6 +- .../document-layout/DocumentLayout.tsx | 15 ++- .../document/statusBar/DocumentStatusBar.tsx | 29 ++++- 41 files changed, 1078 insertions(+), 75 deletions(-) create mode 100644 packages/sanity/src/_singletons/context/SanityCreateConfigContext.tsx delete mode 100644 packages/sanity/src/_singletons/context/StartInCreateContext.tsx create mode 100644 packages/sanity/src/core/create/components/CreateIntegrationWrapper.tsx create mode 100644 packages/sanity/src/core/create/components/CreateLearnMoreButton.tsx create mode 100644 packages/sanity/src/core/create/components/CreateLinkedActions.tsx create mode 100644 packages/sanity/src/core/create/components/CreateLinkedDocumentBanner.tsx create mode 100644 packages/sanity/src/core/create/components/CreateUnlinkConfirmDialog.tsx create mode 100644 packages/sanity/src/core/create/context/SanityCreateConfigProvider.tsx create mode 100644 packages/sanity/src/core/create/context/index.ts create mode 100644 packages/sanity/src/core/create/context/useSanityCreateConfig.ts create mode 100644 packages/sanity/src/core/create/createIntegration.ts create mode 100644 packages/sanity/src/core/create/createUtils.ts create mode 100644 packages/sanity/src/core/create/getStartInCreateSortedActions.ts create mode 100644 packages/sanity/src/core/create/i18n/index.ts create mode 100644 packages/sanity/src/core/create/i18n/resources.ts create mode 100644 packages/sanity/src/core/create/index.ts create mode 100644 packages/sanity/src/core/create/start-in-create/CreateLinkingDialog.tsx create mode 100644 packages/sanity/src/core/create/start-in-create/StartInCreateAction.tsx create mode 100644 packages/sanity/src/core/create/start-in-create/StartInCreateDialog.tsx create mode 100644 packages/sanity/src/core/create/start-in-create/StartInCreateSvg.tsx create mode 100644 packages/sanity/src/core/create/studio-app/appIdCache.ts create mode 100644 packages/sanity/src/core/create/studio-app/manifest.ts create mode 100644 packages/sanity/src/core/create/studio-app/manifestTypes.ts create mode 100644 packages/sanity/src/core/create/studio-app/useStudioAppIdStore.ts create mode 100644 packages/sanity/src/core/create/types.ts create mode 100644 packages/sanity/src/core/create/useCreateDocumentUrl.ts delete mode 100644 packages/sanity/src/core/form/create/start-in-create/StartInCreateProvider.tsx delete mode 100644 packages/sanity/src/core/form/create/start-in-create/useStartInCreateEnabled.ts diff --git a/dev/strict-studio/sanity.config.ts b/dev/strict-studio/sanity.config.ts index da553d9e1fe..6df25b71576 100644 --- a/dev/strict-studio/sanity.config.ts +++ b/dev/strict-studio/sanity.config.ts @@ -10,4 +10,10 @@ export default defineConfig({ projectId: 'ppsg7ml5', dataset: 'test', schema: {types: schemaTypes}, + + beta: { + create: { + fallbackStudioOrigin: 'sanity-manifest-test.sanity.studio', + }, + }, }) diff --git a/packages/@sanity/types/src/user/types.ts b/packages/@sanity/types/src/user/types.ts index e1bbbaeb627..1d90def9cbc 100644 --- a/packages/@sanity/types/src/user/types.ts +++ b/packages/@sanity/types/src/user/types.ts @@ -23,4 +23,7 @@ export interface User { displayName?: string imageUrl?: string email?: string + + /** global sanity user id */ + sanityUserId?: string } diff --git a/packages/sanity/src/_singletons/context/SanityCreateConfigContext.tsx b/packages/sanity/src/_singletons/context/SanityCreateConfigContext.tsx new file mode 100644 index 00000000000..2f89b9833fa --- /dev/null +++ b/packages/sanity/src/_singletons/context/SanityCreateConfigContext.tsx @@ -0,0 +1,13 @@ +import {createContext} from 'sanity/_createContext' + +import type {SanityCreateConfigContextValue} from '../../core' + +/** + * @internal + */ +export const SanityCreateConfigContext = createContext( + 'sanity/_singletons/context/start-in-create-enabled', + { + startInCreateEnabled: false, + }, +) diff --git a/packages/sanity/src/_singletons/context/StartInCreateContext.tsx b/packages/sanity/src/_singletons/context/StartInCreateContext.tsx deleted file mode 100644 index 95a28b5e677..00000000000 --- a/packages/sanity/src/_singletons/context/StartInCreateContext.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import {createContext} from 'sanity/_createContext' - -import type {StartInCreateEnabledContextValue} from '../../core/form/create/start-in-create/useStartInCreateEnabled' - -/** - * @internal - */ -export const StartInCreateContext = createContext( - 'sanity/_singletons/context/start-in-create-enabled', - { - enabled: false, - }, -) diff --git a/packages/sanity/src/_singletons/index.ts b/packages/sanity/src/_singletons/index.ts index 05761b9609f..6679e2e0282 100644 --- a/packages/sanity/src/_singletons/index.ts +++ b/packages/sanity/src/_singletons/index.ts @@ -47,6 +47,7 @@ export * from './context/ResourceCacheContext' export * from './context/ReviewChangesContext' export * from './context/RouterContext' export * from './context/RouterHistoryContext' +export * from './context/SanityCreateConfigContext' export * from './context/ScheduledPublishingEnabledContext' export * from './context/SchedulePublishingUpsellContext' export * from './context/Schedules' diff --git a/packages/sanity/src/core/config/configPropertyReducers.ts b/packages/sanity/src/core/config/configPropertyReducers.ts index 90eba5d9e6a..221ad852b12 100644 --- a/packages/sanity/src/core/config/configPropertyReducers.ts +++ b/packages/sanity/src/core/config/configPropertyReducers.ts @@ -414,3 +414,25 @@ export const startInCreateEnabledReducer = (opts: { return result } + +export const createFallbackOriginReducer = (config: PluginOptions): string | undefined => { + const flattenedConfig = flattenConfig(config, []) + + const result = flattenedConfig.reduce( + (acc, {config: innerConfig}) => { + const resolver = innerConfig.beta?.create?.fallbackStudioOrigin + + if (!resolver) return acc + if (typeof resolver === 'string') return resolver + + throw new Error( + `Expected \`beta.create.fallbackStudioOrigin\` to be a string, but received ${getPrintableType( + resolver, + )}`, + ) + }, + undefined as string | undefined, + ) + + return result +} diff --git a/packages/sanity/src/core/config/prepareConfig.ts b/packages/sanity/src/core/config/prepareConfig.ts index 6f7a180ecb6..ac7f68dc32a 100644 --- a/packages/sanity/src/core/config/prepareConfig.ts +++ b/packages/sanity/src/core/config/prepareConfig.ts @@ -14,6 +14,7 @@ import { import {isValidElementType} from 'react-is' import {map, shareReplay} from 'rxjs/operators' +import {getStartInCreateSortedActions} from '../create/getStartInCreateSortedActions' import {FileSource, ImageSource} from '../form/studio/assetSource' import {type LocaleSource} from '../i18n' import {prepareI18n} from '../i18n/i18nConfig' @@ -25,6 +26,7 @@ import {operatorDefinitions} from '../studio/components/navbar/search/definition import {type InitialValueTemplateItem, type Template, type TemplateItem} from '../templates' import {EMPTY_ARRAY, isNonNullable} from '../util' import { + createFallbackOriginReducer, documentActionsReducer, documentBadgesReducer, documentCommentsEnabledReducer, @@ -496,14 +498,16 @@ function resolveSource({ config, }), document: { - actions: (partialContext) => - resolveConfigProperty({ + actions: (partialContext) => { + const actions = resolveConfigProperty({ config, context: {...context, ...partialContext}, initialValue: initialDocumentActions, propertyName: 'document.actions', reducer: documentActionsReducer, - }), + }) + return getStartInCreateSortedActions(actions) + }, badges: (partialContext) => resolveConfigProperty({ config, @@ -651,6 +655,7 @@ function resolveSource({ }, create: { startInCreateEnabled: startInCreateEnabledReducer({config, initialValue: true}), + fallbackStudioOrigin: createFallbackOriginReducer(config), }, }, } diff --git a/packages/sanity/src/core/config/resolveDefaultPlugins.ts b/packages/sanity/src/core/config/resolveDefaultPlugins.ts index 2e79260b243..978fb8aa20d 100644 --- a/packages/sanity/src/core/config/resolveDefaultPlugins.ts +++ b/packages/sanity/src/core/config/resolveDefaultPlugins.ts @@ -1,4 +1,5 @@ import {comments} from '../comments/plugin' +import {createIntegration} from '../create/createIntegration' import {DEFAULT_SCHEDULED_PUBLISH_PLUGIN_OPTIONS} from '../scheduledPublishing/constants' import {SCHEDULED_PUBLISHING_NAME, scheduledPublishing} from '../scheduledPublishing/plugin' import {tasks, TASKS_NAME} from '../tasks/plugin' @@ -9,7 +10,7 @@ import { type WorkspaceOptions, } from './types' -const defaultPlugins = [comments(), tasks(), scheduledPublishing()] +const defaultPlugins = [comments(), tasks(), scheduledPublishing(), createIntegration()] export function getDefaultPlugins( options: DefaultPluginsWorkspaceOptions, diff --git a/packages/sanity/src/core/config/types.ts b/packages/sanity/src/core/config/types.ts index 47f99596eae..2d1beb7db52 100644 --- a/packages/sanity/src/core/config/types.ts +++ b/packages/sanity/src/core/config/types.ts @@ -925,8 +925,35 @@ interface BetaFeatures { */ create?: { /** - * When true, a "Start in Create" action will be shown for all new documents, in place of regular document actions. + * When true, a "Start in Sanity Create" action will be shown for all new documents, in place of regular document actions, + * when the following are true: + * - the origin of the current url is listed under Studios in sanity.to/manage (OR fallbackStudioOrigin is provided) + * - [origin]/static/create-manifest.json is available over HTTP GET + * + * The manifest file is automatically created and deployed when deploying studios with `sanity deploy` + * + * @see #fallbackStudioOrigin + */ + startInCreateEnabled?: boolean + + /** + * To show the "Start in Create" button on localhost, or in studios not listed under Studios in sanity.io/manage + * provide a fallback origin as a string. + * + * The string must be the exactly equal `name` as shown for the Studio in manage, and the studio must have create-manifest.json available. + * + * If the provided fallback Studio does not expose create-manifest.json "Start in Sanity Create" will fail when using the fallback. + * + * Example: `wonderful.sanity.studio` + * + * Keep in mind that when fallback origin is used, Sanity Create will used the schema types and dataset in the *deployed* Studio, + * not from localhost. + * + * To see data synced from Sanity Create in your localhost Studio, you must ensure that the deployed fallback studio uses the same + * workspace and schemas as your local configuration. + * + * @see #startInCreateEnabled */ - startInCreateEnabled: boolean + fallbackStudioOrigin?: string } } diff --git a/packages/sanity/src/core/create/components/CreateIntegrationWrapper.tsx b/packages/sanity/src/core/create/components/CreateIntegrationWrapper.tsx new file mode 100644 index 00000000000..9d6fb12b88e --- /dev/null +++ b/packages/sanity/src/core/create/components/CreateIntegrationWrapper.tsx @@ -0,0 +1,6 @@ +import {type LayoutProps} from '../../config' +import {SanityCreateConfigProvider} from '../context/SanityCreateConfigProvider' + +export function CreateIntegrationWrapper(props: LayoutProps) { + return {props.renderDefault(props)} +} diff --git a/packages/sanity/src/core/create/components/CreateLearnMoreButton.tsx b/packages/sanity/src/core/create/components/CreateLearnMoreButton.tsx new file mode 100644 index 00000000000..f32db5e4d09 --- /dev/null +++ b/packages/sanity/src/core/create/components/CreateLearnMoreButton.tsx @@ -0,0 +1,22 @@ +import {LaunchIcon} from '@sanity/icons' +import {type ForwardedRef, forwardRef} from 'react' + +import {Button} from '../../../ui-components' +import {useTranslation} from '../../i18n' +import {createLocaleNamespace} from '../i18n' + +export const CreateLearnMoreButton = forwardRef(function CreateLearnMoreButton( + props, + ref: ForwardedRef, +) { + const {t} = useTranslation(createLocaleNamespace) + return ( + + {troubleshootingOpen && ( + + + {t('linking-in-progress-dialog.troubleshooting.content')} + + + )} + + ) diff --git a/packages/sanity/src/core/create/start-in-create/StartInCreateAction.tsx b/packages/sanity/src/core/create/start-in-create/StartInCreateAction.tsx index 856da22909d..a09b9c31e94 100644 --- a/packages/sanity/src/core/create/start-in-create/StartInCreateAction.tsx +++ b/packages/sanity/src/core/create/start-in-create/StartInCreateAction.tsx @@ -91,5 +91,6 @@ export function StartInCreateAction( } setDialogOpen(true) }, + tone: 'default', } } diff --git a/packages/sanity/src/core/create/start-in-create/StartInCreateDialog.tsx b/packages/sanity/src/core/create/start-in-create/StartInCreateDialog.tsx index e32eb18c095..0c7be17bcbc 100644 --- a/packages/sanity/src/core/create/start-in-create/StartInCreateDialog.tsx +++ b/packages/sanity/src/core/create/start-in-create/StartInCreateDialog.tsx @@ -1,12 +1,12 @@ import {LaunchIcon} from '@sanity/icons' -import {Box, Checkbox, Flex, Stack, Text, useToast} from '@sanity/ui' +import {Checkbox, Flex, Stack, Text, useToast} from '@sanity/ui' import {useCallback, useEffect, useId, useState} from 'react' import {Button} from '../../../ui-components' import {useTranslation} from '../../i18n' import {useWorkspace} from '../../studio' import {CreateLearnMoreButton} from '../components/CreateLearnMoreButton' -import {StartInCreateSvg} from '../components/media/StartInCreateSvg' +import {CreateSvg} from '../components/media/CreateSvg' import {createLocaleNamespace} from '../i18n' import {getCreateLinkUrl} from '../useCreateDocumentUrl' import {useSanityCreateTelemetry} from '../useSanityCreateTelemetry' @@ -61,33 +61,27 @@ export function StartInCreateDialog(props: StartInCreateDialogProps) { return ( - - - - - {t('start-in-create-dialog.lede')} - - - {t('start-in-create-dialog.details')} - + + + {t('start-in-create-dialog.lede')} + + + {t('start-in-create-dialog.details')} + - + {t('start-in-create-dialog.dont-remind-me-checkbox')} - - - - -