parent
a9e86113ba
commit
763d37aee3
32 changed files with 326 additions and 139 deletions
|
@ -1,7 +1,7 @@
|
|||
import studio from '@theatre/studio'
|
||||
import type {UseDragOpts} from './useDrag'
|
||||
import useDrag from './useDrag'
|
||||
import React, {useLayoutEffect, useMemo, useState} from 'react'
|
||||
import React, {useLayoutEffect, useMemo, useRef, useState} from 'react'
|
||||
import type {IProject, ISheet} from '@theatre/core'
|
||||
import {onChange, types} from '@theatre/core'
|
||||
import type {IScrub, IStudio} from '@theatre/studio'
|
||||
|
@ -19,6 +19,17 @@ const textInterpolate = (left: string, right: string, progression: number) => {
|
|||
return left
|
||||
}
|
||||
|
||||
const globalConfig = {
|
||||
background: {
|
||||
type: types.stringLiteral('black', {
|
||||
black: 'black',
|
||||
white: 'white',
|
||||
dynamic: 'dynamic',
|
||||
}),
|
||||
dynamic: types.rgba(),
|
||||
},
|
||||
}
|
||||
|
||||
const boxObjectConfig = {
|
||||
test: types.string('Hello?', {interpolate: textInterpolate}),
|
||||
testLiteral: types.stringLiteral('a', {a: 'Option A', b: 'Option B'}),
|
||||
|
@ -162,8 +173,24 @@ export const Scene: React.FC<{project: IProject}> = ({project}) => {
|
|||
})
|
||||
})
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null!)
|
||||
|
||||
const globalObj = sheet.object('global', globalConfig)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const unsubscribeFromChanges = onChange(globalObj.props, (newValues) => {
|
||||
console.log(newValues)
|
||||
containerRef.current.style.background =
|
||||
newValues.background.type !== 'dynamic'
|
||||
? newValues.background.type
|
||||
: newValues.background.dynamic.toString()
|
||||
})
|
||||
return unsubscribeFromChanges
|
||||
}, [globalObj])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
|
|
|
@ -10,8 +10,6 @@ test.describe('setting-static-props', () => {
|
|||
})
|
||||
|
||||
test('Undo/redo', async ({page}) => {
|
||||
await page.locator('[data-testid="OutlinePanel-TriggerButton"]').click()
|
||||
|
||||
// https://github.com/microsoft/playwright/issues/12298
|
||||
// The div does in fact intercept pointer events, but it is meant to 🤦
|
||||
await page
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import {getOutlineSelection} from '@theatre/studio/selectors'
|
||||
import {usePrism, useVal} from '@theatre/react'
|
||||
import React, {useEffect, useLayoutEffect} from 'react'
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
} from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {isProject, isSheetObject} from '@theatre/shared/instanceTypes'
|
||||
import {
|
||||
|
@ -14,15 +19,19 @@ import ProjectDetails from './ProjectDetails'
|
|||
import getStudio from '@theatre/studio/getStudio'
|
||||
import useHotspot from '@theatre/studio/uiComponents/useHotspot'
|
||||
import {Box, prism, val} from '@theatre/dataverse'
|
||||
import EmptyState from './EmptyState'
|
||||
import useLockSet from '@theatre/studio/uiComponents/useLockSet'
|
||||
|
||||
const headerHeight = `32px`
|
||||
|
||||
const Container = styled.div<{pin: boolean}>`
|
||||
${pointerEventsAutoInNormalMode};
|
||||
background-color: rgba(40, 43, 47, 0.8);
|
||||
position: fixed;
|
||||
right: 8px;
|
||||
top: 50px;
|
||||
width: 236px;
|
||||
// Temporary, see comment about CSS grid in SingleRowPropEditor.
|
||||
width: 280px;
|
||||
height: fit-content;
|
||||
z-index: ${panelZIndexes.propsPanel};
|
||||
|
||||
|
@ -68,14 +77,18 @@ const Body = styled.div`
|
|||
user-select: none;
|
||||
`
|
||||
|
||||
export const contextMenuShownContext = createContext<
|
||||
ReturnType<typeof useLockSet>
|
||||
>([false, () => () => {}])
|
||||
|
||||
const DetailPanel: React.FC<{}> = (props) => {
|
||||
const pin = useVal(getStudio().atomP.ahistoric.pinDetails) !== false
|
||||
|
||||
const hostspotActive = useHotspot('right')
|
||||
const hotspotActive = useHotspot('right')
|
||||
|
||||
useLayoutEffect(() => {
|
||||
isDetailPanelHotspotActiveB.set(hostspotActive)
|
||||
}, [hostspotActive])
|
||||
isDetailPanelHotspotActiveB.set(hotspotActive)
|
||||
}, [hotspotActive])
|
||||
|
||||
// cleanup
|
||||
useEffect(() => {
|
||||
|
@ -85,6 +98,10 @@ const DetailPanel: React.FC<{}> = (props) => {
|
|||
}
|
||||
}, [])
|
||||
|
||||
const [isContextMenuShown] = useContext(contextMenuShownContext)
|
||||
|
||||
const showDetailsPanel = pin || hotspotActive || isContextMenuShown
|
||||
|
||||
return usePrism(() => {
|
||||
const selection = getOutlineSelection()
|
||||
|
||||
|
@ -93,7 +110,7 @@ const DetailPanel: React.FC<{}> = (props) => {
|
|||
return (
|
||||
<Container
|
||||
data-testid="DetailPanel-Object"
|
||||
pin={pin || hostspotActive}
|
||||
pin={showDetailsPanel}
|
||||
onMouseEnter={() => {
|
||||
isDetailPanelHoveredB.set(true)
|
||||
}}
|
||||
|
@ -125,7 +142,7 @@ const DetailPanel: React.FC<{}> = (props) => {
|
|||
const project = selection.find(isProject)
|
||||
if (project) {
|
||||
return (
|
||||
<Container pin={pin || hostspotActive}>
|
||||
<Container pin={showDetailsPanel}>
|
||||
<Header>
|
||||
<Title title={`${project.address.projectId}`}>
|
||||
<TitleBar_Piece>{project.address.projectId} </TitleBar_Piece>
|
||||
|
@ -138,11 +155,31 @@ const DetailPanel: React.FC<{}> = (props) => {
|
|||
)
|
||||
}
|
||||
|
||||
return <></>
|
||||
}, [pin, hostspotActive])
|
||||
return (
|
||||
<Container
|
||||
pin={showDetailsPanel}
|
||||
onMouseEnter={() => {
|
||||
isDetailPanelHoveredB.set(true)
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
isDetailPanelHoveredB.set(false)
|
||||
}}
|
||||
>
|
||||
<EmptyState />
|
||||
</Container>
|
||||
)
|
||||
}, [showDetailsPanel])
|
||||
}
|
||||
|
||||
export default DetailPanel
|
||||
export default () => {
|
||||
const lockSet = useLockSet()
|
||||
|
||||
return (
|
||||
<contextMenuShownContext.Provider value={lockSet}>
|
||||
<DetailPanel />
|
||||
</contextMenuShownContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const isDetailPanelHotspotActiveB = new Box<boolean>(false)
|
||||
const isDetailPanelHoveredB = new Box<boolean>(false)
|
||||
|
|
|
@ -18,8 +18,8 @@ import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useCo
|
|||
import {useEditingToolsForCompoundProp} from '@theatre/studio/propEditors/useEditingToolsForCompoundProp'
|
||||
|
||||
const Container = styled.div`
|
||||
--step: 8px;
|
||||
--left-pad: 0px;
|
||||
--step: 15px;
|
||||
--left-pad: 15px;
|
||||
${pointerEventsAutoInNormalMode};
|
||||
`
|
||||
|
||||
|
|
|
@ -12,11 +12,19 @@ import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS
|
|||
|
||||
export const indentationFormula = `calc(var(--left-pad) + var(--depth) * var(--step))`
|
||||
|
||||
const LeftRow = styled.div`
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
height: 30px;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
// We cannot calculate both the container (details panel) width and the descendant
|
||||
// (this) width dynamically. This leads to the container width being calculated
|
||||
// without this percentage being taken into consideration leads to horizontal
|
||||
// clipping/scrolling--the same way as if we explicitly fixed either the container
|
||||
// width, or the descendant width.
|
||||
// The correct solution for tabulated UIs with dynamic container widths is to use
|
||||
// CSS grid. For now I fixed this issue by just giving a great enough width
|
||||
// to the details panel so most things don't break.
|
||||
--right-width: 60%;
|
||||
position: relative;
|
||||
${pointerEventsAutoInNormalMode};
|
||||
|
@ -97,7 +105,7 @@ export function SingleRowPropEditor<T>({
|
|||
})
|
||||
|
||||
return (
|
||||
<LeftRow>
|
||||
<Container>
|
||||
{contextMenu}
|
||||
<Left>
|
||||
<ControlsContainer>{editingTools.controlIndicators}</ControlsContainer>
|
||||
|
@ -113,6 +121,6 @@ export function SingleRowPropEditor<T>({
|
|||
</Left>
|
||||
|
||||
<InputContainer>{children}</InputContainer>
|
||||
</LeftRow>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
|
55
theatre/studio/src/panels/DetailPanel/EmptyState.tsx
Normal file
55
theatre/studio/src/panels/DetailPanel/EmptyState.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import type {FC} from 'react'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {Outline} from '@theatre/studio/uiComponents/icons'
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
`
|
||||
|
||||
const Message = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 11px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
`
|
||||
|
||||
const Icon = styled.div`
|
||||
color: rgba(145, 145, 145, 0.8);
|
||||
`
|
||||
|
||||
const LinkToDoc = styled.a`
|
||||
color: #919191;
|
||||
font-size: 10px;
|
||||
text-decoration-color: #40434a;
|
||||
text-underline-offset: 3px;
|
||||
`
|
||||
|
||||
const EmptyState: FC = () => {
|
||||
return (
|
||||
<Container>
|
||||
<Message>
|
||||
<Icon>
|
||||
<Outline />
|
||||
</Icon>
|
||||
<div>
|
||||
Please select an object from the <u>Outline Menu</u> to see its
|
||||
properties.
|
||||
</div>
|
||||
</Message>
|
||||
{/* Links like this should probably be managed centrally so that we can
|
||||
have a process for updating them when the docs change. */}
|
||||
<LinkToDoc
|
||||
href="https://docs.theatrejs.com/in-depth/#objects"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more about Objects
|
||||
</LinkToDoc>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmptyState
|
|
@ -3,7 +3,7 @@ import React from 'react'
|
|||
import styled, {css} from 'styled-components'
|
||||
import noop from '@theatre/shared/utils/noop'
|
||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||
import {ChevronRight, Package} from '@theatre/studio/uiComponents/icons'
|
||||
import {ChevronDown, Package} from '@theatre/studio/uiComponents/icons'
|
||||
|
||||
export const Container = styled.li`
|
||||
margin: 0;
|
||||
|
@ -22,10 +22,10 @@ const Header = styled(BaseHeader)`
|
|||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
margin-left: calc(4px + var(--depth) * 16px);
|
||||
padding-left: 8px;
|
||||
padding-left: 4px;
|
||||
padding-right: 8px;
|
||||
gap: 8px;
|
||||
height: 23px;
|
||||
gap: 4px;
|
||||
height: 21px;
|
||||
line-height: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
|
@ -86,7 +86,7 @@ const Head_Label = styled.span`
|
|||
${pointerEventsAutoInNormalMode};
|
||||
position: relative;
|
||||
// Compensate for border bottom
|
||||
top: 1px;
|
||||
top: 0.5px;
|
||||
display: flex;
|
||||
height: 20px;
|
||||
align-items: center;
|
||||
|
@ -106,7 +106,6 @@ const Head_Icon_WithDescendants = styled.span<{isOpen: boolean}>`
|
|||
font-size: 9px;
|
||||
position: relative;
|
||||
display: block;
|
||||
transform: rotateZ(${(props) => (props.isOpen ? 90 : 0)}deg);
|
||||
`
|
||||
|
||||
const ChildrenContainer = styled.ul`
|
||||
|
@ -141,7 +140,7 @@ const BaseItem: React.FC<{
|
|||
<Head_IconContainer>
|
||||
{canContainChildren ? (
|
||||
<Head_Icon_WithDescendants isOpen={true}>
|
||||
<ChevronRight />
|
||||
<ChevronDown />
|
||||
</Head_Icon_WithDescendants>
|
||||
) : (
|
||||
<Package />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {useEffect, useState} from 'react'
|
||||
import React, {useEffect, useLayoutEffect} from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {panelZIndexes} from '@theatre/studio/panels/BasePanel/common'
|
||||
import ProjectsList from './ProjectsList/ProjectsList'
|
||||
|
@ -6,6 +6,7 @@ import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
|||
import {useVal} from '@theatre/react'
|
||||
import getStudio from '@theatre/studio/getStudio'
|
||||
import useHotspot from '@theatre/studio/uiComponents/useHotspot'
|
||||
import {Box, prism, val} from '@theatre/dataverse'
|
||||
|
||||
const headerHeight = `44px`
|
||||
|
||||
|
@ -43,26 +44,31 @@ const Container = styled.div<{pin: boolean}>`
|
|||
}
|
||||
`
|
||||
|
||||
const OutlinePanel: React.FC<{}> = (props) => {
|
||||
const pin = useVal(getStudio().atomP.ahistoric.pinOutline) !== false
|
||||
const show = useVal(getStudio().atomP.ephemeral.showOutline)
|
||||
const OutlinePanel: React.FC<{}> = () => {
|
||||
const pin = useVal(getStudio().atomP.ahistoric.pinOutline) ?? true
|
||||
const show = useVal(shouldShowOutlineD)
|
||||
const active = useHotspot('left')
|
||||
const [hovered, setHovered] = useState(false)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
isOutlinePanelHotspotActiveB.set(active)
|
||||
}, [active])
|
||||
|
||||
// cleanup
|
||||
useEffect(() => {
|
||||
getStudio().transaction(({stateEditors, drafts}) => {
|
||||
stateEditors.studio.ephemeral.setShowOutline(active || hovered)
|
||||
})
|
||||
}, [active, hovered])
|
||||
return () => {
|
||||
isOutlinePanelHoveredB.set(false)
|
||||
isOutlinePanelHotspotActiveB.set(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Container
|
||||
pin={pin || show}
|
||||
onMouseEnter={() => {
|
||||
setHovered(true)
|
||||
isOutlinePanelHoveredB.set(true)
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHovered(false)
|
||||
isOutlinePanelHoveredB.set(false)
|
||||
}}
|
||||
>
|
||||
<ProjectsList />
|
||||
|
@ -71,3 +77,13 @@ const OutlinePanel: React.FC<{}> = (props) => {
|
|||
}
|
||||
|
||||
export default OutlinePanel
|
||||
|
||||
const isOutlinePanelHotspotActiveB = new Box<boolean>(false)
|
||||
const isOutlinePanelHoveredB = new Box<boolean>(false)
|
||||
|
||||
export const shouldShowOutlineD = prism<boolean>(() => {
|
||||
const isHovered = val(isOutlinePanelHoveredB.derivation)
|
||||
const isHotspotActive = val(isOutlinePanelHotspotActiveB.derivation)
|
||||
|
||||
return isHovered || isHotspotActive
|
||||
})
|
||||
|
|
|
@ -9,14 +9,9 @@ import BasicTooltip from '@theatre/studio/uiComponents/Popover/BasicTooltip'
|
|||
import {val} from '@theatre/dataverse'
|
||||
import ExtensionToolbar from './ExtensionToolbar/ExtensionToolbar'
|
||||
import PinButton from './PinButton'
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Details,
|
||||
Ellipsis,
|
||||
Outline,
|
||||
} from '@theatre/studio/uiComponents/icons'
|
||||
import {shouldShowDetailD} from '@theatre/studio/panels/DetailPanel/DetailPanel'
|
||||
import {Details, Ellipsis, Outline} from '@theatre/studio/uiComponents/icons'
|
||||
import DoubleChevronLeft from '@theatre/studio/uiComponents/icons/DoubleChevronLeft'
|
||||
import DoubleChevronRight from '@theatre/studio/uiComponents/icons/DoubleChevronRight'
|
||||
import ToolbarIconButton from '@theatre/studio/uiComponents/toolbar/ToolbarIconButton'
|
||||
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
|
||||
import MoreMenu from './MoreMenu/MoreMenu'
|
||||
|
@ -88,10 +83,8 @@ const GlobalToolbar: React.FC = () => {
|
|||
),
|
||||
)
|
||||
|
||||
const outlinePinned = useVal(getStudio().atomP.ahistoric.pinOutline)
|
||||
const detailsPinned = useVal(getStudio().atomP.ahistoric.pinDetails)
|
||||
const showOutline = useVal(getStudio().atomP.ephemeral.showOutline)
|
||||
const showDetails = useVal(shouldShowDetailD)
|
||||
const outlinePinned = useVal(getStudio().atomP.ahistoric.pinOutline) ?? true
|
||||
const detailsPinned = useVal(getStudio().atomP.ahistoric.pinDetails) ?? true
|
||||
const hasUpdates =
|
||||
useVal(getStudio().atomP.ahistoric.updateChecker.result.hasUpdates) === true
|
||||
|
||||
|
@ -124,15 +117,14 @@ const GlobalToolbar: React.FC = () => {
|
|||
onClick={() => {
|
||||
getStudio().transaction(({stateEditors, drafts}) => {
|
||||
stateEditors.studio.ahistoric.setPinOutline(
|
||||
!drafts.ahistoric.pinOutline,
|
||||
!(drafts.ahistoric.pinOutline ?? true),
|
||||
)
|
||||
})
|
||||
}}
|
||||
icon={<Outline />}
|
||||
pinHintIcon={<ChevronRight />}
|
||||
unpinHintIcon={<ChevronLeft />}
|
||||
pinHintIcon={<DoubleChevronRight />}
|
||||
unpinHintIcon={<DoubleChevronLeft />}
|
||||
pinned={outlinePinned}
|
||||
hint={showOutline}
|
||||
/>
|
||||
{conflicts.length > 0 ? (
|
||||
<NumberOfConflictsIndicator>
|
||||
|
@ -158,15 +150,14 @@ const GlobalToolbar: React.FC = () => {
|
|||
onClick={() => {
|
||||
getStudio().transaction(({stateEditors, drafts}) => {
|
||||
stateEditors.studio.ahistoric.setPinDetails(
|
||||
!drafts.ahistoric.pinDetails,
|
||||
!(drafts.ahistoric.pinDetails ?? true),
|
||||
)
|
||||
})
|
||||
}}
|
||||
icon={<Details />}
|
||||
pinHintIcon={<ChevronLeft />}
|
||||
unpinHintIcon={<ChevronRight />}
|
||||
pinHintIcon={<DoubleChevronLeft />}
|
||||
unpinHintIcon={<DoubleChevronRight />}
|
||||
pinned={detailsPinned}
|
||||
hint={showDetails}
|
||||
/>
|
||||
</SubContainer>
|
||||
</Container>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import styled from 'styled-components'
|
||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||
import type {ComponentPropsWithRef, ReactNode} from 'react'
|
||||
import React, {forwardRef} from 'react'
|
||||
import React, {forwardRef, useState} from 'react'
|
||||
|
||||
const Container = styled.button<{pinned?: boolean}>`
|
||||
${pointerEventsAutoInNormalMode};
|
||||
|
@ -15,13 +15,15 @@ const Container = styled.button<{pinned?: boolean}>`
|
|||
height: 32px;
|
||||
outline: none;
|
||||
|
||||
color: #a8a8a9;
|
||||
color: ${({pinned}) => (pinned ? 'rgba(255, 255, 255, 0.8)' : '#A8A8A9')};
|
||||
|
||||
background: ${({pinned}) =>
|
||||
pinned ? 'rgba(40, 43, 47, 0.9)' : 'rgba(40, 43, 47, 0.45)'};
|
||||
background: rgba(40, 43, 47, 0.8);
|
||||
backdrop-filter: blur(14px);
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-bottom: 1px solid
|
||||
${({pinned}) =>
|
||||
pinned ? 'rgba(255, 255, 255, 0.7)' : 'rgba(255, 255, 255, 0.08)'};
|
||||
border-radius: 2px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(59, 63, 69, 0.8);
|
||||
|
@ -31,20 +33,8 @@ const Container = styled.button<{pinned?: boolean}>`
|
|||
background: rgba(82, 88, 96, 0.8);
|
||||
}
|
||||
|
||||
@supports not (backdrop-filter: blur()) {
|
||||
background: rgba(40, 43, 47, 0.8);
|
||||
|
||||
&:hover {
|
||||
background: rgba(59, 63, 69, 0.8);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba(82, 88, 96, 0.7);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: rgb(27, 32, 35);
|
||||
}
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -58,9 +48,33 @@ interface PinButtonProps extends ComponentPropsWithRef<'button'> {
|
|||
|
||||
const PinButton = forwardRef<HTMLButtonElement, PinButtonProps>(
|
||||
({hint, pinned, icon, pinHintIcon, unpinHintIcon, ...props}, ref) => {
|
||||
const [hovered, setHovered] = useState(false)
|
||||
|
||||
const showHint = hovered || hint
|
||||
|
||||
return (
|
||||
<Container {...props} pinned={pinned} ref={ref}>
|
||||
{hint && !pinned ? pinHintIcon : hint && pinned ? unpinHintIcon : icon}
|
||||
<Container
|
||||
{...props}
|
||||
pinned={pinned}
|
||||
ref={ref}
|
||||
onMouseOver={() => setHovered(true)}
|
||||
onMouseOut={() => setHovered(false)}
|
||||
>
|
||||
{/* Necessary for hover to work properly. */}
|
||||
<div
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
width: 'fit-content',
|
||||
height: 'fit-content',
|
||||
inset: 0,
|
||||
}}
|
||||
>
|
||||
{showHint && !pinned
|
||||
? pinHintIcon
|
||||
: showHint && pinned
|
||||
? unpinHintIcon
|
||||
: icon}
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@ import {createPortal} from 'react-dom'
|
|||
import {PortalContext} from 'reakit'
|
||||
import type {AbsolutePlacementBoxConstraints} from './TooltipWrapper'
|
||||
import TooltipWrapper from './TooltipWrapper'
|
||||
import {contextMenuShownContext} from '@theatre/studio/panels/DetailPanel/DetailPanel'
|
||||
|
||||
export type OpenFn = (
|
||||
e: React.MouseEvent | MouseEvent | {clientX: number; clientY: number},
|
||||
|
@ -113,6 +114,18 @@ export default function usePopover(
|
|||
state,
|
||||
})
|
||||
|
||||
// TODO: this lock is now exported from the detail panel, do refactor it when you get the chance
|
||||
const [, addContextMenu] = useContext(contextMenuShownContext)
|
||||
|
||||
useEffect(() => {
|
||||
let removeContextMenu: () => void | undefined
|
||||
if (state.isOpen) {
|
||||
removeContextMenu = addContextMenu()
|
||||
}
|
||||
|
||||
return () => removeContextMenu?.()
|
||||
}, [state.isOpen])
|
||||
|
||||
const portalLayer = useContext(PortalContext)
|
||||
|
||||
const node = state.isOpen ? (
|
||||
|
|
|
@ -12,8 +12,7 @@ function ArrowClockwise(props: React.SVGProps<SVGSVGElement>) {
|
|||
>
|
||||
<path
|
||||
d="M5.586 3.38a5 5 0 015.45 1.087L12.574 6h-1.572a.5.5 0 000 1h3a.5.5 0 00.5-.5v-3a.5.5 0 10-1 0v2.013l-1.76-1.754a6 6 0 100 8.482.5.5 0 10-.707-.707A5 5 0 115.587 3.38z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -12,8 +12,7 @@ function ArrowsOutCardinal(props: React.SVGProps<SVGSVGElement>) {
|
|||
>
|
||||
<path
|
||||
d="M8 1a.498.498 0 01.358.15l1.764 1.765a.5.5 0 01-.707.707L8.5 2.707V6a.5.5 0 01-1 0V2.707l-.915.915a.5.5 0 11-.707-.707l1.768-1.769A.498.498 0 018 1zM8 9.5a.5.5 0 01.5.5v3.292l.915-.915a.5.5 0 01.707.707L8.37 14.836a.499.499 0 01-.74.001l-1.752-1.753a.5.5 0 01.707-.707l.915.915V10a.5.5 0 01.5-.5zM3.622 6.584a.5.5 0 10-.707-.707L1.146 7.646a.498.498 0 00.018.724l1.751 1.752a.5.5 0 10.707-.708L2.708 8.5H6a.5.5 0 000-1H2.706l.916-.916zM12.378 5.877a.5.5 0 01.707 0l1.768 1.769a.498.498 0 01-.017.724l-1.751 1.752a.5.5 0 01-.707-.708l.914-.914H10a.5.5 0 010-1h3.294l-.916-.916a.5.5 0 010-.707z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -14,15 +14,13 @@ function Camera(props: React.SVGProps<SVGSVGElement>) {
|
|||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.767 5.75a2.75 2.75 0 100 5.5 2.75 2.75 0 000-5.5zM6.017 8.5a1.75 1.75 0 113.5 0 1.75 1.75 0 01-3.5 0z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.773 2.25a.5.5 0 00-.416.223l-.85 1.277H2.782a1.496 1.496 0 00-1.497 1.5v7a1.501 1.501 0 001.497 1.5h9.972a1.496 1.496 0 001.498-1.5v-7a1.501 1.501 0 00-1.498-1.5h-1.726l-.849-1.277a.5.5 0 00-.416-.223H5.773zm-.58 2.277L6.04 3.25h3.453l.849 1.277a.5.5 0 00.416.223h1.994a.496.496 0 01.498.5v7a.501.501 0 01-.498.5H2.781a.495.495 0 01-.497-.5v-7a.501.501 0 01.497-.5h1.995a.5.5 0 00.416-.223z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -14,8 +14,7 @@ function ChevronDown(props: React.SVGProps<SVGSVGElement>) {
|
|||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8 10.5L4 6.654 5.2 5.5 8 8.385 10.8 5.5 12 6.654 8 10.5z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -12,8 +12,7 @@ function ChevronLeft(props: React.SVGProps<SVGSVGElement>) {
|
|||
>
|
||||
<path
|
||||
d="M10.45 2.266l.956.954-4.763 4.763 4.763 4.762-.955.954-5.712-5.716 5.712-5.717z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -12,8 +12,7 @@ function ChevronRight(props: React.SVGProps<SVGSVGElement>) {
|
|||
>
|
||||
<path
|
||||
d="M5.694 2.266l-.955.954 4.763 4.763-4.763 4.762.955.954 5.712-5.716-5.712-5.717z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -14,8 +14,7 @@ function Cube(props: React.SVGProps<SVGSVGElement>) {
|
|||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.51.953L2.01 4.046c-.314.178-.51.51-.511.87v6.168c.002.354.202.695.51.87l5.5 3.093c.298.171.682.171.98 0l5.5-3.093c.308-.175.508-.516.51-.87V4.919a1.008 1.008 0 00-.51-.872L8.49.953a1.003 1.003 0 00-.98 0zm5.474 3.674L8 1.824l-4.977 2.8 5.03 2.804 4.93-2.8zM2.5 5.477v5.605l5.007 2.816.047-5.604L2.5 5.477zm6.007 8.414l4.99-2.807.003-5.6-4.946 2.81-.047 5.597z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -12,8 +12,7 @@ function CubeFull(props: React.SVGProps<SVGSVGElement>) {
|
|||
>
|
||||
<path
|
||||
d="M2.135 4.253l5.968 3.241 5.746-3.241-5.252-3.064a1 1 0 00-.993-.008L2.135 4.253zM7.586 14.947V8.338l-5.922-3.25v5.918a1 1 0 00.507.87l5.415 3.071zM8.414 14.947V8.338l5.922-3.25v5.918a1 1 0 01-.507.87l-5.415 3.071z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -12,8 +12,7 @@ function CubeHalf(props: React.SVGProps<SVGSVGElement>) {
|
|||
>
|
||||
<path
|
||||
d="M13.988 4.05L8.488.958a1.015 1.015 0 00-.975 0l-5.5 3.094c-.286.161-.514.533-.513.868v6.163c0 .356.202.7.513.875l5.5 3.094c.3.164.671.168.975 0l5.5-3.094c.31-.175.511-.519.512-.875V4.919c.002-.327-.223-.705-.512-.868zM8.056 7.427l-5.031-2.8L8 1.826l4.981 2.8-4.925 2.8zm5.444 3.656l-4.994 2.813.05-5.6L13.5 5.481v5.6z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -13,14 +13,12 @@ function CubeRendered(props: React.SVGProps<SVGSVGElement>) {
|
|||
<path
|
||||
d="M8.202 2.973l-2.92 1.58 2.714 1.58 2.817-1.58-2.61-1.58zM3.532 10.555v-3.22l3.031 1.72v3l-3.03-1.5zM12.532 10.555v-3.22l-3.031 1.72v3l3.031-1.5z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.51.955c.298-.171.682-.171.98 0l5.5 3.093c.315.179.508.51.51.87v6.165a1.024 1.024 0 01-.51.873l-5.5 3.093a1.003 1.003 0 01-.98 0L2.01 11.957a1.025 1.025 0 01-.511-.874V4.918c.002-.355.203-.696.51-.87L7.51.955zm.49.871l4.982 2.802-4.928 2.8-5.03-2.803L8 1.826zm-5.5 9.255V5.477l5.054 2.817-.047 5.606L2.5 11.08zm6.007 2.812l4.99-2.807.003-5.602-4.946 2.81-.047 5.599z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -14,7 +14,7 @@ function Details(props: React.SVGProps<SVGSVGElement>) {
|
|||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.5 3c-1.072 0-1.969.904-1.969 1.969 0 1 .929 1.968 1.969 1.968h9A1.969 1.969 0 1012.5 3h-9zm9 1H5.531v1.938H12.5A.969.969 0 0012.5 4zM3.5 9.14a1.969 1.969 0 000 3.938h9a1.969 1.969 0 100-3.937h-9zm9 1H8.406v1.938H12.5a.969.969 0 100-1.937z"
|
||||
fill="#A7A8A9"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
23
theatre/studio/src/uiComponents/icons/DoubleChevronLeft.tsx
Normal file
23
theatre/studio/src/uiComponents/icons/DoubleChevronLeft.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import * as React from 'react'
|
||||
|
||||
function DoubleChevronLeft(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.732 4.048l-.792-.792L7.2 8l4.74 4.744.792-.792L8.781 8l3.951-3.952zm-3.932 0l-.792-.792L3.268 8l4.74 4.744.792-.792L4.848 8 8.8 4.048z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default DoubleChevronLeft
|
23
theatre/studio/src/uiComponents/icons/DoubleChevronRight.tsx
Normal file
23
theatre/studio/src/uiComponents/icons/DoubleChevronRight.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import * as React from 'react'
|
||||
|
||||
function DoubleChevronRight(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.694 3.765l.792-.792 4.74 4.744-4.74 4.744-.792-.793 3.951-3.951-3.951-3.952zm3.932 0l.792-.792 4.74 4.744-4.74 4.744-.792-.793 3.952-3.951-3.952-3.952z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default DoubleChevronRight
|
|
@ -14,8 +14,7 @@ function Ellipsis(props: React.SVGProps<SVGSVGElement>) {
|
|||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M.166 7.994a2.26 2.26 0 114.518 0 2.26 2.26 0 01-4.518 0zM2.425 6.91a1.085 1.085 0 100 2.17 1.085 1.085 0 000-2.17zM5.74 7.994a2.26 2.26 0 114.519 0 2.26 2.26 0 01-4.519 0zM8 6.91a1.085 1.085 0 100 2.17 1.085 1.085 0 000-2.17zM13.575 5.735a2.26 2.26 0 100 4.519 2.26 2.26 0 000-4.52zm-1.086 2.26a1.085 1.085 0 112.171 0 1.085 1.085 0 01-2.17 0z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -14,8 +14,7 @@ function GlobeSimple(props: React.SVGProps<SVGSVGElement>) {
|
|||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zm4.75-5.216A5.505 5.505 0 002.522 7.5H5.01c.051-1.468.328-2.807.764-3.825.14-.326.298-.626.477-.89zM13.478 7.5A5.505 5.505 0 009.75 2.784c.179.265.338.565.477.891.436 1.018.713 2.357.764 3.825h2.487zm0 1a5.505 5.505 0 01-3.729 4.716c.18-.264.339-.566.478-.892.436-1.018.713-2.356.764-3.824h2.487zM6.25 13.216A5.505 5.505 0 012.522 8.5H5.01c.051 1.468.328 2.806.764 3.824.14.326.299.627.478.892zm.44-9.147c-.374.874-.63 2.074-.682 3.431h3.982c-.051-1.357-.308-2.557-.683-3.431-.21-.491-.448-.857-.686-1.092-.236-.234-.446-.315-.622-.315s-.386.081-.622.315c-.238.235-.476.6-.686 1.092zm2.617 7.862c.375-.875.631-2.075.683-3.431H6.009c.052 1.356.308 2.556.683 3.43.21.492.448.857.686 1.093.236.233.446.314.622.314s.386-.081.622-.314c.238-.236.476-.601.686-1.092z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -14,7 +14,7 @@ function Outline(props: React.SVGProps<SVGSVGElement>) {
|
|||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1.775 2.781a.5.5 0 01.5.5v1.7H4.67c.108-.957.92-1.7 1.905-1.7h6.608a1.917 1.917 0 110 3.834H6.574c-.78 0-1.452-.466-1.751-1.135H2.275v5.03h2.39a2.032 2.032 0 012.023-1.854h6.38a2.031 2.031 0 110 4.063h-6.38c-.83 0-1.543-.497-1.858-1.21H1.775a.5.5 0 01-.5-.5V3.281a.5.5 0 01.5-.5zm4.799 1.5h6.608a.917.917 0 110 1.834H6.574a.917.917 0 110-1.834zm.114 5.875h6.38a1.031 1.031 0 110 2.063h-6.38a1.032 1.032 0 110-2.063z"
|
||||
fill="#A8A8A9"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -12,8 +12,7 @@ function Package(props: React.SVGProps<SVGSVGElement>) {
|
|||
>
|
||||
<path
|
||||
d="M8.339 4.5l-2.055.644 4.451 1.393v2.748l-2.966.928-2.504-.783V6.738l2.42.758 2.055-.644-4.458-1.395L4 5.858v4.463L7.768 11.5 12 10.175V5.646L8.339 4.5z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -14,13 +14,11 @@ function Resize(props: React.SVGProps<SVGSVGElement>) {
|
|||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1.452 3.452a2 2 0 012-2h9.096a2 2 0 012 2v9.096a2 2 0 01-2 2H3.452a2 2 0 01-2-2V3.452zm2-1h9.096a1 1 0 011 1v9.096a1 1 0 01-1 1h-5.06V8.511H2.451V3.452a1 1 0 011-1z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M12.501 4.09a.5.5 0 00-.5-.5H8.95a.5.5 0 100 1h1.98l-2.45 2.449a.5.5 0 10.707.707l2.315-2.315v1.627a.5.5 0 001 0V4.09z"
|
||||
fill="#fff"
|
||||
fillOpacity={0.6}
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type {VoidFn} from '@theatre/shared/utils/types'
|
||||
import React, {useEffect} from 'react'
|
||||
import React, {useContext, useEffect} from 'react'
|
||||
import ContextMenu from './ContextMenu/ContextMenu'
|
||||
import type {
|
||||
IContextMenuItemsValue,
|
||||
|
@ -7,6 +7,7 @@ import type {
|
|||
} from './ContextMenu/ContextMenu'
|
||||
import useRequestContextMenu from './useRequestContextMenu'
|
||||
import type {IRequestContextMenuOptions} from './useRequestContextMenu'
|
||||
import {contextMenuShownContext} from '@theatre/studio/panels/DetailPanel/DetailPanel'
|
||||
|
||||
// re-exports
|
||||
export type {
|
||||
|
@ -27,10 +28,17 @@ export default function useContextMenu(
|
|||
): [node: React.ReactNode, close: VoidFn, isOpen: boolean] {
|
||||
const [status, close] = useRequestContextMenu(target, opts)
|
||||
|
||||
// TODO: this lock is now exported from the detail panel, do refactor it when you get the chance
|
||||
const [, addContextMenu] = useContext(contextMenuShownContext)
|
||||
|
||||
useEffect(() => {
|
||||
let removeContextMenu: () => void | undefined
|
||||
if (status.isOpen) {
|
||||
opts.onOpen?.()
|
||||
removeContextMenu = addContextMenu()
|
||||
}
|
||||
|
||||
return () => removeContextMenu?.()
|
||||
}, [status.isOpen, opts.onOpen])
|
||||
|
||||
const node = !status.isOpen ? (
|
||||
|
|
|
@ -21,10 +21,11 @@ export const Container = styled.button`
|
|||
|
||||
color: #a8a8a9;
|
||||
|
||||
background: rgba(40, 43, 47, 0.45);
|
||||
background: rgba(40, 43, 47, 0.8);
|
||||
backdrop-filter: blur(14px);
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 2px;
|
||||
|
||||
filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.25))
|
||||
drop-shadow(0px 2px 6px rgba(0, 0, 0, 0.15));
|
||||
|
@ -38,10 +39,17 @@ export const Container = styled.button`
|
|||
}
|
||||
|
||||
&.selected {
|
||||
background: rgba(40, 43, 47, 0.9);
|
||||
color: white;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
// Don't blur if in a button group, because it already blurs. We need to blur
|
||||
// on the group-level, otherwise we get seams.
|
||||
${ToolbarSwitchSelectContainer} > & {
|
||||
backdrop-filter: none;
|
||||
filter: none;
|
||||
border-radius: 0;
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
|
@ -51,28 +59,6 @@ export const Container = styled.button`
|
|||
border-bottom-right-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
|
||||
// Don't blur if in a button group, because it already blurs. We need to blur
|
||||
// on the group-level, otherwise we get seams.
|
||||
${ToolbarSwitchSelectContainer} > & {
|
||||
backdrop-filter: none;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
@supports not (backdrop-filter: blur()) {
|
||||
background: rgba(40, 43, 47, 0.8);
|
||||
|
||||
&:hover {
|
||||
background: rgba(59, 63, 69, 0.8);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba(82, 88, 96, 0.7);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: rgb(27, 32, 35);
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
|
|
|
@ -7,9 +7,15 @@ export default function useHotspot(spot: 'left' | 'right') {
|
|||
const hoverListener = (e: MouseEvent) => {
|
||||
const threshold = active ? 200 : 50
|
||||
|
||||
const mouseInside =
|
||||
// This is a super specific solution just for now so that the hotspot region
|
||||
// excludes the pin button.
|
||||
const topBuffer = 56
|
||||
|
||||
let mouseInside =
|
||||
spot === 'left' ? e.x < threshold : e.x > window.innerWidth - threshold
|
||||
|
||||
mouseInside &&= e.y > topBuffer
|
||||
|
||||
if (mouseInside) {
|
||||
setActive(true)
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue