Skip to content

Commit

Permalink
feat: refactor createTrackerScope internals to be more efficient (#…
Browse files Browse the repository at this point in the history
…6946)

* feat: add new createTrackerScope implementation

* refactor: remove debug code and previous implementation
  • Loading branch information
stipsan authored and ricokahler committed Jul 23, 2024
1 parent 8b92196 commit c4f9a1f
Show file tree
Hide file tree
Showing 29 changed files with 472 additions and 399 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {createContext} from 'react'

import type {
ChangeIndicatorTrackerContextStoreType,
ChangeIndicatorTrackerGetSnapshotType,
} from '../../../core/changeIndicators/ChangeIndicatorTrackerContexts'

/** @internal */
export const ChangeIndicatorTrackerContextStore =
createContext<ChangeIndicatorTrackerContextStoreType>(null)

/** @internal */
export const ChangeIndicatorTrackerContextGetSnapshot =
createContext<ChangeIndicatorTrackerGetSnapshotType>(null)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {createContext} from 'react'

import type {
PresenceTrackerContextStoreType,
PresenceTrackerGetSnapshotType,
} from '../../../../core/presence/overlay/PresenceTrackerContexts'

/** @internal */
export const PresenceTrackerContextStore = createContext<PresenceTrackerContextStoreType>(null)

/** @internal */
export const PresenceTrackerContextGetSnapshot = createContext<PresenceTrackerGetSnapshotType>(null)
2 changes: 2 additions & 0 deletions packages/sanity/src/_singletons/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './core/changeIndicators/ChangeIndicatorTrackerContexts'
export * from './core/changeIndicators/ConnectorContext'
export * from './core/components/previewCard/PreviewCardContext'
export * from './core/components/scroll/scrollContext'
Expand Down Expand Up @@ -25,6 +26,7 @@ export * from './core/form/studio/TreeEditingEnabledContext'
export * from './core/form/studio/ValidationContext'
export * from './core/i18n/LocaleContext'
export * from './core/presence/FormFieldPresenceContext'
export * from './core/presence/overlay/PresenceTrackerContexts'
export * from './core/schedulePublishing/DocumentActionPropsContext'
export * from './core/schedulePublishing/tool/ScheduledPublishingEnabledContext'
export * from './core/schedulePublishing/tool/SchedulePublishingUpsellContext'
Expand Down
43 changes: 32 additions & 11 deletions packages/sanity/src/core/changeIndicators/ChangeFieldWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import {type Path} from '@sanity/types'
import * as PathUtils from '@sanity/util/paths'
import {type ReactNode, type SyntheticEvent, useCallback, useContext, useRef, useState} from 'react'
import {
type ReactNode,
type SyntheticEvent,
useCallback,
useContext,
useMemo,
useState,
} from 'react'
import deepCompare from 'react-fast-compare'
import {ConnectorContext} from 'sanity/_singletons'

import {useReporter} from './tracker'
import {useChangeIndicatorsReporter} from './tracker'

