Editor popover for SequenceLengthIndicator

This commit is contained in:
Aria Minaei 2021-08-02 21:06:58 +02:00
parent 3745ce02ff
commit 58e9d9ff8b
8 changed files with 134 additions and 20 deletions

View file

@ -74,6 +74,7 @@
"react-error-boundary": "^3.1.3",
"react-icons": "^4.2.0",
"react-is": "^17.0.2",
"react-merge-refs": "^1.1.0",
"react-shadow": "^19.0.2",
"react-use": "^17.2.4",
"react-use-gesture": "^9.1.3",

View file

@ -0,0 +1,96 @@
import type {Pointer} from '@theatre/dataverse'
import React, {useLayoutEffect, useMemo, useRef} from 'react'
import styled from 'styled-components'
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
import {usePrism, useVal} from '@theatre/dataverse-react'
import getStudio from '@theatre/studio/getStudio'
import BasicNumberInput from '@theatre/studio/uiComponents/form/BasicNumberInput'
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
import {propNameText} from '@theatre/studio/panels/ObjectEditorPanel/propEditors/utils/SingleRowPropEditor'
const greaterThanZero = (v: number) => isFinite(v) && v > 0
const Container = styled.div`
display: flex;
gap: 8px;
padding: 4px 8px;
height: 28px;
align-items: center;
`
const Label = styled.div`
${propNameText};
white-space: nowrap;
`
const LengthEditorPopover: React.FC<{
layoutP: Pointer<SequenceEditorPanelLayout>
/**
* Called when user hits enter/escape
*/
onRequestClose: () => void
}> = ({layoutP, onRequestClose}) => {
const sheet = useVal(layoutP.sheet)
const fns = useMemo(() => {
let tempTransaction: CommitOrDiscard | undefined
return {
temporarilySetValue(newLength: number): void {
if (tempTransaction) {
tempTransaction.discard()
tempTransaction = undefined
}
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
stateEditors.coreByProject.historic.sheetsById.sequence.setLength({
...sheet.address,
length: newLength,
})
})
},
discardTemporaryValue(): void {
if (tempTransaction) {
tempTransaction.discard()
tempTransaction = undefined
}
},
permenantlySetValue(newLength: number): void {
if (tempTransaction) {
tempTransaction.discard()
tempTransaction = undefined
}
getStudio()!.transaction(({stateEditors}) => {
stateEditors.coreByProject.historic.sheetsById.sequence.setLength({
...sheet.address,
length: newLength,
})
})
},
}
}, [layoutP, sheet])
const inputRef = useRef<HTMLInputElement>(null)
useLayoutEffect(() => {
inputRef.current!.focus()
}, [])
return usePrism(() => {
const sequence = sheet.getSequence()
const sequenceLength = sequence.length
return (
<Container>
<Label>Sequence length</Label>
<BasicNumberInput
value={sequenceLength}
{...fns}
isValid={greaterThanZero}
inputRef={inputRef}
onBlur={onRequestClose}
/>
</Container>
)
}, [sheet, fns, inputRef])
}
export default LengthEditorPopover

View file

@ -17,6 +17,7 @@ import {
useLockFrameStampPosition,
} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
import {GoChevronLeft, GoChevronRight} from 'react-icons/all'
import LengthEditorPopover from './LengthEditorPopover'
const coverWidth = 1000
@ -130,9 +131,13 @@ type IProps = {
const LengthIndicator: React.FC<IProps> = ({layoutP}) => {
const [nodeRef, node] = useRefAndState<HTMLDivElement | null>(null)
const [isDraggingD] = useDragBulge(node, {layoutP})
const [popoverNode, openPopover, _, isPopoverOpen] = usePopover(() => {
return <div>poppio</div>
})
const [popoverNode, openPopover, closePopover, isPopoverOpen] = usePopover(
() => {
return (
<LengthEditorPopover layoutP={layoutP} onRequestClose={closePopover} />
)
},
)
return usePrism(() => {
const sheet = val(layoutP.sheet)

View file

@ -5,7 +5,7 @@ import type {Pointer} from '@theatre/dataverse'
import {val} from '@theatre/dataverse'
import React from 'react'
import styled from 'styled-components'
import LengthIndicator from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator'
import LengthIndicator from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator/LengthIndicator'
import FrameStamp from './FrameStamp'
import HorizontalScrollbar from './HorizontalScrollbar'
import Playhead from './Playhead'

View file

@ -392,7 +392,7 @@ namespace stateEditors {
export function setLength(
p: WithoutSheetInstance<SheetAddress> & {length: number},
) {
_ensure(p).length = p.length
_ensure(p).length = parseFloat(p.length.toFixed(2))
}
function _ensureTracksOfObject(

View file

@ -6,8 +6,6 @@ import {createPortal} from 'react-dom'
import useWindowSize from 'react-use/esm/useWindowSize'
import styled from 'styled-components'
const minWidth = 190
/**
* How far from the menu should the pointer travel to auto close the menu
*/
@ -15,14 +13,11 @@ const defaultPointerDistanceThreshold = 200
const Container = styled.ul`
position: absolute;
min-width: ${minWidth}px;
z-index: 10000;
background: ${transparentize(0.2, '#111')};
color: white;
list-style-type: none;
padding: 2px 0;
padding: 0;
margin: 0;
border-radius: 1px;
cursor: default;
pointer-events: all;
border-radius: 3px;

View file

@ -1,9 +1,11 @@
import {theme} from '@theatre/studio/css'
import {clamp, isInteger, round} from 'lodash-es'
import {darken, lighten} from 'polished'
import type {MutableRefObject} from 'react'
import React, {useMemo, useRef, useState} from 'react'
import styled from 'styled-components'
import DraggableArea from '@theatre/studio/uiComponents/DraggableArea'
import mergeRefs from 'react-merge-refs'
type IMode = IState['mode']
@ -75,9 +77,8 @@ const FillIndicator = styled.div`
}
`
function isValueAcceptable(s: string) {
const v = parseFloat(s)
return !isNaN(v)
function isFiniteFloat(s: string) {
return isFinite(parseFloat(s))
}
type IState_NoFocus = {
@ -98,6 +99,8 @@ type IState_Dragging = {
type IState = IState_NoFocus | IState_EditingViaKeyboard | IState_Dragging
const alwaysValid = (v: number) => true
const BasicNumberInput: React.FC<{
value: number
temporarilySetValue: (v: number) => void
@ -105,13 +108,22 @@ const BasicNumberInput: React.FC<{
permenantlySetValue: (v: number) => void
className?: string
range?: [min: number, max: number]
isValid?: (v: number) => boolean
inputRef?: MutableRefObject<HTMLInputElement | null>
/**
* Called when the user hits Enter. One of the *SetValue() callbacks will be called
* before this, so use this for UI purposes such as closing a popover.
*/
onBlur?: () => void
}> = (propsA) => {
const [stateA, setState] = useState<IState>({mode: 'noFocus'})
const isValid = propsA.isValid ?? alwaysValid
const refs = useRef({state: stateA, props: propsA})
refs.current = {state: stateA, props: propsA}
const inputRef = useRef<HTMLInputElement | null>(null)
const bodyCursorBeforeDrag = useRef<string | null>(null)
const callbacks = useMemo(() => {
@ -122,9 +134,9 @@ const BasicNumberInput: React.FC<{
setState({...curState, currentEditedValueInString: value})
if (!isValueAcceptable(value)) return
const valInFloat = parseFloat(value)
if (!isFinite(valInFloat) || !isValid(valInFloat)) return
refs.current.props.temporarilySetValue(valInFloat)
}
@ -132,16 +144,17 @@ const BasicNumberInput: React.FC<{
if (refs.current.state.mode === 'editingViaKeyboard') {
commitKeyboardInput()
setState({mode: 'noFocus'})
} else {
}
if (propsA.onBlur) propsA.onBlur()
}
const commitKeyboardInput = () => {
const curState = refs.current.state as IState_EditingViaKeyboard
if (!isValueAcceptable(curState.currentEditedValueInString)) {
const value = parseFloat(curState.currentEditedValueInString)
if (!isFinite(value) || !isValid(value)) {
refs.current.props.discardTemporaryValue()
} else {
const value = parseFloat(curState.currentEditedValueInString)
if (curState.valueBeforeEditing === value) {
refs.current.props.discardTemporaryValue()
} else {
@ -256,6 +269,9 @@ const BasicNumberInput: React.FC<{
value = 'NaN'
}
const _refs = [inputRef]
if (propsA.inputRef) _refs.push(propsA.inputRef)
const theInput = (
<Input
key="input"
@ -266,7 +282,7 @@ const BasicNumberInput: React.FC<{
onKeyDown={callbacks.onInputKeyDown}
onClick={callbacks.onClick}
onFocus={callbacks.onFocus}
ref={inputRef}
ref={mergeRefs(_refs)}
onMouseDown={(e: React.MouseEvent) => {
e.stopPropagation()
}}

View file

@ -16436,6 +16436,7 @@ fsevents@^1.2.7:
react-error-boundary: ^3.1.3
react-icons: ^4.2.0
react-is: ^17.0.2
react-merge-refs: ^1.1.0
react-shadow: ^19.0.2
react-use: ^17.2.4
react-use-gesture: ^9.1.3