Co-authored-by: Aria Minaei <aria.minaei@gmail.com>
This commit is contained in:
Andrew Prifer 2022-06-01 14:46:37 +02:00 committed by GitHub
parent a9e86113ba
commit 763d37aee3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 326 additions and 139 deletions

View file

@ -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',

View file

@ -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

View file

@ -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)

View file

@ -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};
`

View file

@ -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>
)
}

View 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

View file

@ -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 />

View file

@ -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
})

View file

@ -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>

View file

@ -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>
)
},

View file

@ -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 ? (

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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>
)

View 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

View 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

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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>
)

View file

@ -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 ? (

View file

@ -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,18 +39,8 @@ export const Container = styled.button`
}
&.selected {
background: rgba(40, 43, 47, 0.9);
color: white;
}
&:first-child {
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}
&:last-child {
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
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
@ -57,21 +48,16 @@ export const Container = styled.button`
${ToolbarSwitchSelectContainer} > & {
backdrop-filter: none;
filter: none;
}
border-radius: 0;
@supports not (backdrop-filter: blur()) {
background: rgba(40, 43, 47, 0.8);
&:hover {
background: rgba(59, 63, 69, 0.8);
&:first-child {
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}
&:active {
background: rgba(82, 88, 96, 0.7);
}
&.selected {
background: rgb(27, 32, 35);
&:last-child {
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
}
}
`

View file

@ -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 {