/**
* This is used to draw the bar that wraps the diff components in the changes panel
*
* @internal
*/
export const ChangeFieldWrapper = (props: {path: Path; children: ReactNode; hasHover: boolean}) => {
const ref = useRef<HTMLDivElement>(null)
const {path, hasHover} = props
const {onSetFocus} = useContext(ConnectorContext)
const [isHover, setHover] = useState(false)

Expand All @@ -24,29 +31,43 @@ export const ChangeFieldWrapper = (props: {path: Path; children: ReactNode; hasH
setHover(false)
}, [])

useReporter(
`change-${PathUtils.toString(props.path)}`,
const [element, setElement] = useState<HTMLDivElement | null>(null)
const reporterId = useMemo(
() => (element ? `change-${PathUtils.toString(path)}` : null),
[element, path],
)
const reporterGetSnapshot = useCallback(
() => ({
element: ref.current!,
path: props.path,
element,
path,
isChanged: true,
hasFocus: false,
hasHover: isHover,
hasRevertHover: props.hasHover,
hasRevertHover: hasHover,
}),
[element, isHover, hasHover, path],
)
useChangeIndicatorsReporter(
reporterId,
reporterGetSnapshot,
// note: deepCompare should be ok here since we're not comparing deep values
deepCompare,
)

const handleClick = useCallback(
(event: SyntheticEvent) => {
setFocusWithStopPropagation(event, onSetFocus, props.path)
setFocusWithStopPropagation(event, onSetFocus, path)
},
[onSetFocus, props.path],
[onSetFocus, path],
)

return (
<div ref={ref} onClick={handleClick} onMouseLeave={onMouseLeave} onMouseEnter={onMouseEnter}>
<div
ref={setElement}
onClick={handleClick}
onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter}
>
{props.children}
</div>
)
Expand Down
23 changes: 16 additions & 7 deletions packages/sanity/src/core/changeIndicators/ChangeIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import {
memo,
type MouseEvent,
useCallback,
useRef,
useMemo,
useState,
} from 'react'
import deepCompare from 'react-fast-compare'

import {EMPTY_ARRAY} from '../util'
import {ElementWithChangeBar} from './ElementWithChangeBar'
import {useReporter} from './tracker'
import {useChangeIndicatorsReporter} from './tracker'

const ChangeBarWrapper = memo(function ChangeBarWrapper(
props: Omit<ComponentProps<'div'>, 'onChange'> & {
Expand Down Expand Up @@ -52,22 +52,31 @@ const ChangeBarWrapper = memo(function ChangeBarWrapper(
},
[onMouseLeaveProp],
)
const ref = useRef<HTMLDivElement | null>(null)
useReporter(
disabled ? null : `field-${PathUtils.toString(path)}`,

const [element, setElement] = useState<HTMLDivElement | null>(null)
const reporterId = useMemo(
() => (disabled || !element ? null : `field-${PathUtils.toString(path)}`),
[disabled, element, path],
)
const reporterGetSnapshot = useCallback(
() => ({
element: ref.current!,
element,
path: path,
isChanged: isChanged,
hasFocus: hasFocus,
hasHover: hasHover,
zIndex: layer.zIndex,
}),
[element, hasFocus, hasHover, isChanged, layer.zIndex, path],
)
useChangeIndicatorsReporter(
reporterId,
reporterGetSnapshot,
deepCompare, // note: deepCompare should be ok here since we're not comparing deep values
)

return (
<div {...restProps} ref={ref} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<div {...restProps} ref={setElement} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<ElementWithChangeBar
hasFocus={hasFocus}
isChanged={isChanged}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
type TrackerContextGetSnapshot,
type TrackerContextStore,
} from '../components/react-track-elements'
import {type ChangeIndicatorTrackerContextValue} from './types'

/**
* @internal
* @hidden
*/
export type ChangeIndicatorTrackerContextStoreType =
TrackerContextStore<ChangeIndicatorTrackerContextValue> | null

/**
* @internal
* @hidden
*/
export type ChangeIndicatorTrackerGetSnapshotType =
TrackerContextGetSnapshot<ChangeIndicatorTrackerContextValue> | null
3 changes: 0 additions & 3 deletions packages/sanity/src/core/changeIndicators/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// Toggle tracking of change indicator DOM positions and connectors overlay rendering
export const ENABLED = true

export const CORNER_RADIUS = 4

export const INTERACTIVE_STROKE_WIDTH = 16
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import {isScrollable} from './scrollUtils'

const SCROLL_INTO_VIEW_TOP_PADDING = -15

export function scrollIntoView(field: {element: HTMLElement; rect: Rect; bounds: Rect}): void {
// @TODO refactor this to use `compute-scroll-into-view`
export function scrollIntoView(field: {
element: HTMLElement | null
rect: Rect
bounds: Rect
}): void {
const element = field.element
if (!element) return

/*
* Start at current element and check the parent for a scroll
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import {type ReactNode, useMemo, useState} from 'react'
import {ConnectorContext} from 'sanity/_singletons'

import {ScrollContainer} from '../../components/scroll'
import {ENABLED} from '../constants'
import {Tracker} from '../tracker'
import {ChangeIndicatorsTracker} from '../tracker'
import {ConnectorsOverlay} from './ConnectorsOverlay'

/** @internal */
export interface EnabledChangeConnectorRootProps {
export interface ChangeConnectorRootProps {
children: ReactNode
className?: string
isReviewChangesOpen: boolean
Expand All @@ -17,14 +16,14 @@ export interface EnabledChangeConnectorRootProps {
}

/** @internal */
export function EnabledChangeConnectorRoot({
export function ChangeConnectorRoot({
children,
className,
isReviewChangesOpen,
onOpenReviewChanges,
onSetFocus,
...restProps
}: EnabledChangeConnectorRootProps) {
}: ChangeConnectorRootProps) {
const [rootElement, setRootElement] = useState<HTMLDivElement | null>()

const contextValue = useMemo(
Expand All @@ -38,31 +37,12 @@ export function EnabledChangeConnectorRoot({

return (
<ConnectorContext.Provider value={contextValue}>
<Tracker>
<ChangeIndicatorsTracker>
<ScrollContainer {...restProps} ref={setRootElement} className={className}>
{children}
{rootElement && <ConnectorsOverlay rootElement={rootElement} onSetFocus={onSetFocus} />}
</ScrollContainer>
</Tracker>
</ChangeIndicatorsTracker>
</ConnectorContext.Provider>
)
}

/** @internal */
export interface DisabledChangeConnectorRootProps {
className?: string
children: ReactNode
}

/** @internal */
export function DisabledChangeConnectorRoot({
children,
className,
}: DisabledChangeConnectorRootProps) {
return <ScrollContainer className={className}>{children}</ScrollContainer>
}

/** @internal */
export const ChangeConnectorRoot = ENABLED
? EnabledChangeConnectorRoot
: DisabledChangeConnectorRoot
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import {useCallback, useMemo, useState} from 'react'

import {type Reported} from '../../components/react-track-elements'
import {ScrollMonitor} from '../../components/scroll'
import {isNonNullable} from '../../util'
import {DEBUG_LAYER_BOUNDS} from '../constants'
import {findMostSpecificTarget} from '../helpers/findMostSpecificTarget'
import {getOffsetsTo} from '../helpers/getOffsetsTo'
import {isChangeBar} from '../helpers/isChangeBar'
import {scrollIntoView} from '../helpers/scrollIntoView'
import {type TrackedArea, type TrackedChange, useReportedValues} from '../tracker'
import {type TrackedArea, type TrackedChange, useChangeIndicatorsReportedValues} from '../tracker'
import {Connector} from './Connector'
import {SvgWrapper} from './ConnectorsOverlay.styled'
import {DebugLayers} from './DebugLayers'
Expand Down Expand Up @@ -84,14 +83,16 @@ function getState(

return {field: {id, ...field}, change: {id, ...change}}
})
.filter(isNonNullable)
// .filter(({field, change}) => field && change && field.element && change.element)
.filter(
(value): value is NonNullable<typeof value> =>
Boolean(value?.field.element) && Boolean(value?.change.element),
)
.map(({field, change}) => ({
hasHover: field.hasHover || change.hasHover,
hasFocus: field.hasFocus,
hasRevertHover: change.hasRevertHover,
field: {...field, ...getOffsetsTo(field.element, rootElement)},
change: {...change, ...getOffsetsTo(change.element, rootElement)},
field: {...field, ...getOffsetsTo(field.element!, rootElement)},
change: {...change, ...getOffsetsTo(change.element!, rootElement)},
}))

return {connectors, isHoverConnector}
Expand All @@ -100,7 +101,7 @@ function getState(
export function ConnectorsOverlay(props: ConnectorsOverlayProps) {
const {rootElement, onSetFocus} = props
const [hovered, setHovered] = useState<string | null>(null)
const allReportedValues = useReportedValues()
const allReportedValues = useChangeIndicatorsReportedValues()
const byId: Map<string, TrackedChange | TrackedArea> = useMemo(
() => new Map(allReportedValues),
[allReportedValues],
Expand Down
Loading

0 comments on commit c4f9a1f

Please sign in to comment.