import {
ArrowShapeArrowheadEndStyle,
ArrowShapeArrowheadStartStyle,
DefaultColorStyle,
DefaultDashStyle,
DefaultFillStyle,
DefaultFontStyle,
DefaultHorizontalAlignStyle,
DefaultSizeStyle,
DefaultVerticalAlignStyle,
GeoShapeGeoStyle,
LineShapeSplineStyle,
ReadonlySharedStyleMap,
SharedStyle,
StyleProp,
minBy,
useEditor,
} from '@bigbluebutton/editor'
import React, { useCallback } from 'react'
import { useRelevantStyles } from '../../hooks/useRevelantStyles'
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { Button } from '../primitives/Button'
import { ButtonPicker } from '../primitives/ButtonPicker'
import { Slider } from '../primitives/Slider'
import { DoubleDropdownPicker } from './DoubleDropdownPicker'
import { DropdownPicker } from './DropdownPicker'
import { STYLES } from './styles'
interface StylePanelProps {
isMobile?: boolean
}
/** @internal */
export const StylePanel = function StylePanel({ isMobile }: StylePanelProps) {
const editor = useEditor()
const relevantStyles = useRelevantStyles()
const handlePointerOut = useCallback(() => {
if (!isMobile) {
editor.updateInstanceState({ isChangingStyle: false })
}
}, [editor, isMobile])
if (!relevantStyles) return null
const { styles, opacity } = relevantStyles
const geo = styles.get(GeoShapeGeoStyle)
const arrowheadEnd = styles.get(ArrowShapeArrowheadEndStyle)
const arrowheadStart = styles.get(ArrowShapeArrowheadStartStyle)
const spline = styles.get(LineShapeSplineStyle)
const font = styles.get(DefaultFontStyle)
const hideGeo = geo === undefined
const hideArrowHeads = arrowheadEnd === undefined && arrowheadStart === undefined
const hideSpline = spline === undefined
const hideText = font === undefined
return (
{!hideText &&
}
{!(hideGeo && hideArrowHeads && hideSpline) && (
)}
)
}
function useStyleChangeCallback() {
const editor = useEditor()
return React.useMemo(() => {
return function handleStyleChange(style: StyleProp, value: T, squashing: boolean) {
editor.batch(() => {
if (editor.isIn('select')) {
editor.setStyleForSelectedShapes(style, value, { squashing })
}
editor.setStyleForNextShapes(style, value, { squashing })
editor.updateInstanceState({ isChangingStyle: true })
})
}
}, [editor])
}
const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const
function CommonStylePickerSet({
styles,
opacity,
}: {
styles: ReadonlySharedStyleMap
opacity: SharedStyle
}) {
const editor = useEditor()
const msg = useTranslation()
const handleValueChange = useStyleChangeCallback()
const handleOpacityValueChange = React.useCallback(
(value: number, ephemeral: boolean) => {
const item = tldrawSupportedOpacities[value]
editor.batch(() => {
if (editor.isIn('select')) {
editor.setOpacityForSelectedShapes(item, { ephemeral })
}
editor.setOpacityForNextShapes(item, { ephemeral })
editor.updateInstanceState({ isChangingStyle: true })
})
},
[editor]
)
const color = styles.get(DefaultColorStyle)
const fill = styles.get(DefaultFillStyle)
const dash = styles.get(DefaultDashStyle)
const size = styles.get(DefaultSizeStyle)
const showPickers = fill !== undefined || dash !== undefined || size !== undefined
const opacityIndex =
opacity.type === 'mixed'
? -1
: tldrawSupportedOpacities.indexOf(
minBy(tldrawSupportedOpacities, (supportedOpacity) =>
Math.abs(supportedOpacity - opacity.value)
)!
)
return (
<>
{color === undefined ? null : (
)}
{opacity === undefined ? null : (
= 0 ? opacityIndex : tldrawSupportedOpacities.length - 1}
label={
opacity.type === 'mixed' ? 'style-panel.mixed' : `opacity-style.${opacity.value}`
}
onValueChange={handleOpacityValueChange}
steps={tldrawSupportedOpacities.length - 1}
title={msg('style-panel.opacity')}
/>
)}
{showPickers && (
{fill === undefined ? null : (
)}
{dash === undefined ? null : (
)}
{size === undefined ? null : (
)}
)}
>
)
}
function TextStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
const msg = useTranslation()
const handleValueChange = useStyleChangeCallback()
const font = styles.get(DefaultFontStyle)
const align = styles.get(DefaultHorizontalAlignStyle)
const verticalAlign = styles.get(DefaultVerticalAlignStyle)
if (font === undefined && align === undefined) {
return null
}
return (
{font === undefined ? null : (
)}
{align === undefined ? null : (
{verticalAlign === undefined ? (
) : (
)}
)}
)
}
function GeoStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
const handleValueChange = useStyleChangeCallback()
const geo = styles.get(GeoShapeGeoStyle)
if (geo === undefined) {
return null
}
return (
)
}
function SplineStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
const handleValueChange = useStyleChangeCallback()
const spline = styles.get(LineShapeSplineStyle)
if (spline === undefined) {
return null
}
return (
)
}
function ArrowheadStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
const handleValueChange = useStyleChangeCallback()
const arrowheadEnd = styles.get(ArrowShapeArrowheadEndStyle)
const arrowheadStart = styles.get(ArrowShapeArrowheadStartStyle)
if (!arrowheadEnd || !arrowheadStart) {
return null
}
return (
)
}