theatre/theatre/studio/src/propEditors/NextPrevKeyframeCursors.tsx
2023-08-04 14:23:11 +02:00

249 lines
6 KiB
TypeScript

import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
import type {StudioSheetItemKey} from '@theatre/shared/utils/ids'
import type {VoidFn} from '@theatre/shared/utils/types'
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
import React from 'react'
import styled, {css} from 'styled-components'
import {PresenceFlag} from '@theatre/studio/uiComponents/usePresence'
import usePresence from '@theatre/studio/uiComponents/usePresence'
export type NearbyKeyframesControls = {
prev?: Pick<Keyframe, 'position'> & {
jump: VoidFn
itemKey: StudioSheetItemKey
}
cur:
| {type: 'on'; toggle: VoidFn; itemKey: StudioSheetItemKey}
| {type: 'off'; toggle: VoidFn}
next?: Pick<Keyframe, 'position'> & {
jump: VoidFn
itemKey: StudioSheetItemKey
}
}
const Container = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 16px;
margin: 0 0px 0 2px;
position: relative;
z-index: 0;
opacity: 1;
&:after {
position: absolute;
left: -14px;
right: -14px;
top: -2px;
bottom: -2px;
content: ' ';
display: none;
z-index: -1;
background: transparent;
}
&:hover {
opacity: 1;
&:after {
display: block;
}
}
`
const Button = styled.div`
background: none;
position: relative;
border: 0;
transition: transform 0.1s ease-out;
z-index: 0;
outline: none;
cursor: pointer;
&:after {
display: none;
${Container}:hover & {
display: block;
}
position: absolute;
left: -4px;
right: -4px;
top: -4px;
bottom: -4px;
content: ' ';
z-index: -1;
}
`
export const nextPrevCursorsTheme = {
offColor: '#555',
onColor: '#000000',
}
const CurButton = styled(Button)<{
isOn: boolean
presence: PresenceFlag | undefined
}>`
&:hover {
color: #000000;
}
color: ${(props) =>
props.presence === PresenceFlag.Primary
? 'white'
: props.isOn
? nextPrevCursorsTheme.onColor
: nextPrevCursorsTheme.offColor};
`
const pointerEventsNone = css`
pointer-events: none !important;
`
const PrevOrNextButton = styled(Button)<{
available: boolean
flag: PresenceFlag | undefined
}>`
color: ${(props) =>
props.flag === PresenceFlag.Primary
? 'white'
: props.available
? nextPrevCursorsTheme.onColor
: nextPrevCursorsTheme.offColor};
${(props) =>
props.available ? pointerEventsAutoInNormalMode : pointerEventsNone};
`
const Prev = styled(PrevOrNextButton)<{
available: boolean
flag: PresenceFlag | undefined
}>`
transform: translateX(2px);
${Container}:hover & {
transform: translateX(-2px);
}
`
const Next = styled(PrevOrNextButton)<{
available: boolean
flag: PresenceFlag | undefined
}>`
transform: translateX(-2px);
${Container}:hover & {
transform: translateX(2px);
}
`
namespace Icons {
const Chevron_Group = styled.g`
stroke-width: 1;
${PrevOrNextButton}:hover & path {
stroke-width: 3;
}
`
export const Prev = () => (
<svg
width="6.5"
height="9.3"
viewBox="0 0 6.5 9.3"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<Chevron_Group transform={`translate(0 0)`}>
<path
fill="#ea2333"
d="M3.8,0c0.4,0,0.8,0.2,0.8,0.6c0,0.5-2.8,3-2.8,4c0,1,2.8,3.4,2.8,4c0,0.4-0.3,0.6-0.8,0.6C2.4,9.3,0,5.8,0,4.6
C0,3.5,2.4,0,3.8,0z M11,0.4c1.1,0,4.2,3,4.2,4.2c0,1.1-3,4.2-4.2,4.2c-1.1,0-4.2-3-4.2-4.2C6.8,3.4,9.9,0.4,11,0.4z M17.5,0.6
c0-0.4,0.3-0.6,0.8-0.6C19.6,0,22,3.5,22,4.6c0,1.2-2.4,4.6-3.8,4.6c-0.4,0-0.8-0.2-0.8-0.6c0-0.7,2.8-3.1,2.8-4
C20.3,3.6,17.5,1.1,17.5,0.6z"
/>
</Chevron_Group>
</svg>
)
export const Next = () => (
<svg
width="6.5"
height="9.3"
viewBox="0 0 6.5 9.3"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<Chevron_Group transform={`translate(2 0)`}>
<path
fill="#ea2333"
d="M-13.7,0c0.4,0,0.8,0.2,0.8,0.6c0,0.5-2.8,3-2.8,4c0,1,2.8,3.4,2.8,4c0,0.4-0.3,0.6-0.8,0.6
c-1.3,0-3.8-3.4-3.8-4.6C-17.5,3.5-15.1,0-13.7,0z M-6.5,0.4c1.1,0,4.2,3,4.2,4.2c0,1.1-3,4.2-4.2,4.2c-1.1,0-4.2-3-4.2-4.2
C-10.7,3.4-7.6,0.4-6.5,0.4z M0,0.6C0,0.2,0.3,0,0.8,0c1.3,0,3.8,3.5,3.8,4.6c0,1.2-2.4,4.6-3.8,4.6C0.3,9.3,0,9,0,8.7
c0-0.7,2.8-3.1,2.8-4C2.8,3.6,0,1.1,0,0.6z"
/>
</Chevron_Group>
</svg>
)
const Cur_Group = styled.g`
stroke-width: 0;
${CurButton}:hover & path {
}
`
export const Cur = () => (
<svg
width="8.3"
height="9.3"
viewBox="0 0 8.3 9.3"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<Cur_Group transform="translate(0 0)">
<path
fill="#ea2333"
d="M-3.1,0c0.4,0,0.8,0.2,0.8,0.6c0,0.5-2.8,3-2.8,4c0,1,2.8,3.4,2.8,4c0,0.4-0.3,0.6-0.8,0.6
c-1.3,0-3.8-3.4-3.8-4.6C-6.8,3.5-4.4,0-3.1,0z M4.2,0.4c1.1,0,4.2,3,4.2,4.2c0,1.1-3,4.2-4.2,4.2C3,8.7,0,5.7,0,4.6
C0,3.4,3,0.4,4.2,0.4z M10.7,0.6C10.7,0.2,11,0,11.4,0c1.3,0,3.8,3.5,3.8,4.6c0,1.2-2.4,4.6-3.8,4.6c-0.4,0-0.8-0.2-0.8-0.6
c0-0.7,2.8-3.1,2.8-4C13.5,3.6,10.7,1.1,10.7,0.6z"
/>
</Cur_Group>
</svg>
)
}
const NextPrevKeyframeCursors: React.VFC<NearbyKeyframesControls> = (props) => {
const prevPresence = usePresence(props.prev?.itemKey)
const curPresence = usePresence(
props.cur?.type === 'on' ? props.cur.itemKey : undefined,
)
const nextPresence = usePresence(props.next?.itemKey)
return (
<Container>
<Prev
available={!!props.prev}
onClick={props.prev?.jump}
flag={prevPresence.flag}
{...prevPresence.attrs}
>
<Icons.Prev />
</Prev>
<CurButton
isOn={props.cur.type === 'on'}
onClick={props.cur.toggle}
presence={curPresence.flag}
{...curPresence.attrs}
>
<Icons.Cur />
</CurButton>
<Next
available={!!props.next}
onClick={props.next?.jump}
flag={nextPresence.flag}
{...nextPresence.attrs}
>
<Icons.Next />
</Next>
</Container>
)
}
export default NextPrevKeyframeCursors