Skip to content

Commit

Permalink
feat(structure): add history and review tabs to changes inspector pan…
Browse files Browse the repository at this point in the history
…el (#7435)

* feat(structure): add history and review tabs to changes inspector panel

* fix(structure): move changes inspector navigation to paneRouter

* fix(structure): update isReviewChangesOpen check in ChangeConnectorRoot

* fix(structure): rename uses of version to revision

* chore(core): rename review changes action to history
  • Loading branch information
pedrobonamin committed Nov 14, 2024
1 parent a4c1200 commit b8b0b80
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 65 deletions.
6 changes: 3 additions & 3 deletions packages/sanity/src/core/i18n/bundles/studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export const studioLocaleStrings = defineLocalesResources('studio', {
'calendar.weekday-names.short.wednesday': 'Wed',

/** Label for the close button label in Review Changes pane */
'changes.action.close-label': 'Close review changes',
'changes.action.close-label': 'Close history',
/** Cancel label for revert button prompt action */
'changes.action.revert-all-cancel': 'Cancel',
/** Revert all confirm label for revert button action - used on prompt button + review changes pane */
Expand Down Expand Up @@ -313,7 +313,7 @@ export const studioLocaleStrings = defineLocalesResources('studio', {
/** Label for when the action of the change was a removal, eg a field was cleared, an array item was removed, an asset was deselected or similar */
'changes.removed-label': 'Removed',
/** Title for the Review Changes pane */
'changes.title': 'Review changes',
'changes.title': 'History',

/** --- Common components --- */
/** Tooltip text for context menu buttons */
Expand Down Expand Up @@ -1658,7 +1658,7 @@ export const studioLocaleStrings = defineLocalesResources('studio', {
* Label for determining since which version the changes for timeline menu dropdown are showing.
* Receives the time label as a parameter (`timestamp`).
*/
'timeline.since': 'Since: {{timestamp, datetime}}',
'timeline.since': '{{timestamp, datetime}}',
/** Label for missing change version for timeline menu dropdown are showing */
'timeline.since-version-missing': 'Since: unknown version',
/** Aria label for the action buttons in the PTE toolbar */
Expand Down
19 changes: 13 additions & 6 deletions packages/sanity/src/structure/i18n/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ const structureLocaleStrings = defineLocalesResources('structure', {
/** Tooltip when publish button is waiting for validation and async tasks to complete.*/
'action.publish.waiting': 'Waiting for tasks to finish before publishing',

/** Message prompting the user to confirm that they want to restore to an earlier version*/
/** Message prompting the user to confirm that they want to restore to an earlier revision*/
'action.restore.confirm.message': 'Are you sure you want to restore this document?',
/** Fallback tooltip for when user is looking at the initial version */
'action.restore.disabled.cannot-restore-initial': "You can't restore to the initial version",
/** Fallback tooltip for when user is looking at the initial revision */
'action.restore.disabled.cannot-restore-initial': "You can't restore to the initial revision",

/** Label for the "Restore" document action */
'action.restore.label': 'Revert to revision',
Expand All @@ -90,7 +90,7 @@ const structureLocaleStrings = defineLocalesResources('structure', {
'This document has live edit enabled and cannot be unpublished',

/** The text for the restore button on the deleted document banner */
'banners.deleted-document-banner.restore-button.text': 'Restore most recent version',
'banners.deleted-document-banner.restore-button.text': 'Restore most recent revision',
/** The text content for the deleted document banner */
'banners.deleted-document-banner.text': 'This document has been deleted.',
/** The text content for the deprecated document type banner */
Expand Down Expand Up @@ -147,7 +147,14 @@ const structureLocaleStrings = defineLocalesResources('structure', {
'buttons.split-pane-close-button.title': 'Close split pane',
/** The title for the close group button on the split pane on the document panel header */
'buttons.split-pane-close-group-button.title': 'Close pane group',

/** The label used in the changes inspector for the from selector */
'changes.from.label': 'From',
/* The label for the history tab in the changes inspector*/
'changes.tab.history': 'History',
/* The label for the review tab in the changes inspector*/
'changes.tab.review-changes': 'Review changes',
/** The label used in the changes inspector for the to selector */
'changes.to.label': 'To',
/** The text in the "Cancel" button in the confirm delete dialog that cancels the action and closes the dialog */
'confirm-delete-dialog.cancel-button.text': 'Cancel',
/** Used in `confirm-delete-dialog.cdr-summary.title` */
Expand Down Expand Up @@ -378,7 +385,7 @@ const structureLocaleStrings = defineLocalesResources('structure', {
'<Strong>{{title}}</Strong> was restored',
/** The text when an unpublish operation succeeded */
'panes.document-operation-results.operation-success_unpublish':
'<Strong>{{title}}</Strong> was unpublished. A draft has been created from the latest published version.',
'<Strong>{{title}}</Strong> was unpublished. A draft has been created from the latest published revision.',
/** The document title shown when document title is "undefined" in operation message */
'panes.document-operation-results.operation-undefined-title': 'Untitled',
/** The title of the reconnecting toast */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => {
}

if (resolvedChangesInspector) {
openInspector(resolvedChangesInspector.name)
openInspector(resolvedChangesInspector.name, {changesInspectorTab: 'review'})
}
}, [features.reviewChanges, openInspector, resolvedChangesInspector])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {type Path} from 'sanity-diff-patch'
import {styled} from 'styled-components'

import {TooltipDelayGroupProvider} from '../../../../ui-components'
import {Pane, PaneFooter, usePane, usePaneLayout} from '../../../components'
import {Pane, PaneFooter, usePane, usePaneLayout, usePaneRouter} from '../../../components'
import {DOCUMENT_PANEL_PORTAL_ELEMENT} from '../../../constants'
import {structureLocaleNamespace} from '../../../i18n'
import {useStructureTool} from '../../../useStructureTool'
Expand Down Expand Up @@ -82,7 +82,7 @@ export function DocumentLayout() {
schemaType,
value,
} = useDocumentPane()

const {params: paneParams} = usePaneRouter()
const {features} = useStructureTool()
const {t} = useTranslation(structureLocaleNamespace)
const {collapsed: layoutCollapsed} = usePaneLayout()
Expand Down Expand Up @@ -228,7 +228,7 @@ export function DocumentLayout() {
<Flex direction="column" flex={1} height={layoutCollapsed ? undefined : 'fill'}>
<StyledChangeConnectorRoot
data-testid="change-connector-root"
isReviewChangesOpen={changesOpen}
isReviewChangesOpen={changesOpen && paneParams?.changesInspectorTab === 'review'}
onOpenReviewChanges={onHistoryOpen}
onSetFocus={onConnectorSetFocus}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ import {
useMemo,
useState,
} from 'react'
import {
type DocumentActionDescription,
useFieldActions,
useTimelineSelector,
useTranslation,
} from 'sanity'
import {type DocumentActionDescription, useFieldActions, useTranslation} from 'sanity'

import {Button, TooltipDelayGroupProvider} from '../../../../../ui-components'
import {
Expand All @@ -33,7 +28,6 @@ import {type PaneMenuItem} from '../../../../types'
import {useStructureTool} from '../../../../useStructureTool'
import {ActionDialogWrapper, ActionMenuListItem} from '../../statusBar/ActionMenuButton'
import {isRestoreAction} from '../../statusBar/DocumentStatusBarActions'
import {TimelineMenu} from '../../timeline'
import {useDocumentPane} from '../../useDocumentPane'
import {DocumentHeaderTabs} from './DocumentHeaderTabs'
import {DocumentHeaderTitle} from './DocumentHeaderTitle'
Expand Down Expand Up @@ -84,9 +78,6 @@ export const DocumentPanelHeader = memo(
const contextMenuNodes = useMemo(() => menuNodes.filter(isNotMenuNodeButton), [menuNodes])
const showTabs = views.length > 1

// Subscribe to external timeline state changes
const rev = useTimelineSelector(timelineStore, (state) => state.revTime)

const {collapsed, isLast} = usePane()
// Prevent focus if this is the last (non-collapsed) pane.
const tabIndex = isLast && !collapsed ? -1 : 0
Expand Down Expand Up @@ -152,7 +143,6 @@ export const DocumentPanelHeader = memo(
/>
)
}
subActions={<TimelineMenu chunk={rev} mode="rev" placement="bottom-end" />}
actions={
<Flex align="center" gap={1}>
{unstable_languageFilter.length > 0 && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import {type ObjectDiff} from '@sanity/diff'
import {AvatarStack, BoundaryElementProvider, Box, Card, Flex} from '@sanity/ui'
import {AvatarStack, BoundaryElementProvider, Box, Card, Flex, Text} from '@sanity/ui'
import {type ReactElement, useMemo, useRef} from 'react'
import {
ChangeFieldWrapper,
ChangeList,
DiffTooltip,
type DocumentChangeContextInstance,
type DocumentInspectorProps,
LoadingBlock,
NoChanges,
type ObjectSchemaType,
Expand All @@ -18,7 +17,7 @@ import {
import {DocumentChangeContext} from 'sanity/_singletons'
import {styled} from 'styled-components'

import {DocumentInspectorHeader} from '../../documentInspector'
import {structureLocaleNamespace} from '../../../../i18n'
import {TimelineMenu} from '../../timeline'
import {useDocumentPane} from '../../useDocumentPane'
import {collectLatestAuthorAnnotations} from './helpers'
Expand All @@ -30,12 +29,21 @@ const Scroller = styled(ScrollContainer)`
scroll-behavior: smooth;
`

export function ChangesInspector(props: DocumentInspectorProps): ReactElement {
const {onClose} = props
const Grid = styled(Box)`
&:not([hidden]) {
display: grid;
}
grid-template-columns: 48px 1fr;
align-items: center;
gap: 0.25em;
`

export function ChangesInspector({showChanges}: {showChanges: boolean}): ReactElement {
const {documentId, schemaType, timelineError, timelineStore, value} = useDocumentPane()
const scrollRef = useRef<HTMLDivElement | null>(null)

// Subscribe to external timeline state changes
const rev = useTimelineSelector(timelineStore, (state) => state.revTime)
const diff = useTimelineSelector(timelineStore, (state) => state.diff)
const onOlderRevision = useTimelineSelector(timelineStore, (state) => state.onOlderRevision)
const selectionState = useTimelineSelector(timelineStore, (state) => state.selectionState)
Expand All @@ -46,6 +54,7 @@ export function ChangesInspector(props: DocumentInspectorProps): ReactElement {
// Note that we are using the studio core namespace here, as changes theoretically should
// be part of Sanity core (needs to be moved from structure at some point)
const {t} = useTranslation('studio')
const {t: structureT} = useTranslation(structureLocaleNamespace)

const documentContext: DocumentChangeContextInstance = useMemo(
() => ({
Expand All @@ -67,45 +76,48 @@ export function ChangesInspector(props: DocumentInspectorProps): ReactElement {

return (
<Flex data-testid="review-changes-pane" direction="column" height="fill" overflow="hidden">
<DocumentInspectorHeader
as="header"
closeButtonLabel={t('changes.action.close-label')}
flex="none"
onClose={onClose}
title={t('changes.title')}
>
<Flex gap={1} padding={3} paddingTop={0} paddingBottom={2}>
<Box flex={1}>
<TimelineMenu mode="since" chunk={sinceTime} placement="bottom-start" />
</Box>
<Box padding={3}>
<Grid paddingX={1}>
<Text size={1} muted>
{structureT('changes.from.label')}
</Text>

<Box flex="none">
<TimelineMenu mode="since" chunk={sinceTime} placement="bottom-start" />
<Text size={1} muted>
{structureT('changes.to.label')}
</Text>
<TimelineMenu chunk={rev} mode="rev" placement="bottom-end" />
</Grid>
{changeAnnotations.length > 0 && (
<Flex width={'full'} justify={'flex-end'} padding={3} paddingBottom={0}>
<DiffTooltip
annotations={changeAnnotations}
description={t('changes.changes-by-author')}
portal
>
<AvatarStack maxLength={4} aria-label={t('changes.changes-by-author')}>
{changeAnnotations.map(({author}) => (
<UserAvatar key={author} user={author} />
<UserAvatar key={author} user={author} size={0} />
))}
</AvatarStack>
</DiffTooltip>
</Box>
</Flex>
</DocumentInspectorHeader>
</Flex>
)}
</Box>

<Card flex={1}>
<BoundaryElementProvider element={scrollRef.current}>
<Scroller data-ui="Scroller" ref={scrollRef}>
<Box flex={1} padding={4}>
<Content
diff={diff}
documentContext={documentContext}
error={timelineError}
loading={loading}
schemaType={schemaType}
/>
<Box flex={1} padding={3} paddingTop={2} height="fill">
{showChanges && (
<Content
diff={diff}
documentContext={documentContext}
error={timelineError}
loading={loading}
schemaType={schemaType}
/>
)}
</Box>
</Scroller>
</BoundaryElementProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {CloseIcon} from '@sanity/icons'
import {Box, Flex, TabList, TabPanel} from '@sanity/ui'
import {type DocumentInspectorProps, useTranslation} from 'sanity'
import {styled} from 'styled-components'

import {Button, Tab} from '../../../../../ui-components'
import {usePaneRouter} from '../../../../components/paneRouter/usePaneRouter'
import {structureLocaleNamespace} from '../../../../i18n'
import {HISTORY_INSPECTOR_NAME} from '../../constants'
import {ChangesInspector} from './ChangesInspector'
import {HistorySelector} from './HistorySelector'

const FadeInFlex = styled(Flex)`
opacity: 0;
transition: opacity 200ms;
&[data-ready] {
opacity: 1;
}
`
const TABS = ['history', 'review'] as const
const isValidTab = (tab: string | undefined): tab is (typeof TABS)[number] =>
// @ts-expect-error TS doesn't understand the type guard
tab && TABS.includes(tab)

export function ChangesTabs(props: DocumentInspectorProps) {
const {params, setParams} = usePaneRouter()
const {t} = useTranslation(structureLocaleNamespace)
const isReady = params?.inspect === HISTORY_INSPECTOR_NAME

const paneRouterTab = isValidTab(params?.changesInspectorTab)
? params.changesInspectorTab
: TABS[0]
const setPaneRouterTab = (tab: (typeof TABS)[number]) =>
setParams({
...params,
changesInspectorTab: tab,
})

return (
<FadeInFlex direction="column" padding={0} height="fill" data-ready={isReady ? '' : undefined}>
<Flex align="center" padding={3} gap={2}>
<TabList space={1} flex={1}>
<Tab
aria-controls="history-panel"
id="history-tab"
label={t('changes.tab.history')}
onClick={() => setPaneRouterTab('history')}
selected={paneRouterTab === 'history'}
/>
<Tab
aria-controls="review-changes-panel"
id="changes-tab"
label={t('changes.tab.review-changes')}
onClick={() => setPaneRouterTab('review')}
selected={paneRouterTab === 'review'}
/>
</TabList>
<Box flex="none">
<Button
aria-label={t('changes.action.close-label')}
icon={CloseIcon}
mode="bleed"
onClick={props.onClose}
tooltipProps={{content: t('document-inspector.close-button.tooltip')}}
/>
</Box>
</Flex>

<TabPanel
aria-labelledby="history-tab"
height="fill"
hidden={paneRouterTab !== 'history'}
id="history-panel"
>
<HistorySelector showList={paneRouterTab === 'history'} />
</TabPanel>

<TabPanel
aria-labelledby="review-tab"
hidden={paneRouterTab !== 'review'}
id="review-panel"
height="fill"
>
<ChangesInspector showChanges={paneRouterTab === 'review'} />
</TabPanel>
</FadeInFlex>
)
}
Loading

0 comments on commit b8b0b80

Please sign in to comment.