Change DeterminePropEditor... to take an array

- Add autoFocus to prop inputs
This commit is contained in:
vezwork 2022-08-09 16:18:10 -04:00 committed by Elliot
parent 38e6a4ba36
commit 047f01bb45
17 changed files with 148 additions and 122 deletions

View file

@ -17,7 +17,11 @@ import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/studio/store/t
import {commonRootOfPathsToProps} from '@theatre/shared/utils/addresses'
import type {ILogger} from '@theatre/shared/logger'
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
import type {EditingOptionsTree} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover'
import type {
PrimitivePropEditingOptions,
PropWithChildrenEditingOptionsTree,
SheetObjectEditingOptionsTree,
} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover'
import {useKeyframeInlineEditorPopover} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover'
import type {
SequenceEditorTree_PrimitiveProp,
@ -31,17 +35,22 @@ type IAggregateKeyframeDotProps = {
utils: IAggregateKeyframeEditorUtils
}
const isOptionsTreeNodeNotNull = (
a: PropWithChildrenEditingOptionsTree | PrimitivePropEditingOptions | null,
): a is PropWithChildrenEditingOptionsTree | PrimitivePropEditingOptions =>
a !== null
function sheetObjectBuild(
viewModel: SequenceEditorTree_SheetObject,
keyframes: KeyframeWithTrack[],
): EditingOptionsTree | null {
): SheetObjectEditingOptionsTree | null {
const children = viewModel.children
.map((a) =>
a.type === 'propWithChildren'
? propWithChildrenBuild(a, keyframes)
: primitivePropBuild(a, keyframes),
)
.filter((a): a is EditingOptionsTree => a !== null)
.filter(isOptionsTreeNodeNotNull)
if (children.length === 0) return null
return {
type: 'sheetObject',
@ -52,14 +61,14 @@ function sheetObjectBuild(
function propWithChildrenBuild(
viewModel: SequenceEditorTree_PropWithChildren,
keyframes: KeyframeWithTrack[],
): EditingOptionsTree | null {
): PropWithChildrenEditingOptionsTree | null {
const children = viewModel.children
.map((a) =>
a.type === 'propWithChildren'
? propWithChildrenBuild(a, keyframes)
: primitivePropBuild(a, keyframes),
)
.filter((a): a is EditingOptionsTree => a !== null)
.filter(isOptionsTreeNodeNotNull)
if (children.length === 0) return null
return {
type: 'propWithChildren',
@ -71,7 +80,7 @@ function propWithChildrenBuild(
function primitivePropBuild(
viewModelLeaf: SequenceEditorTree_PrimitiveProp,
keyframes: KeyframeWithTrack[],
): EditingOptionsTree | null {
): PrimitivePropEditingOptions | null {
const keyframe = keyframes.find((kf) => kf.track.id === viewModelLeaf.trackId)
if (!keyframe) return null
return {
@ -94,7 +103,9 @@ export function AggregateKeyframeDot(
useKeyframeInlineEditorPopover(
props.editorProps.viewModel.type === 'sheetObject'
? sheetObjectBuild(props.editorProps.viewModel, cur.keyframes)
: propWithChildrenBuild(props.editorProps.viewModel, cur.keyframes),
?.children ?? null
: propWithChildrenBuild(props.editorProps.viewModel, cur.keyframes)
?.children ?? null,
)
const presence = usePresence(props.utils.itemKey)

View file

@ -4,8 +4,6 @@ import styled from 'styled-components'
import type {PropTypeConfig_AllSimples} from '@theatre/core/propTypes'
import type {ISimplePropEditorReactProps} from '@theatre/studio/propEditors/simpleEditors/ISimplePropEditorReactProps'
import {simplePropEditorByPropType} from '@theatre/studio/propEditors/simpleEditors/simplePropEditorByPropType'
import SingleKeyframeSimplePropEditor from './DeterminePropEditorForSingleKeyframe/SingleKeyframeSimplePropEditor'
import type {
EditingOptionsTree,
PrimitivePropEditingOptions,
@ -33,7 +31,7 @@ const SingleKeyframePropLabel = styled.div`
color: #919191;
`
const IndentedThing = styled.div`
const Indent = styled.div`
margin-left: 24px;
`
@ -47,18 +45,24 @@ const IndentedThing = styled.div`
*
* @param p - propConfig object for any type of prop.
*/
export function DeterminePropEditorForKeyframeTree(p: EditingOptionsTree) {
export function DeterminePropEditorForKeyframeTree(
p: EditingOptionsTree & {autoFocusInput?: boolean},
) {
if (p.type === 'sheetObject') {
return (
<>
<SingleKeyframePropLabel>
{p.sheetObject.address.objectKey}
</SingleKeyframePropLabel>
<IndentedThing>
<Indent>
{p.children.map((c, i) => (
<DeterminePropEditorForKeyframeTree key={i} {...c} />
<DeterminePropEditorForKeyframeTree
key={i}
{...c}
autoFocusInput={p.autoFocusInput && i === 0}
/>
))}
</IndentedThing>
</Indent>
</>
)
} else if (p.type === 'propWithChildren') {
@ -66,19 +70,31 @@ export function DeterminePropEditorForKeyframeTree(p: EditingOptionsTree) {
return (
<>
<SingleKeyframePropLabel>{label}</SingleKeyframePropLabel>
<IndentedThing>
<Indent>
{p.children.map((c, i) => (
<DeterminePropEditorForKeyframeTree key={i} {...c} />
<DeterminePropEditorForKeyframeTree
key={i}
{...c}
autoFocusInput={p.autoFocusInput && i === 0}
/>
))}
</IndentedThing>
</Indent>
</>
)
} else {
return <BeepBoop {...p} />
return <PrimitivePropEditor {...p} autoFocusInput={p.autoFocusInput} />
}
}
function BeepBoop(p: PrimitivePropEditingOptions) {
const SingleKeyframeSimplePropEditorContainer = styled.div`
padding: 0 6px;
display: flex;
align-items: center;
`
function PrimitivePropEditor(
p: PrimitivePropEditingOptions & {autoFocusInput?: boolean},
) {
const label = p.propConfig.label ?? last(p.pathToProp)
const editingTools = useEditingToolsForKeyframeEditorPopover(p)
@ -92,12 +108,14 @@ function BeepBoop(p: PrimitivePropEditingOptions) {
return (
<SingleKeyframePropEditorContainer>
<SingleKeyframePropLabel>{label}</SingleKeyframePropLabel>
<SingleKeyframeSimplePropEditor
SimpleEditorComponent={PropEditor}
propConfig={p.propConfig}
<SingleKeyframeSimplePropEditorContainer>
<PropEditor
editingTools={editingTools}
keyframeValue={p.keyframe.value}
propConfig={p.propConfig}
value={p.keyframe.value}
autoFocus={p.autoFocusInput}
/>
</SingleKeyframeSimplePropEditorContainer>
</SingleKeyframePropEditorContainer>
)
}

View file

@ -1,45 +0,0 @@
import React from 'react'
import type {ISimplePropEditorReactProps} from '@theatre/studio/propEditors/simpleEditors/ISimplePropEditorReactProps'
import styled from 'styled-components'
import type {PropTypeConfig_AllSimples} from '@theatre/core/propTypes'
import type {IEditingTools} from '@theatre/studio/propEditors/utils/IEditingTools'
export type ISingleKeyframeSimplePropEditorProps<
TPropTypeConfig extends PropTypeConfig_AllSimples,
> = {
propConfig: TPropTypeConfig
editingTools: IEditingTools<TPropTypeConfig['valueType']>
keyframeValue: TPropTypeConfig['valueType']
SimpleEditorComponent: React.VFC<ISimplePropEditorReactProps<TPropTypeConfig>>
}
const SingleKeyframeSimplePropEditorContainer = styled.div`
padding: 0 6px;
display: flex;
align-items: center;
`
/**
* Initially used for inline keyframe property editor, this editor is attached to the
* functionality of editing a property for a sequence keyframe.
*/
function SingleKeyframeSimplePropEditor<
TPropTypeConfig extends PropTypeConfig_AllSimples,
>({
propConfig,
editingTools,
keyframeValue: value,
SimpleEditorComponent: EditorComponent,
}: ISingleKeyframeSimplePropEditorProps<TPropTypeConfig>) {
return (
<SingleKeyframeSimplePropEditorContainer>
<EditorComponent
editingTools={editingTools}
propConfig={propConfig}
value={value}
/>
</SingleKeyframeSimplePropEditorContainer>
)
}
export default SingleKeyframeSimplePropEditor

View file

@ -102,14 +102,16 @@ const SingleKeyframeDot: React.VFC<ISingleKeyframeDotProps> = (props) => {
const [contextMenu] = useSingleKeyframeContextMenu(node, logger, props)
const [inlineEditorPopover, openEditor, _, isInlineEditorPopoverOpen] =
useKeyframeInlineEditorPopover({
useKeyframeInlineEditorPopover([
{
type: 'primitiveProp',
keyframe: props.keyframe,
pathToProp: props.leaf.pathToProp,
propConfig: props.leaf.propConf,
sheetObject: props.leaf.sheetObject,
trackId: props.leaf.trackId,
})
},
])
const [isDragging] = useDragForSingleKeyframeDot(node, props, {
onClickFromDrag(dragStartEvent) {
openEditor(dragStartEvent, ref.current!)
@ -132,8 +134,7 @@ const SingleKeyframeDot: React.VFC<ISingleKeyframeDotProps> = (props) => {
{contextMenu}
</>
)
},
)
}
export default SingleKeyframeDot

View file

@ -15,13 +15,19 @@ import type {UnknownValidCompoundProps} from '@theatre/core/propTypes/internals'
/** The editor that pops up when directly clicking a Keyframe. */
export function useKeyframeInlineEditorPopover(
props: EditingOptionsTree | null,
props: EditingOptionsTree[] | null,
) {
return usePopover({debugName: 'useKeyframeInlineEditorPopover'}, () => (
<BasicPopover showPopoverEdgeTriangle>
{props === null ? undefined : (
<DeterminePropEditorForKeyframeTree {...props} />
)}
{!Array.isArray(props)
? undefined
: props.map((prop, i) => (
<DeterminePropEditorForKeyframeTree
key={i}
{...prop}
autoFocusInput={i === 0}
/>
))}
</BasicPopover>
))
}
@ -30,12 +36,12 @@ export type EditingOptionsTree =
| SheetObjectEditingOptionsTree
| PropWithChildrenEditingOptionsTree
| PrimitivePropEditingOptions
type SheetObjectEditingOptionsTree = {
export type SheetObjectEditingOptionsTree = {
type: 'sheetObject'
sheetObject: SheetObject
children: EditingOptionsTree[]
}
type PropWithChildrenEditingOptionsTree = {
export type PropWithChildrenEditingOptionsTree = {
type: 'propWithChildren'
propConfig: PropTypeConfig_Compound<UnknownValidCompoundProps>
pathToProp: PathToProp

View file

@ -71,13 +71,16 @@ const GraphEditorDotNonScalar: React.VFC<IProps> = (props) => {
const curValue = props.which === 'left' ? 0 : 1
const [inlineEditorPopover, openEditor, _, _isInlineEditorPopoverOpen] =
useKeyframeInlineEditorPopover({
useKeyframeInlineEditorPopover([
{
type: 'primitiveProp',
keyframe: props.keyframe,
pathToProp: props.pathToProp,
propConf: props.propConfig,
propConfig: props.propConfig,
sheetObject: props.sheetObject,
trackId: props.trackId,
})
},
])
const isDragging = useDragKeyframe({
node,

View file

@ -71,13 +71,16 @@ const GraphEditorDotScalar: React.VFC<IProps> = (props) => {
const cyInExtremumSpace = props.extremumSpace.fromValueSpace(curValue)
const [inlineEditorPopover, openEditor, _, _isInlineEditorPopoverOpen] =
useKeyframeInlineEditorPopover({
useKeyframeInlineEditorPopover([
{
type: 'primitiveProp',
keyframe: props.keyframe,
pathToProp: props.pathToProp,
propConf: props.propConfig,
propConfig: props.propConfig,
sheetObject: props.sheetObject,
trackId: props.trackId,
})
},
])
const isDragging = useDragKeyframe({
node,

View file

@ -6,12 +6,17 @@ import type {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps'
const Input = styled(BasicCheckbox)`
margin-left: 6px;
:focus {
outline: 1px solid #555;
}
`
function BooleanPropEditor({
propConfig,
editingTools,
value,
autoFocus,
}: ISimplePropEditorReactProps<PropTypeConfig_Boolean>) {
const onChange = useCallback(
(el: React.ChangeEvent<HTMLInputElement>) => {
@ -20,7 +25,7 @@ function BooleanPropEditor({
[propConfig, editingTools],
)
return <Input checked={value} onChange={onChange} />
return <Input checked={value} onChange={onChange} autoFocus={autoFocus} />
}
export default BooleanPropEditor

View file

@ -8,4 +8,5 @@ export type ISimplePropEditorReactProps<
propConfig: TPropTypeConfig
editingTools: IEditingTools<TPropTypeConfig['valueType']>
value: TPropTypeConfig['valueType']
autoFocus?: boolean
}

View file

@ -7,6 +7,7 @@ function NumberPropEditor({
propConfig,
editingTools,
value,
autoFocus,
}: ISimplePropEditorReactProps<PropTypeConfig_Number>) {
const nudge = useCallback(
(params: {deltaX: number; deltaFraction: number; magnitude: number}) => {
@ -23,6 +24,7 @@ function NumberPropEditor({
permanentlySetValue={editingTools.permanentlySetValue}
range={propConfig.range}
nudge={nudge}
autoFocus={autoFocus}
/>
)
}

View file

@ -63,6 +63,7 @@ const RgbaPopover = styled.div`
function RgbaPropEditor({
editingTools,
value,
autoFocus,
}: ISimplePropEditorReactProps<PropTypeConfig_Rgba>) {
const containerRef = useRef<HTMLDivElement>(null!)
@ -116,6 +117,7 @@ function RgbaPropEditor({
discardTemporaryValue={noop}
permanentlySetValue={onChange}
isValid={(v) => !!v.match(validHexRegExp)}
autoFocus={autoFocus}
/>
</RowContainer>
{popoverNode}

View file

@ -8,6 +8,7 @@ function StringLiteralPropEditor<TLiteralOptions extends string>({
propConfig,
editingTools,
value,
autoFocus,
}: ISimplePropEditorReactProps<PropTypeConfig_StringLiteral<TLiteralOptions>>) {
const onChange = useCallback(
(val: TLiteralOptions) => {
@ -21,12 +22,14 @@ function StringLiteralPropEditor<TLiteralOptions extends string>({
value={value}
onChange={onChange}
options={propConfig.valuesAndLabels}
autoFocus={autoFocus}
/>
) : (
<BasicSwitch
value={value}
onChange={onChange}
options={propConfig.valuesAndLabels}
autoFocus={autoFocus}
/>
)
}

View file

@ -6,6 +6,7 @@ import type {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps'
function StringPropEditor({
editingTools,
value,
autoFocus,
}: ISimplePropEditorReactProps<PropTypeConfig_String>) {
return (
<BasicStringInput
@ -13,6 +14,7 @@ function StringPropEditor({
temporarilySetValue={editingTools.temporarilySetValue}
discardTemporaryValue={editingTools.discardTemporaryValue}
permanentlySetValue={editingTools.permanentlySetValue}
autoFocus={autoFocus}
/>
)
}

View file

@ -115,6 +115,7 @@ const BasicNumberInput: React.FC<{
*/
onBlur?: () => void
nudge: BasicNumberInputNudgeFn
autoFocus?: boolean
}> = (propsA) => {
const [stateRef] = useRefAndState<IState>({mode: 'noFocus'})
const isValid = propsA.isValid ?? alwaysValid
@ -307,6 +308,7 @@ const BasicNumberInput: React.FC<{
e.preventDefault()
e.stopPropagation()
}}
autoFocus={propsA.autoFocus}
/>
)

View file

@ -55,11 +55,13 @@ function BasicSelect<TLiteralOptions extends string>({
onChange,
options,
className,
autoFocus,
}: {
value: TLiteralOptions
onChange: (val: TLiteralOptions) => void
options: Record<TLiteralOptions, string>
className?: string
autoFocus?: boolean
}) {
const _onChange = useCallback(
(el: React.ChangeEvent<HTMLSelectElement>) => {
@ -70,7 +72,12 @@ function BasicSelect<TLiteralOptions extends string>({
return (
<Container>
<Select className={className} value={value} onChange={_onChange}>
<Select
className={className}
value={value}
onChange={_onChange}
autoFocus={autoFocus}
>
{Object.keys(options).map((key, i) => (
<option key={'option-' + i} value={key}>
{options[key]}

View file

@ -64,6 +64,7 @@ const BasicStringInput: React.FC<{
* before this, so use this for UI purposes such as closing a popover.
*/
onBlur?: () => void
autoFocus?: boolean
}> = (props) => {
const [stateRef] = useRefAndState<IState>({mode: 'noFocus'})
const isValid = props.isValid ?? alwaysValid
@ -195,6 +196,7 @@ const BasicStringInput: React.FC<{
e.preventDefault()
e.stopPropagation()
}}
autoFocus={props.autoFocus}
/>
)

View file

@ -56,10 +56,12 @@ function BasicSwitch<TLiteralOptions extends string>({
value,
onChange,
options,
autoFocus,
}: {
value: TLiteralOptions
onChange: (val: TLiteralOptions) => void
options: Record<TLiteralOptions, string>
autoFocus?: boolean
}) {
const _onChange = useCallback(
(el: React.ChangeEvent<HTMLInputElement>) => {
@ -78,6 +80,7 @@ function BasicSwitch<TLiteralOptions extends string>({
value={key}
onChange={_onChange}
name="switchbox"
autoFocus={autoFocus}
/>
</Label>
))}