-
Notifications
You must be signed in to change notification settings - Fork 451
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sanity): add Rendering Context Store
- Loading branch information
Showing
6 changed files
with
223 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
packages/sanity/src/core/store/renderingContext/coreUiRenderingContext.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import {distinctUntilChanged, map, of, type OperatorFunction, pipe, switchMap} from 'rxjs' | ||
|
||
import {isCoreUiRenderingContext, type StudioRenderingContext} from './types' | ||
|
||
// Core UI Rendering Context is provided via the URL hash, and remains static the entire duration | ||
// Studio is rendered inside the Core UI iframe. | ||
// | ||
// However, the URL hash is liable to be lost when Studio renders (for example, when | ||
// `ActiveWorkspaceMatcher` performs a redirect, or when the user navigates inside the app). | ||
// Therefore, the URL hash is captured as soon as this code is evaluated, and later referenced | ||
// when a consumer subscribes to the store. | ||
const INITIAL_URL_HASH = location?.hash | ||
|
||
const CORE_UI_MODE_PARAMETER = 'mode' | ||
const CORE_UI_MODE_DELIMITER = '--' | ||
const CORE_UI_MODE_NAME = 'core-ui' | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export function coreUiRenderingContext(): OperatorFunction< | ||
StudioRenderingContext | undefined, | ||
StudioRenderingContext | undefined | ||
> { | ||
return pipe( | ||
switchMap((renderingContext) => { | ||
if (renderingContext) { | ||
return of(renderingContext) | ||
} | ||
|
||
return of(INITIAL_URL_HASH.slice(1)).pipe( | ||
distinctUntilChanged(), | ||
map((hash) => new URLSearchParams(hash).get(CORE_UI_MODE_PARAMETER)), | ||
map((coreUiHashParam) => { | ||
if (coreUiHashParam === null) { | ||
return undefined | ||
} | ||
|
||
const [mode, environment] = coreUiHashParam.split(CORE_UI_MODE_DELIMITER) | ||
|
||
const coreUirenderingContext = { | ||
name: mode === CORE_UI_MODE_NAME ? 'coreUi' : undefined, | ||
metadata: { | ||
environment, | ||
}, | ||
} | ||
|
||
if (isCoreUiRenderingContext(coreUirenderingContext)) { | ||
return coreUirenderingContext | ||
} | ||
|
||
return undefined | ||
}), | ||
) | ||
}), | ||
) | ||
} |
30 changes: 30 additions & 0 deletions
30
packages/sanity/src/core/store/renderingContext/createRenderingContextStore.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import {of, shareReplay} from 'rxjs' | ||
|
||
import {coreUiRenderingContext} from './coreUiRenderingContext' | ||
import {defaultRenderingContext} from './defaultRenderingContext' | ||
import {listCapabilities} from './listCapabilities' | ||
import {type RenderingContextStore} from './types' | ||
|
||
/** | ||
* Rendering Context Store provides information about where Studio is being rendered, and which | ||
* capabilities are provided by the rendering context. | ||
* | ||
* This can be used to adapt parts of the Studio UI that are provided by the rendering context, | ||
* such as the global user menu. | ||
* | ||
* @internal | ||
*/ | ||
export function createRenderingContextStore(): RenderingContextStore { | ||
const renderingContext = of(undefined).pipe( | ||
coreUiRenderingContext(), | ||
defaultRenderingContext(), | ||
shareReplay(1), | ||
) | ||
|
||
const capabilities = renderingContext.pipe(listCapabilities(), shareReplay(1)) | ||
|
||
return { | ||
renderingContext, | ||
capabilities, | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
packages/sanity/src/core/store/renderingContext/defaultRenderingContext.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import {map, type OperatorFunction} from 'rxjs' | ||
|
||
import {type DefaultRenderingContext, type StudioRenderingContext} from './types' | ||
|
||
const DEFAULT_RENDERING_CONTEXT: DefaultRenderingContext = { | ||
name: 'default', | ||
metadata: {}, | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export function defaultRenderingContext(): OperatorFunction< | ||
StudioRenderingContext | undefined, | ||
StudioRenderingContext | ||
> { | ||
return map((renderingContext) => renderingContext ?? DEFAULT_RENDERING_CONTEXT) | ||
} |
18 changes: 18 additions & 0 deletions
18
packages/sanity/src/core/store/renderingContext/listCapabilities.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import {map, type OperatorFunction} from 'rxjs' | ||
|
||
import {type CapabilityRecord, type StudioRenderingContext} from './types' | ||
|
||
const capabilitiesByRenderingContext: Record<StudioRenderingContext['name'], CapabilityRecord> = { | ||
coreUi: { | ||
globalUserMenu: true, | ||
globalWorkspaceControl: true, | ||
}, | ||
default: {}, | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export function listCapabilities(): OperatorFunction<StudioRenderingContext, CapabilityRecord> { | ||
return map((renderingContext) => capabilitiesByRenderingContext[renderingContext.name]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import {type Observable} from 'rxjs' | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export type BaseStudioRenderingContext< | ||
Name extends string = string, | ||
Metadata = Record<PropertyKey, never>, | ||
> = { | ||
name: Name | ||
metadata: Metadata | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export type DefaultRenderingContext = BaseStudioRenderingContext<'default'> | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export type CoreUiRenderingContext = BaseStudioRenderingContext< | ||
'coreUi', | ||
{ | ||
environment: 'staging' | 'production' | ||
} | ||
> | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export type StudioRenderingContext = DefaultRenderingContext | CoreUiRenderingContext | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const capabilities = ['globalUserMenu', 'globalWorkspaceControl'] as const | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export type Capability = (typeof capabilities)[number] | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export type CapabilityRecord = Partial<Record<Capability, boolean>> | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export type RenderingContextStore = { | ||
renderingContext: Observable<StudioRenderingContext> | ||
capabilities: Observable<CapabilityRecord> | ||
} | ||
|
||
/** | ||
* Check whether the provided value satisfies the `CoreUiRenderingContext` type. | ||
* | ||
* @internal | ||
*/ | ||
export function isCoreUiRenderingContext( | ||
maybeCoreUiRenderingContext: unknown, | ||
): maybeCoreUiRenderingContext is CoreUiRenderingContext { | ||
return ( | ||
typeof maybeCoreUiRenderingContext === 'object' && | ||
maybeCoreUiRenderingContext !== null && | ||
'name' in maybeCoreUiRenderingContext && | ||
maybeCoreUiRenderingContext.name === 'coreUi' && | ||
'metadata' in maybeCoreUiRenderingContext && | ||
typeof maybeCoreUiRenderingContext.metadata === 'object' && | ||
maybeCoreUiRenderingContext.metadata !== null && | ||
'environment' in maybeCoreUiRenderingContext.metadata && | ||
typeof maybeCoreUiRenderingContext.metadata.environment === 'string' && | ||
['production', 'staging'].includes(maybeCoreUiRenderingContext.metadata.environment) | ||
) | ||
} |