diff --git a/apps/dev/src/cms.tsx b/apps/dev/src/cms.tsx index 4374d0519..cb73f4576 100644 --- a/apps/dev/src/cms.tsx +++ b/apps/dev/src/cms.tsx @@ -7,9 +7,13 @@ import * as schema from './schema' const editor = Config.role('Editor', { permissions(policy) { policy.set( + { + workspace: cms.workspaces.secondary, + allow: {explore: false, read: false}, + }, { workspace: cms.workspaces.primary, - allow: {read: true}, + allow: {read: true, explore: true}, grant: 'explicit' }, { diff --git a/src/picker/entry/EntryPicker.browser.tsx b/src/picker/entry/EntryPicker.browser.tsx index e1808a4fd..4322efc49 100644 --- a/src/picker/entry/EntryPicker.browser.tsx +++ b/src/picker/entry/EntryPicker.browser.tsx @@ -13,6 +13,7 @@ import {useEntryEditor} from 'alinea/dashboard/hook/UseEntryEditor' import {useFocusList} from 'alinea/dashboard/hook/UseFocusList' import {useGraph} from 'alinea/dashboard/hook/UseGraph' import {useLocale} from 'alinea/dashboard/hook/UseLocale' +import {usePolicy} from 'alinea/dashboard/hook/UsePolicy' import {useRoot} from 'alinea/dashboard/hook/UseRoot' import {useWorkspace} from 'alinea/dashboard/hook/UseWorkspace' import {Breadcrumbs, BreadcrumbsItem} from 'alinea/dashboard/view/Breadcrumbs' @@ -61,6 +62,28 @@ const styles = styler(css) export interface EntryPickerModalProps extends PickerProps {} +function firstExplorableRootName( + workspaceName: string, + workspace: Workspace, + policy: ReturnType +) { + return entries(workspace).find(([rootName]) => + policy.canExplore({workspace: workspaceName, root: rootName}) + )?.[0] +} + +function firstExplorableMediaRootName( + workspaceName: string, + workspace: Workspace, + policy: ReturnType +) { + return entries(workspace).find( + ([rootName, root]) => + Root.isMediaRoot(root) && + policy.canExplore({workspace: workspaceName, root: rootName}) + )?.[0] +} + export function EntryPickerModal({ type, options, @@ -70,6 +93,7 @@ export function EntryPickerModal({ }: EntryPickerModalProps) { const config = useConfig() const graph = useGraph() + const policy = usePolicy() const editor = useEntryEditor() const {title, defaultView, max, pickChildren, showMedia} = options const [search, setSearch] = useState('') @@ -98,18 +122,72 @@ export function EntryPickerModal({ {suspense: true} ) const location = locationQuery.data + const explorableWorkspaces = useMemo(() => { + return entries(config.workspaces).filter(([name]) => + policy.canExplore({workspace: name}) + ) + }, [config.workspaces, policy]) + const availableWorkspaces = useMemo(() => { + if (!showMedia) return explorableWorkspaces + return explorableWorkspaces.filter(([name, workspace]) => + Boolean(firstExplorableMediaRootName(name, workspace, policy)) + ) + }, [explorableWorkspaces, policy, showMedia]) + const defaultWorkspaceName = + showMedia && + firstExplorableMediaRootName( + currentWorkspace, + config.workspaces[currentWorkspace], + policy + ) + ? currentWorkspace + : availableWorkspaces[0]?.[0] ?? currentWorkspace + const defaultMediaRoot = firstExplorableMediaRootName( + defaultWorkspaceName, + config.workspaces[defaultWorkspaceName], + policy + ) + const defaultRoot = firstExplorableRootName( + defaultWorkspaceName, + config.workspaces[defaultWorkspaceName], + policy + ) const [destination, setDestination] = useState({ - workspace: currentWorkspace, + workspace: defaultWorkspaceName, parentId: pickChildren ? editor?.entryId : undefined, - root: showMedia - ? Workspace.defaultMediaRoot(config.workspaces[currentWorkspace]) - : currentRoot, + root: showMedia ? defaultMediaRoot ?? currentRoot : defaultRoot ?? currentRoot, ...location, locale: locale }) - const workspace = config.workspaces[destination.workspace] + const destinationWorkspaceName = + config.workspaces[destination.workspace] + ? showMedia && + !firstExplorableMediaRootName( + destination.workspace, + config.workspaces[destination.workspace], + policy + ) + ? defaultWorkspaceName + : destination.workspace + : defaultWorkspaceName + const workspace = config.workspaces[destinationWorkspaceName] + const explorableRoots = useMemo(() => { + return entries(workspace).filter(([name]) => + policy.canExplore({workspace: destinationWorkspaceName, root: name}) + ) + }, [workspace, policy, destinationWorkspaceName]) + const destinationRootName = + explorableRoots.some(([name]) => name === destination.root) + ? destination.root + : showMedia + ? firstExplorableMediaRootName( + destinationWorkspaceName, + workspace, + policy + ) ?? currentRoot + : explorableRoots[0]?.[0] ?? currentRoot const workspaceData = Workspace.data(workspace) - const destinationRoot = Root.data(workspace[destination.root]) + const destinationRoot = Root.data(workspace[destinationRootName]) const locales = destinationRoot.i18n?.locales const destinationLocale = !destinationRoot.i18n ? undefined @@ -143,6 +221,8 @@ export function EntryPickerModal({ ) const withNavigation = options.enableNavigation || (!options.condition && !options.pickChildren) + const canSwitchWorkspace = availableWorkspaces.length > 1 + const showHeaderNavigation = withNavigation || (showMedia && canSwitchWorkspace) const conditionQuery = useQuery( ['entry-condition', graph, entry], () => { @@ -164,8 +244,8 @@ export function EntryPickerModal({ and: [ condition, { - _workspace: destination.workspace, - _root: destination.root, + _workspace: destinationWorkspaceName, + _root: destinationRootName, _parentId: parentId, _locale: destinationLocale } @@ -179,6 +259,8 @@ export function EntryPickerModal({ }, [ withNavigation, destination, + destinationWorkspaceName, + destinationRootName, destinationLocale, search, conditionQuery.data @@ -240,76 +322,124 @@ export function EntryPickerModal({ - {withNavigation && ( + {showHeaderNavigation && ( - + {canSwitchWorkspace ? ( + + + + {workspaceData.label} + + + + + {availableWorkspaces.map(([name, workspace]) => { + const nextRoot = showMedia + ? firstExplorableMediaRootName( + name, + workspace, + policy + )! + : firstExplorableRootName( + name, + workspace, + policy + )! + const root = workspace[nextRoot] + return ( + { + setDestination({ + workspace: name, + root: nextRoot, + parentId: undefined, + locale: Root.defaultLocale(root) + }) + }} + > + {Workspace.label(workspace)} + + ) + })} + + + ) : ( + + )} - - - - - {Root.label(workspace[destination.root])} - - - - - {entries(workspace).map(([name, root]) => { - return ( - + + + + + {Root.label(workspace[destinationRootName])} + + + + + {explorableRoots.map(([name, root]) => { + return ( + { + setDestination({ + workspace: destinationWorkspaceName, + root: name, + parentId: undefined, + locale: Root.defaultLocale(root) + }) + }} + > + {Root.label(root)} + + ) + })} + + + {destinationRoot.i18n && ( + { + setDestination({ + ...destination, + parentId: undefined, + locale + }) + }} + /> + )} + + {parentEntries?.map(({id, title}) => { + return ( + + - - ) - })} + {title} + + + ) + })} + + )} )}

@@ -375,7 +505,9 @@ export function EntryPickerModal({ position="left" destination={{ ...destination, - directory: workspaceMediaDir(config, destination.workspace) + workspace: destinationWorkspaceName, + root: destinationRootName, + directory: workspaceMediaDir(config, destinationWorkspaceName) }} max={max} toggleSelect={handleSelect}