From 177bc79763a5567a028c1ead33794ea9445d171a Mon Sep 17 00:00:00 2001 From: Nina Andal Aarvik Date: Tue, 12 Mar 2024 16:14:09 +0300 Subject: [PATCH] fix(tasks): show pending tasks in document footer (#5894) * fix(tasks): show pending tasks in document footer * fix(tasks): add button to open tasks * fix(tasks): remove comment * fix(tasks): move out of document * fix(tasks): add conditional to tasks footer * fix(tasks): fix ts error * fix(tasks): update pr with comments * fix(tasks): keep task sidebar open if clicked on badge * fix(tasks): pluralize task text * fix(tasks): change React.ReactNode for ReactNode and remove data-as from button --------- Co-authored-by: Pedro Bonamin --- .../src/core/config/configPropertyReducers.ts | 26 +++++++++++ .../sanity/src/core/config/prepareConfig.ts | 6 +++ packages/sanity/src/core/config/types.ts | 7 +++ .../panes/document/DocumentPaneContext.ts | 3 ++ .../panes/document/DocumentPaneProvider.tsx | 18 +++++--- .../statusBar/DocumentStatusBarActions.tsx | 3 ++ .../sanity/src/tasks/plugin/TasksBadge.tsx | 26 ----------- .../src/tasks/plugin/TasksFooterOpenTasks.tsx | 44 +++++++++++++++++++ packages/sanity/src/tasks/plugin/index.tsx | 9 ++-- 9 files changed, 105 insertions(+), 37 deletions(-) delete mode 100644 packages/sanity/src/tasks/plugin/TasksBadge.tsx create mode 100644 packages/sanity/src/tasks/plugin/TasksFooterOpenTasks.tsx diff --git a/packages/sanity/src/core/config/configPropertyReducers.ts b/packages/sanity/src/core/config/configPropertyReducers.ts index 829672bd124..ff802d191f8 100644 --- a/packages/sanity/src/core/config/configPropertyReducers.ts +++ b/packages/sanity/src/core/config/configPropertyReducers.ts @@ -1,4 +1,5 @@ import {type AssetSource, type SchemaTypeDefinition} from '@sanity/types' +import {type ReactNode} from 'react' import {type LocaleConfigContext, type LocaleDefinition, type LocaleResourceBundle} from '../i18n' import {type Template, type TemplateItem} from '../templates' @@ -308,6 +309,31 @@ export const documentCommentsEnabledReducer = (opts: { return result } +export const internalTasksReducer = (opts: { + config: PluginOptions +}): {footerAction: ReactNode} | undefined => { + const {config} = opts + const flattenedConfig = flattenConfig(config, []) + + const result = flattenedConfig.reduce( + (acc: {footerAction: ReactNode} | undefined, {config: innerConfig}) => { + const resolver = innerConfig.__internal_tasks + + if (!resolver) return acc + if (typeof resolver === 'object' && resolver.footerAction) return resolver + + throw new Error( + `Expected \`__internal__tasks\` to be an object with footerAction, but received ${getPrintableType( + resolver, + )}`, + ) + }, + undefined, + ) + + return result +} + export const partialIndexingEnabledReducer = (opts: { config: PluginOptions initialValue: boolean diff --git a/packages/sanity/src/core/config/prepareConfig.ts b/packages/sanity/src/core/config/prepareConfig.ts index 4300f728a0a..258ae873f67 100644 --- a/packages/sanity/src/core/config/prepareConfig.ts +++ b/packages/sanity/src/core/config/prepareConfig.ts @@ -29,6 +29,7 @@ import { initialDocumentActions, initialDocumentBadges, initialLanguageFilter, + internalTasksReducer, newDocumentOptionsResolver, newSearchEnabledReducer, partialIndexingEnabledReducer, @@ -472,6 +473,10 @@ function resolveSource({ templates, auth, i18n: i18n.source, + // eslint-disable-next-line camelcase + __internal_tasks: internalTasksReducer({ + config, + }), document: { actions: (partialContext) => resolveConfigProperty({ @@ -533,6 +538,7 @@ function resolveSource({ }, }, }, + form: { file: { assetSources: resolveConfigProperty({ diff --git a/packages/sanity/src/core/config/types.ts b/packages/sanity/src/core/config/types.ts index e90ba85c6f5..9f4628b5adf 100644 --- a/packages/sanity/src/core/config/types.ts +++ b/packages/sanity/src/core/config/types.ts @@ -358,6 +358,10 @@ export interface PluginOptions { tools?: Tool[] | ComposableOption form?: SanityFormConfig + __internal_tasks?: { + footerAction: ReactNode + } + studio?: { /** * Components for the studio. @@ -653,6 +657,9 @@ export interface Source { } } + /** @internal */ + __internal_tasks?: {footerAction: ReactNode} + /** * Form-related functionality. * @hidden diff --git a/packages/sanity/src/structure/panes/document/DocumentPaneContext.ts b/packages/sanity/src/structure/panes/document/DocumentPaneContext.ts index f227d9d1b0f..178619c383d 100644 --- a/packages/sanity/src/structure/panes/document/DocumentPaneContext.ts +++ b/packages/sanity/src/structure/panes/document/DocumentPaneContext.ts @@ -81,6 +81,9 @@ export interface DocumentPaneContextValue { isDeleted: boolean isPermissionsLoading: boolean unstable_languageFilter: DocumentLanguageFilterComponent[] + __internal_tasks?: { + footerAction: React.ReactNode + } } /** @internal */ diff --git a/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx b/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx index aad8ddb868d..fad10c844ed 100644 --- a/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx +++ b/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ import {isActionEnabled} from '@sanity/schema/_internal' import { type ObjectSchemaType, @@ -62,12 +63,15 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { const schema = useSchema() const templates = useTemplates() const { - actions: documentActions, - badges: documentBadges, - unstable_fieldActions: fieldActionsResolver, - unstable_languageFilter: languageFilterResolver, - inspectors: inspectorsResolver, - } = useSource().document + __internal_tasks, + document: { + actions: documentActions, + badges: documentBadges, + unstable_fieldActions: fieldActionsResolver, + unstable_languageFilter: languageFilterResolver, + inspectors: inspectorsResolver, + }, + } = useSource() const presenceStore = usePresenceStore() const paneRouter = usePaneRouter() const setPaneParams = paneRouter.setParams @@ -583,6 +587,7 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { focusPath, inspector: currentInspector || null, inspectors, + __internal_tasks, onBlur: handleBlur, onChange: handleChange, onFocus: handleFocus, @@ -622,6 +627,7 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { unstable_languageFilter: languageFilter, }), [ + __internal_tasks, actions, activeViewId, badges, diff --git a/packages/sanity/src/structure/panes/document/statusBar/DocumentStatusBarActions.tsx b/packages/sanity/src/structure/panes/document/statusBar/DocumentStatusBarActions.tsx index a6908794300..9a4719591bc 100644 --- a/packages/sanity/src/structure/panes/document/statusBar/DocumentStatusBarActions.tsx +++ b/packages/sanity/src/structure/panes/document/statusBar/DocumentStatusBarActions.tsx @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ import {Flex, Hotkeys, LayerProvider, Stack, Text} from '@sanity/ui' import {memo, useMemo, useState} from 'react' import {type DocumentActionDescription, useTimelineSelector} from 'sanity' @@ -17,6 +18,7 @@ interface DocumentStatusBarActionsInnerProps { function DocumentStatusBarActionsInner(props: DocumentStatusBarActionsInnerProps) { const {disabled, showMenu, states} = props + const {__internal_tasks} = useDocumentPane() const [firstActionState, ...menuActionStates] = states const [buttonElement, setButtonElement] = useState(null) @@ -42,6 +44,7 @@ function DocumentStatusBarActionsInner(props: DocumentStatusBarActionsInnerProps return ( + {__internal_tasks && __internal_tasks.footerAction} {firstActionState && ( diff --git a/packages/sanity/src/tasks/plugin/TasksBadge.tsx b/packages/sanity/src/tasks/plugin/TasksBadge.tsx deleted file mode 100644 index 5e8acd14e80..00000000000 --- a/packages/sanity/src/tasks/plugin/TasksBadge.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import {useMemo} from 'react' - -import {useTasks} from '../src' - -/** - * Badge that shows how many pending tasks are assigned to the current document. - * It's not the final stage, we want to introduce a button positioned left to the publish button for easier - * discovery of the pending tasks. - * @internal - */ -export function DocumentBadge() { - const {data, activeDocument} = useTasks() - const pendingTasks = useMemo( - () => - data.filter((item) => { - return item.target?.document._ref === activeDocument?.documentId && item.status === 'open' - }), - [activeDocument, data], - ) - - if (pendingTasks.length === 0) return null - return { - label: ` ${pendingTasks.length} open tasks`, - color: 'primary' as const, - } -} diff --git a/packages/sanity/src/tasks/plugin/TasksFooterOpenTasks.tsx b/packages/sanity/src/tasks/plugin/TasksFooterOpenTasks.tsx new file mode 100644 index 00000000000..3dbca588954 --- /dev/null +++ b/packages/sanity/src/tasks/plugin/TasksFooterOpenTasks.tsx @@ -0,0 +1,44 @@ +import {useCallback, useMemo} from 'react' + +import {Button} from '../../ui-components' +import {useTasks, useTasksEnabled} from '../src' + +/** + * Button that shows how many pending tasks are assigned to the current document. + * Clicking it will open the task sidebar, showing the open tasks related to the document. + * + * todo: just show the tab with tasks related to the document + * @internal + */ +export function TasksFooterOpenTasks() { + const {data, activeDocument, toggleOpen, isOpen} = useTasks() + const {enabled} = useTasksEnabled() + + const pendingTasks = useMemo( + () => + data.filter((item) => { + return item.target?.document._ref === activeDocument?.documentId && item.status === 'open' + }), + [activeDocument, data], + ) + + const handleOnClick = useCallback(() => { + if (isOpen) { + return + } + toggleOpen() + }, [isOpen, toggleOpen]) + + if (pendingTasks.length === 0 || !enabled) return null + + const pluralizedTask = `task${pendingTasks.length > 1 ? 's' : ''}` + + return ( +