wip: context bar action

This commit is contained in:
Peng Xiao
2022-08-20 16:05:30 +08:00
parent e9b102145f
commit b3d69ec100
4 changed files with 123 additions and 125 deletions

View File

@@ -1,58 +1,19 @@
import * as React from 'react'
import {
getContextBarTranslation,
HTMLContainer,
TLContextBarComponent,
useApp,
getContextBarTranslation,
} from '@tldraw/react'
import { observer } from 'mobx-react-lite'
import type { TextShape, Shape } from '~lib/shapes'
import { NumberInput } from '~components/inputs/NumberInput'
import { ColorInput } from '~components/inputs/ColorInput'
import { SwitchInput } from '../inputs/SwitchInput'
import * as React from 'react'
import type { Shape } from '~lib/shapes'
import { getContextBarActionsForTypes } from './contextBarActionFactory'
const _ContextBar: TLContextBarComponent<Shape> = ({ shapes, offsets, hidden }) => {
const app = useApp()
const rSize = React.useRef<[number, number] | null>(null)
const rContextBar = React.useRef<HTMLDivElement>(null)
const updateStroke = React.useCallback<React.ChangeEventHandler<HTMLInputElement>>(e => {
shapes.forEach(shape => shape.update({ stroke: e.currentTarget.value }))
app.persist()
}, [])
const updateFill = React.useCallback<React.ChangeEventHandler<HTMLInputElement>>(e => {
shapes.forEach(shape => shape.update({ fill: e.currentTarget.value }))
app.persist()
}, [])
const updateStrokeWidth = React.useCallback<React.ChangeEventHandler<HTMLInputElement>>(e => {
shapes.forEach(shape => shape.update({ strokeWidth: +e.currentTarget.value }))
app.persist()
}, [])
const updateOpacity = React.useCallback<React.ChangeEventHandler<HTMLInputElement>>(e => {
shapes.forEach(shape => shape.update({ opacity: +e.currentTarget.value }))
app.persist()
}, [])
const updateFontSize = React.useCallback<React.ChangeEventHandler<HTMLInputElement>>(e => {
textShapes.forEach(shape => shape.update({ fontSize: +e.currentTarget.value }))
app.persist()
}, [])
const updateFontWeight = React.useCallback<React.ChangeEventHandler<HTMLInputElement>>(e => {
textShapes.forEach(shape => shape.update({ fontWeight: +e.currentTarget.value }))
app.persist()
}, [])
const updateTransparent = React.useCallback(transparent => {
// const transparent = shapes.some(s => s.props.fill !== 'transparent')
console.log(transparent)
shapes.forEach(shape => shape.update({ fill: transparent ? 'transparent' : '#fff' }))
app.persist()
}, [])
React.useLayoutEffect(() => {
setTimeout(() => {
const elm = rContextBar.current
@@ -72,63 +33,21 @@ const _ContextBar: TLContextBarComponent<Shape> = ({ shapes, offsets, hidden })
if (!app) return null
const textShapes = shapes.filter(shape => shape.type === 'text') as TextShape[]
const ShapeContent =
shapes.length === 1 && 'ReactContextBar' in shapes[0] ? shapes[0]['ReactContextBar'] : null
const transparent = shapes.every(s => s.props.fill === 'transparent')
const Actions = getContextBarActionsForTypes(shapes.map(s => s.props.type))
return (
<HTMLContainer centered>
<div
ref={rContextBar}
className="tl-contextbar"
style={{ pointerEvents: hidden ? 'none' : 'all' }}
>
{ShapeContent ? (
<ShapeContent />
) : (
<>
<ColorInput label="Stroke" value={shapes[0].props.stroke} onChange={updateStroke} />
{!transparent && (
<ColorInput label="Fill" value={shapes[0].props.fill} onChange={updateFill} />
)}
<SwitchInput
label="Transparent"
checked={transparent}
onCheckedChange={updateTransparent}
/>
<NumberInput
label="Width"
value={Math.max(...shapes.map(shape => shape.props.strokeWidth))}
onChange={updateStrokeWidth}
style={{ width: 48 }}
/>
<NumberInput
label="Opacity"
value={Math.max(...shapes.map(shape => shape.props.opacity))}
onChange={updateOpacity}
step={0.1}
style={{ width: 48 }}
/>
{textShapes.length > 0 ? (
<>
<NumberInput
label="Size"
value={Math.max(...textShapes.map(shape => shape.props.fontSize))}
onChange={updateFontSize}
style={{ width: 48 }}
/>
<NumberInput
label=" Weight"
value={Math.max(...textShapes.map(shape => shape.props.fontWeight))}
onChange={updateFontWeight}
style={{ width: 48 }}
/>
</>
) : null}
</>
)}
</div>
{Actions.length > 0 && (
<div
ref={rContextBar}
className="tl-contextbar"
style={{ pointerEvents: hidden ? 'none' : 'all' }}
>
{Actions.map((Action, idx) => (
<Action key={idx} />
))}
</div>
)}
</HTMLContainer>
)
}

View File

@@ -0,0 +1,78 @@
import { isNonNullable } from '@tldraw/core'
import { useApp } from '@tldraw/react'
import { observer } from 'mobx-react-lite'
import { TablerIcon } from '~components/icons'
import { SelectInput, SelectOption } from '~components/inputs/SelectInput'
import { LogseqPortalShape, Shape } from '~lib'
export const contextBarActionTypes = [
'NoFill',
'LogseqPortalViewMode',
'ScaleLevel',
'ColorAccent',
'StrokeColor',
'NoStroke',
'OpenPage',
'OpenInRightSidebar',
] as const
type ContextBarActionType = typeof contextBarActionTypes[number]
const contextBarActionMapping = new Map<ContextBarActionType, React.FC>()
const LogseqPortalViewModeAction = observer(() => {
const app = useApp<Shape>()
const shapes = app.selectedShapesArray.filter(
s => s.props.type === LogseqPortalShape.defaultProps.type
) as LogseqPortalShape[]
const collapsed = shapes.every(s => s.collapsed)
const ViewModeOptions: SelectOption[] = [
{
value: '0',
label: <TablerIcon name="layout-navbar-expand" />,
},
{
value: '1',
label: <TablerIcon name="layout-navbar-collapse" />,
},
]
return (
<SelectInput
options={ViewModeOptions}
value={collapsed ? '1' : '0'}
onValueChange={v => {
shapes.forEach(shape => {
shape.setCollapsed(v === '1' ? true : false)
})
}}
/>
)
})
contextBarActionMapping.set('LogseqPortalViewMode', LogseqPortalViewModeAction)
type ShapeType = Shape['props']['type']
const shapeMapping: Partial<Record<ShapeType, ContextBarActionType[]>> = {
'logseq-portal': ['LogseqPortalViewMode'],
}
export const getContextBarActionsForType = (type: ShapeType) => {
return (shapeMapping[type] ?? [])
.map(actionType => contextBarActionMapping.get(actionType))
.filter(isNonNullable)
}
export const getContextBarActionsForTypes = (types: ShapeType[]) => {
const actions = new Set(types.length > 0 ? getContextBarActionsForType(types[0]) : [])
for (let i = 1; i < types.length && actions.size > 0; i++) {
const actionsForType = getContextBarActionsForType(types[i])
actions.forEach(action => {
if (!actionsForType.includes(action)) {
actions.delete(action)
}
})
}
return Array.from(actions)
}

View File

@@ -2,7 +2,7 @@
import { TLBoxShape, TLBoxShapeProps, TLResizeInfo, validUUID } from '@tldraw/core'
import { HTMLContainer, TLComponentProps, useApp } from '@tldraw/react'
import Vec from '@tldraw/vec'
import { makeObservable, runInAction } from 'mobx'
import { action, computed, makeObservable, runInAction } from 'mobx'
import { observer } from 'mobx-react-lite'
import * as React from 'react'
import { TablerIcon } from '~components/icons'
@@ -196,6 +196,32 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
return false
}
@computed get collapsed() {
return this.props.blockType === 'B' ? this.props.compact : this.props.collapsed
}
@action setCollapsed = (collapsed: boolean) => {
if (this.props.blockType === 'B') {
this.update({ compact: collapsed })
this.canResize[1] = !collapsed
if (!collapsed) {
// this will also persist the state, so we can skip persist call
this.autoResizeHeight()
} else {
this.persist?.()
}
} else {
const originalHeight = this.props.size[1]
this.canResize[1] = !collapsed
this.update({
collapsed: collapsed,
size: [this.props.size[0], collapsed ? HEADER_HEIGHT : this.props.collapsedHeight],
collapsedHeight: collapsed ? originalHeight : this.props.collapsedHeight,
})
this.persist?.()
}
}
useComponentSize<T extends HTMLElement>(ref: React.RefObject<T> | null, selector = '') {
const [size, setSize] = React.useState<[number, number]>([0, 0])
const app = useApp<Shape>()
@@ -253,21 +279,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
<SwitchInput
label="Collapsed"
checked={this.props.collapsed}
onCheckedChange={collapsing => {
runInAction(() => {
const originalHeight = this.props.size[1]
this.canResize[1] = !collapsing
this.update({
collapsed: collapsing,
size: [
this.props.size[0],
collapsing ? HEADER_HEIGHT : this.props.collapsedHeight,
],
collapsedHeight: collapsing ? originalHeight : this.props.collapsedHeight,
})
app.persist()
})
}}
onCheckedChange={this.setCollapsed}
/>
)}
@@ -275,18 +287,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
<SwitchInput
label="Compact"
checked={this.props.compact}
onCheckedChange={compact => {
runInAction(() => {
this.update({ compact })
this.canResize[1] = !compact
if (!compact) {
// this will also persist the state, so we can skip persist call
this.autoResizeHeight()
} else {
app.persist()
}
})
}}
onCheckedChange={this.setCollapsed}
/>
)}
</>