parent
a9e86113ba
commit
763d37aee3
32 changed files with 326 additions and 139 deletions
|
@ -1,7 +1,7 @@
|
||||||
import studio from '@theatre/studio'
|
import studio from '@theatre/studio'
|
||||||
import type {UseDragOpts} from './useDrag'
|
import type {UseDragOpts} from './useDrag'
|
||||||
import useDrag 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 type {IProject, ISheet} from '@theatre/core'
|
||||||
import {onChange, types} from '@theatre/core'
|
import {onChange, types} from '@theatre/core'
|
||||||
import type {IScrub, IStudio} from '@theatre/studio'
|
import type {IScrub, IStudio} from '@theatre/studio'
|
||||||
|
@ -19,6 +19,17 @@ const textInterpolate = (left: string, right: string, progression: number) => {
|
||||||
return left
|
return left
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const globalConfig = {
|
||||||
|
background: {
|
||||||
|
type: types.stringLiteral('black', {
|
||||||
|
black: 'black',
|
||||||
|
white: 'white',
|
||||||
|
dynamic: 'dynamic',
|
||||||
|
}),
|
||||||
|
dynamic: types.rgba(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const boxObjectConfig = {
|
const boxObjectConfig = {
|
||||||
test: types.string('Hello?', {interpolate: textInterpolate}),
|
test: types.string('Hello?', {interpolate: textInterpolate}),
|
||||||
testLiteral: types.stringLiteral('a', {a: 'Option A', b: 'Option B'}),
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={containerRef}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: '0',
|
left: '0',
|
||||||
|
|
|
@ -10,8 +10,6 @@ test.describe('setting-static-props', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Undo/redo', async ({page}) => {
|
test('Undo/redo', async ({page}) => {
|
||||||
await page.locator('[data-testid="OutlinePanel-TriggerButton"]').click()
|
|
||||||
|
|
||||||
// https://github.com/microsoft/playwright/issues/12298
|
// https://github.com/microsoft/playwright/issues/12298
|
||||||
// The div does in fact intercept pointer events, but it is meant to 🤦
|
// The div does in fact intercept pointer events, but it is meant to 🤦
|
||||||
await page
|
await page
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import {getOutlineSelection} from '@theatre/studio/selectors'
|
import {getOutlineSelection} from '@theatre/studio/selectors'
|
||||||
import {usePrism, useVal} from '@theatre/react'
|
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 styled from 'styled-components'
|
||||||
import {isProject, isSheetObject} from '@theatre/shared/instanceTypes'
|
import {isProject, isSheetObject} from '@theatre/shared/instanceTypes'
|
||||||
import {
|
import {
|
||||||
|
@ -14,15 +19,19 @@ import ProjectDetails from './ProjectDetails'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import useHotspot from '@theatre/studio/uiComponents/useHotspot'
|
import useHotspot from '@theatre/studio/uiComponents/useHotspot'
|
||||||
import {Box, prism, val} from '@theatre/dataverse'
|
import {Box, prism, val} from '@theatre/dataverse'
|
||||||
|
import EmptyState from './EmptyState'
|
||||||
|
import useLockSet from '@theatre/studio/uiComponents/useLockSet'
|
||||||
|
|
||||||
const headerHeight = `32px`
|
const headerHeight = `32px`
|
||||||
|
|
||||||
const Container = styled.div<{pin: boolean}>`
|
const Container = styled.div<{pin: boolean}>`
|
||||||
|
${pointerEventsAutoInNormalMode};
|
||||||
background-color: rgba(40, 43, 47, 0.8);
|
background-color: rgba(40, 43, 47, 0.8);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
width: 236px;
|
// Temporary, see comment about CSS grid in SingleRowPropEditor.
|
||||||
|
width: 280px;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
z-index: ${panelZIndexes.propsPanel};
|
z-index: ${panelZIndexes.propsPanel};
|
||||||
|
|
||||||
|
@ -68,14 +77,18 @@ const Body = styled.div`
|
||||||
user-select: none;
|
user-select: none;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const contextMenuShownContext = createContext<
|
||||||
|
ReturnType<typeof useLockSet>
|
||||||
|
>([false, () => () => {}])
|
||||||
|
|
||||||
const DetailPanel: React.FC<{}> = (props) => {
|
const DetailPanel: React.FC<{}> = (props) => {
|
||||||
const pin = useVal(getStudio().atomP.ahistoric.pinDetails) !== false
|
const pin = useVal(getStudio().atomP.ahistoric.pinDetails) !== false
|
||||||
|
|
||||||
const hostspotActive = useHotspot('right')
|
const hotspotActive = useHotspot('right')
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
isDetailPanelHotspotActiveB.set(hostspotActive)
|
isDetailPanelHotspotActiveB.set(hotspotActive)
|
||||||
}, [hostspotActive])
|
}, [hotspotActive])
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -85,6 +98,10 @@ const DetailPanel: React.FC<{}> = (props) => {
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const [isContextMenuShown] = useContext(contextMenuShownContext)
|
||||||
|
|
||||||
|
const showDetailsPanel = pin || hotspotActive || isContextMenuShown
|
||||||
|
|
||||||
return usePrism(() => {
|
return usePrism(() => {
|
||||||
const selection = getOutlineSelection()
|
const selection = getOutlineSelection()
|
||||||
|
|
||||||
|
@ -93,7 +110,7 @@ const DetailPanel: React.FC<{}> = (props) => {
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
data-testid="DetailPanel-Object"
|
data-testid="DetailPanel-Object"
|
||||||
pin={pin || hostspotActive}
|
pin={showDetailsPanel}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
isDetailPanelHoveredB.set(true)
|
isDetailPanelHoveredB.set(true)
|
||||||
}}
|
}}
|
||||||
|
@ -125,7 +142,7 @@ const DetailPanel: React.FC<{}> = (props) => {
|
||||||
const project = selection.find(isProject)
|
const project = selection.find(isProject)
|
||||||
if (project) {
|
if (project) {
|
||||||
return (
|
return (
|
||||||
<Container pin={pin || hostspotActive}>
|
<Container pin={showDetailsPanel}>
|
||||||
<Header>
|
<Header>
|
||||||
<Title title={`${project.address.projectId}`}>
|
<Title title={`${project.address.projectId}`}>
|
||||||
<TitleBar_Piece>{project.address.projectId} </TitleBar_Piece>
|
<TitleBar_Piece>{project.address.projectId} </TitleBar_Piece>
|
||||||
|
@ -138,11 +155,31 @@ const DetailPanel: React.FC<{}> = (props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <></>
|
return (
|
||||||
}, [pin, hostspotActive])
|
<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 isDetailPanelHotspotActiveB = new Box<boolean>(false)
|
||||||
const isDetailPanelHoveredB = 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'
|
import {useEditingToolsForCompoundProp} from '@theatre/studio/propEditors/useEditingToolsForCompoundProp'
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
--step: 8px;
|
--step: 15px;
|
||||||
--left-pad: 0px;
|
--left-pad: 15px;
|
||||||
${pointerEventsAutoInNormalMode};
|
${pointerEventsAutoInNormalMode};
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,19 @@ import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS
|
||||||
|
|
||||||
export const indentationFormula = `calc(var(--left-pad) + var(--depth) * var(--step))`
|
export const indentationFormula = `calc(var(--left-pad) + var(--depth) * var(--step))`
|
||||||
|
|
||||||
const LeftRow = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
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%;
|
--right-width: 60%;
|
||||||
position: relative;
|
position: relative;
|
||||||
${pointerEventsAutoInNormalMode};
|
${pointerEventsAutoInNormalMode};
|
||||||
|
@ -97,7 +105,7 @@ export function SingleRowPropEditor<T>({
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LeftRow>
|
<Container>
|
||||||
{contextMenu}
|
{contextMenu}
|
||||||
<Left>
|
<Left>
|
||||||
<ControlsContainer>{editingTools.controlIndicators}</ControlsContainer>
|
<ControlsContainer>{editingTools.controlIndicators}</ControlsContainer>
|
||||||
|
@ -113,6 +121,6 @@ export function SingleRowPropEditor<T>({
|
||||||
</Left>
|
</Left>
|
||||||
|
|
||||||
<InputContainer>{children}</InputContainer>
|
<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 styled, {css} from 'styled-components'
|
||||||
import noop from '@theatre/shared/utils/noop'
|
import noop from '@theatre/shared/utils/noop'
|
||||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
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`
|
export const Container = styled.li`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -22,10 +22,10 @@ const Header = styled(BaseHeader)`
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
margin-left: calc(4px + var(--depth) * 16px);
|
margin-left: calc(4px + var(--depth) * 16px);
|
||||||
padding-left: 8px;
|
padding-left: 4px;
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
gap: 8px;
|
gap: 4px;
|
||||||
height: 23px;
|
height: 21px;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -86,7 +86,7 @@ const Head_Label = styled.span`
|
||||||
${pointerEventsAutoInNormalMode};
|
${pointerEventsAutoInNormalMode};
|
||||||
position: relative;
|
position: relative;
|
||||||
// Compensate for border bottom
|
// Compensate for border bottom
|
||||||
top: 1px;
|
top: 0.5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -106,7 +106,6 @@ const Head_Icon_WithDescendants = styled.span<{isOpen: boolean}>`
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
transform: rotateZ(${(props) => (props.isOpen ? 90 : 0)}deg);
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const ChildrenContainer = styled.ul`
|
const ChildrenContainer = styled.ul`
|
||||||
|
@ -141,7 +140,7 @@ const BaseItem: React.FC<{
|
||||||
<Head_IconContainer>
|
<Head_IconContainer>
|
||||||
{canContainChildren ? (
|
{canContainChildren ? (
|
||||||
<Head_Icon_WithDescendants isOpen={true}>
|
<Head_Icon_WithDescendants isOpen={true}>
|
||||||
<ChevronRight />
|
<ChevronDown />
|
||||||
</Head_Icon_WithDescendants>
|
</Head_Icon_WithDescendants>
|
||||||
) : (
|
) : (
|
||||||
<Package />
|
<Package />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {useEffect, useState} from 'react'
|
import React, {useEffect, useLayoutEffect} from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {panelZIndexes} from '@theatre/studio/panels/BasePanel/common'
|
import {panelZIndexes} from '@theatre/studio/panels/BasePanel/common'
|
||||||
import ProjectsList from './ProjectsList/ProjectsList'
|
import ProjectsList from './ProjectsList/ProjectsList'
|
||||||
|
@ -6,6 +6,7 @@ import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||||
import {useVal} from '@theatre/react'
|
import {useVal} from '@theatre/react'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import useHotspot from '@theatre/studio/uiComponents/useHotspot'
|
import useHotspot from '@theatre/studio/uiComponents/useHotspot'
|
||||||
|
import {Box, prism, val} from '@theatre/dataverse'
|
||||||
|
|
||||||
const headerHeight = `44px`
|
const headerHeight = `44px`
|
||||||
|
|
||||||
|
@ -43,26 +44,31 @@ const Container = styled.div<{pin: boolean}>`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const OutlinePanel: React.FC<{}> = (props) => {
|
const OutlinePanel: React.FC<{}> = () => {
|
||||||
const pin = useVal(getStudio().atomP.ahistoric.pinOutline) !== false
|
const pin = useVal(getStudio().atomP.ahistoric.pinOutline) ?? true
|
||||||
const show = useVal(getStudio().atomP.ephemeral.showOutline)
|
const show = useVal(shouldShowOutlineD)
|
||||||
const active = useHotspot('left')
|
const active = useHotspot('left')
|
||||||
const [hovered, setHovered] = useState(false)
|
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
isOutlinePanelHotspotActiveB.set(active)
|
||||||
|
}, [active])
|
||||||
|
|
||||||
|
// cleanup
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getStudio().transaction(({stateEditors, drafts}) => {
|
return () => {
|
||||||
stateEditors.studio.ephemeral.setShowOutline(active || hovered)
|
isOutlinePanelHoveredB.set(false)
|
||||||
})
|
isOutlinePanelHotspotActiveB.set(false)
|
||||||
}, [active, hovered])
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
pin={pin || show}
|
pin={pin || show}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
setHovered(true)
|
isOutlinePanelHoveredB.set(true)
|
||||||
}}
|
}}
|
||||||
onMouseLeave={() => {
|
onMouseLeave={() => {
|
||||||
setHovered(false)
|
isOutlinePanelHoveredB.set(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ProjectsList />
|
<ProjectsList />
|
||||||
|
@ -71,3 +77,13 @@ const OutlinePanel: React.FC<{}> = (props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OutlinePanel
|
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 {val} from '@theatre/dataverse'
|
||||||
import ExtensionToolbar from './ExtensionToolbar/ExtensionToolbar'
|
import ExtensionToolbar from './ExtensionToolbar/ExtensionToolbar'
|
||||||
import PinButton from './PinButton'
|
import PinButton from './PinButton'
|
||||||
import {
|
import {Details, Ellipsis, Outline} from '@theatre/studio/uiComponents/icons'
|
||||||
ChevronLeft,
|
import DoubleChevronLeft from '@theatre/studio/uiComponents/icons/DoubleChevronLeft'
|
||||||
ChevronRight,
|
import DoubleChevronRight from '@theatre/studio/uiComponents/icons/DoubleChevronRight'
|
||||||
Details,
|
|
||||||
Ellipsis,
|
|
||||||
Outline,
|
|
||||||
} from '@theatre/studio/uiComponents/icons'
|
|
||||||
import {shouldShowDetailD} from '@theatre/studio/panels/DetailPanel/DetailPanel'
|
|
||||||
import ToolbarIconButton from '@theatre/studio/uiComponents/toolbar/ToolbarIconButton'
|
import ToolbarIconButton from '@theatre/studio/uiComponents/toolbar/ToolbarIconButton'
|
||||||
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
|
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
|
||||||
import MoreMenu from './MoreMenu/MoreMenu'
|
import MoreMenu from './MoreMenu/MoreMenu'
|
||||||
|
@ -88,10 +83,8 @@ const GlobalToolbar: React.FC = () => {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const outlinePinned = useVal(getStudio().atomP.ahistoric.pinOutline)
|
const outlinePinned = useVal(getStudio().atomP.ahistoric.pinOutline) ?? true
|
||||||
const detailsPinned = useVal(getStudio().atomP.ahistoric.pinDetails)
|
const detailsPinned = useVal(getStudio().atomP.ahistoric.pinDetails) ?? true
|
||||||
const showOutline = useVal(getStudio().atomP.ephemeral.showOutline)
|
|
||||||
const showDetails = useVal(shouldShowDetailD)
|
|
||||||
const hasUpdates =
|
const hasUpdates =
|
||||||
useVal(getStudio().atomP.ahistoric.updateChecker.result.hasUpdates) === true
|
useVal(getStudio().atomP.ahistoric.updateChecker.result.hasUpdates) === true
|
||||||
|
|
||||||
|
@ -124,15 +117,14 @@ const GlobalToolbar: React.FC = () => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
getStudio().transaction(({stateEditors, drafts}) => {
|
getStudio().transaction(({stateEditors, drafts}) => {
|
||||||
stateEditors.studio.ahistoric.setPinOutline(
|
stateEditors.studio.ahistoric.setPinOutline(
|
||||||
!drafts.ahistoric.pinOutline,
|
!(drafts.ahistoric.pinOutline ?? true),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
icon={<Outline />}
|
icon={<Outline />}
|
||||||
pinHintIcon={<ChevronRight />}
|
pinHintIcon={<DoubleChevronRight />}
|
||||||
unpinHintIcon={<ChevronLeft />}
|
unpinHintIcon={<DoubleChevronLeft />}
|
||||||
pinned={outlinePinned}
|
pinned={outlinePinned}
|
||||||
hint={showOutline}
|
|
||||||
/>
|
/>
|
||||||
{conflicts.length > 0 ? (
|
{conflicts.length > 0 ? (
|
||||||
<NumberOfConflictsIndicator>
|
<NumberOfConflictsIndicator>
|
||||||
|
@ -158,15 +150,14 @@ const GlobalToolbar: React.FC = () => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
getStudio().transaction(({stateEditors, drafts}) => {
|
getStudio().transaction(({stateEditors, drafts}) => {
|
||||||
stateEditors.studio.ahistoric.setPinDetails(
|
stateEditors.studio.ahistoric.setPinDetails(
|
||||||
!drafts.ahistoric.pinDetails,
|
!(drafts.ahistoric.pinDetails ?? true),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
icon={<Details />}
|
icon={<Details />}
|
||||||
pinHintIcon={<ChevronLeft />}
|
pinHintIcon={<DoubleChevronLeft />}
|
||||||
unpinHintIcon={<ChevronRight />}
|
unpinHintIcon={<DoubleChevronRight />}
|
||||||
pinned={detailsPinned}
|
pinned={detailsPinned}
|
||||||
hint={showDetails}
|
|
||||||
/>
|
/>
|
||||||
</SubContainer>
|
</SubContainer>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||||
import type {ComponentPropsWithRef, ReactNode} from 'react'
|
import type {ComponentPropsWithRef, ReactNode} from 'react'
|
||||||
import React, {forwardRef} from 'react'
|
import React, {forwardRef, useState} from 'react'
|
||||||
|
|
||||||
const Container = styled.button<{pinned?: boolean}>`
|
const Container = styled.button<{pinned?: boolean}>`
|
||||||
${pointerEventsAutoInNormalMode};
|
${pointerEventsAutoInNormalMode};
|
||||||
|
@ -15,13 +15,15 @@ const Container = styled.button<{pinned?: boolean}>`
|
||||||
height: 32px;
|
height: 32px;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
color: #a8a8a9;
|
color: ${({pinned}) => (pinned ? 'rgba(255, 255, 255, 0.8)' : '#A8A8A9')};
|
||||||
|
|
||||||
background: ${({pinned}) =>
|
background: rgba(40, 43, 47, 0.8);
|
||||||
pinned ? 'rgba(40, 43, 47, 0.9)' : 'rgba(40, 43, 47, 0.45)'};
|
|
||||||
backdrop-filter: blur(14px);
|
backdrop-filter: blur(14px);
|
||||||
border: none;
|
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 {
|
&:hover {
|
||||||
background: rgba(59, 63, 69, 0.8);
|
background: rgba(59, 63, 69, 0.8);
|
||||||
|
@ -31,20 +33,8 @@ const Container = styled.button<{pinned?: boolean}>`
|
||||||
background: rgba(82, 88, 96, 0.8);
|
background: rgba(82, 88, 96, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports not (backdrop-filter: blur()) {
|
svg {
|
||||||
background: rgba(40, 43, 47, 0.8);
|
display: block;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(59, 63, 69, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: rgba(82, 88, 96, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background: rgb(27, 32, 35);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -58,9 +48,33 @@ interface PinButtonProps extends ComponentPropsWithRef<'button'> {
|
||||||
|
|
||||||
const PinButton = forwardRef<HTMLButtonElement, PinButtonProps>(
|
const PinButton = forwardRef<HTMLButtonElement, PinButtonProps>(
|
||||||
({hint, pinned, icon, pinHintIcon, unpinHintIcon, ...props}, ref) => {
|
({hint, pinned, icon, pinHintIcon, unpinHintIcon, ...props}, ref) => {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
|
||||||
|
const showHint = hovered || hint
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container {...props} pinned={pinned} ref={ref}>
|
<Container
|
||||||
{hint && !pinned ? pinHintIcon : hint && pinned ? unpinHintIcon : icon}
|
{...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>
|
</Container>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {createPortal} from 'react-dom'
|
||||||
import {PortalContext} from 'reakit'
|
import {PortalContext} from 'reakit'
|
||||||
import type {AbsolutePlacementBoxConstraints} from './TooltipWrapper'
|
import type {AbsolutePlacementBoxConstraints} from './TooltipWrapper'
|
||||||
import TooltipWrapper from './TooltipWrapper'
|
import TooltipWrapper from './TooltipWrapper'
|
||||||
|
import {contextMenuShownContext} from '@theatre/studio/panels/DetailPanel/DetailPanel'
|
||||||
|
|
||||||
export type OpenFn = (
|
export type OpenFn = (
|
||||||
e: React.MouseEvent | MouseEvent | {clientX: number; clientY: number},
|
e: React.MouseEvent | MouseEvent | {clientX: number; clientY: number},
|
||||||
|
@ -113,6 +114,18 @@ export default function usePopover(
|
||||||
state,
|
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 portalLayer = useContext(PortalContext)
|
||||||
|
|
||||||
const node = state.isOpen ? (
|
const node = state.isOpen ? (
|
||||||
|
|
|
@ -12,8 +12,7 @@ function ArrowClockwise(props: React.SVGProps<SVGSVGElement>) {
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,8 +12,7 @@ function ArrowsOutCardinal(props: React.SVGProps<SVGSVGElement>) {
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,15 +14,13 @@ function Camera(props: React.SVGProps<SVGSVGElement>) {
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,8 +14,7 @@ function ChevronDown(props: React.SVGProps<SVGSVGElement>) {
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,8 +12,7 @@ function ChevronLeft(props: React.SVGProps<SVGSVGElement>) {
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,8 +12,7 @@ function ChevronRight(props: React.SVGProps<SVGSVGElement>) {
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,8 +14,7 @@ function Cube(props: React.SVGProps<SVGSVGElement>) {
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,8 +12,7 @@ function CubeFull(props: React.SVGProps<SVGSVGElement>) {
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,8 +12,7 @@ function CubeHalf(props: React.SVGProps<SVGSVGElement>) {
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,14 +13,12 @@ function CubeRendered(props: React.SVGProps<SVGSVGElement>) {
|
||||||
<path
|
<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"
|
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"
|
fill="#fff"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,7 +14,7 @@ function Details(props: React.SVGProps<SVGSVGElement>) {
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="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"
|
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>
|
</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"
|
fillRule="evenodd"
|
||||||
clipRule="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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,8 +14,7 @@ function GlobeSimple(props: React.SVGProps<SVGSVGElement>) {
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,7 +14,7 @@ function Outline(props: React.SVGProps<SVGSVGElement>) {
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="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"
|
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>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,8 +12,7 @@ function Package(props: React.SVGProps<SVGSVGElement>) {
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,13 +14,11 @@ function Resize(props: React.SVGProps<SVGSVGElement>) {
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
<path
|
<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"
|
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"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type {VoidFn} from '@theatre/shared/utils/types'
|
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 ContextMenu from './ContextMenu/ContextMenu'
|
||||||
import type {
|
import type {
|
||||||
IContextMenuItemsValue,
|
IContextMenuItemsValue,
|
||||||
|
@ -7,6 +7,7 @@ import type {
|
||||||
} from './ContextMenu/ContextMenu'
|
} from './ContextMenu/ContextMenu'
|
||||||
import useRequestContextMenu from './useRequestContextMenu'
|
import useRequestContextMenu from './useRequestContextMenu'
|
||||||
import type {IRequestContextMenuOptions} from './useRequestContextMenu'
|
import type {IRequestContextMenuOptions} from './useRequestContextMenu'
|
||||||
|
import {contextMenuShownContext} from '@theatre/studio/panels/DetailPanel/DetailPanel'
|
||||||
|
|
||||||
// re-exports
|
// re-exports
|
||||||
export type {
|
export type {
|
||||||
|
@ -27,10 +28,17 @@ export default function useContextMenu(
|
||||||
): [node: React.ReactNode, close: VoidFn, isOpen: boolean] {
|
): [node: React.ReactNode, close: VoidFn, isOpen: boolean] {
|
||||||
const [status, close] = useRequestContextMenu(target, opts)
|
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(() => {
|
useEffect(() => {
|
||||||
|
let removeContextMenu: () => void | undefined
|
||||||
if (status.isOpen) {
|
if (status.isOpen) {
|
||||||
opts.onOpen?.()
|
opts.onOpen?.()
|
||||||
|
removeContextMenu = addContextMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return () => removeContextMenu?.()
|
||||||
}, [status.isOpen, opts.onOpen])
|
}, [status.isOpen, opts.onOpen])
|
||||||
|
|
||||||
const node = !status.isOpen ? (
|
const node = !status.isOpen ? (
|
||||||
|
|
|
@ -21,10 +21,11 @@ export const Container = styled.button`
|
||||||
|
|
||||||
color: #a8a8a9;
|
color: #a8a8a9;
|
||||||
|
|
||||||
background: rgba(40, 43, 47, 0.45);
|
background: rgba(40, 43, 47, 0.8);
|
||||||
backdrop-filter: blur(14px);
|
backdrop-filter: blur(14px);
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
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))
|
filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.25))
|
||||||
drop-shadow(0px 2px 6px rgba(0, 0, 0, 0.15));
|
drop-shadow(0px 2px 6px rgba(0, 0, 0, 0.15));
|
||||||
|
@ -38,18 +39,8 @@ export const Container = styled.button`
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
background: rgba(40, 43, 47, 0.9);
|
color: rgba(255, 255, 255, 0.8);
|
||||||
color: white;
|
border-bottom: 1px solid rgba(255, 255, 255, 0.7);
|
||||||
}
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
border-top-left-radius: 2px;
|
|
||||||
border-bottom-left-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
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
|
// 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} > & {
|
${ToolbarSwitchSelectContainer} > & {
|
||||||
backdrop-filter: none;
|
backdrop-filter: none;
|
||||||
filter: none;
|
filter: none;
|
||||||
}
|
border-radius: 0;
|
||||||
|
|
||||||
@supports not (backdrop-filter: blur()) {
|
&:first-child {
|
||||||
background: rgba(40, 43, 47, 0.8);
|
border-top-left-radius: 2px;
|
||||||
|
border-bottom-left-radius: 2px;
|
||||||
&:hover {
|
|
||||||
background: rgba(59, 63, 69, 0.8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:last-child {
|
||||||
background: rgba(82, 88, 96, 0.7);
|
border-bottom-right-radius: 2px;
|
||||||
}
|
border-top-right-radius: 2px;
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background: rgb(27, 32, 35);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -7,9 +7,15 @@ export default function useHotspot(spot: 'left' | 'right') {
|
||||||
const hoverListener = (e: MouseEvent) => {
|
const hoverListener = (e: MouseEvent) => {
|
||||||
const threshold = active ? 200 : 50
|
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
|
spot === 'left' ? e.x < threshold : e.x > window.innerWidth - threshold
|
||||||
|
|
||||||
|
mouseInside &&= e.y > topBuffer
|
||||||
|
|
||||||
if (mouseInside) {
|
if (mouseInside) {
|
||||||
setActive(true)
|
setActive(true)
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue