mirror of
https://github.com/logseq/logseq.git
synced 2026-06-02 19:31:24 +00:00
wip: context bar action
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user