2023-10-11 13:36:54 +11:00
|
|
|
import { Fragment, WheelEvent, useRef, useMemo } from 'react'
|
2023-08-31 09:47:59 -04:00
|
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
|
|
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
|
|
|
|
import { Popover, Transition } from '@headlessui/react'
|
|
|
|
import styles from './Toolbar.module.css'
|
2023-10-11 15:12:29 +11:00
|
|
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
2023-09-16 01:23:11 -04:00
|
|
|
import { ActionIcon } from 'components/ActionIcon'
|
2023-09-25 19:49:53 -07:00
|
|
|
import { engineCommandManager } from './lang/std/engineConnection'
|
2023-10-11 13:36:54 +11:00
|
|
|
import { useModelingContext } from 'hooks/useModelingContext'
|
2023-09-16 01:23:11 -04:00
|
|
|
|
|
|
|
export const sketchButtonClassnames = {
|
|
|
|
background:
|
|
|
|
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-fern-20 dark:group-hover:bg-fern-10 dark:hover:bg-fern-10 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50',
|
|
|
|
icon: 'text-fern-20 h-auto group-hover:text-fern-10 hover:text-fern-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-60 hover:group-disabled:text-inherit',
|
|
|
|
}
|
|
|
|
|
2022-11-27 14:06:33 +11:00
|
|
|
export const Toolbar = () => {
|
2023-10-11 13:36:54 +11:00
|
|
|
const { state, send, context } = useModelingContext()
|
2023-10-04 12:35:50 -04:00
|
|
|
const toolbarButtonsRef = useRef<HTMLSpanElement>(null)
|
2023-10-11 13:36:54 +11:00
|
|
|
const pathId = useMemo(
|
|
|
|
() =>
|
|
|
|
isCursorInSketchCommandRange(
|
|
|
|
engineCommandManager.artifactMap,
|
|
|
|
context.selectionRanges
|
|
|
|
),
|
|
|
|
[engineCommandManager.artifactMap, context.selectionRanges]
|
|
|
|
)
|
2023-10-04 12:35:50 -04:00
|
|
|
|
|
|
|
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
|
|
|
|
const span = toolbarButtonsRef.current
|
|
|
|
if (!span) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
span.scrollLeft = span.scrollLeft += ev.deltaY
|
|
|
|
}
|
2023-02-12 10:56:45 +11:00
|
|
|
|
2023-09-16 01:23:11 -04:00
|
|
|
function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
|
2023-08-31 09:47:59 -04:00
|
|
|
return (
|
2023-10-04 12:35:50 -04:00
|
|
|
<span
|
|
|
|
ref={toolbarButtonsRef}
|
|
|
|
onWheel={handleToolbarButtonsWheelEvent}
|
|
|
|
className={styles.toolbarButtons + ' ' + className}
|
|
|
|
>
|
2023-10-11 13:36:54 +11:00
|
|
|
{state.nextEvents.includes('Enter sketch') && (
|
2023-01-06 12:45:34 +11:00
|
|
|
<button
|
2023-10-11 13:36:54 +11:00
|
|
|
onClick={() => send({ type: 'Enter sketch' })}
|
2023-09-16 01:23:11 -04:00
|
|
|
className="group"
|
2023-01-06 12:45:34 +11:00
|
|
|
>
|
2023-09-16 01:23:11 -04:00
|
|
|
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
2023-11-24 08:59:24 +11:00
|
|
|
<span data-testid="start-sketch">Start Sketch</span>
|
2023-01-06 12:45:34 +11:00
|
|
|
</button>
|
2023-08-31 09:47:59 -04:00
|
|
|
)}
|
2023-10-11 13:36:54 +11:00
|
|
|
{state.nextEvents.includes('Enter sketch') && pathId && (
|
2023-01-06 12:45:34 +11:00
|
|
|
<button
|
2023-10-11 13:36:54 +11:00
|
|
|
onClick={() => send({ type: 'Enter sketch' })}
|
2023-09-16 01:23:11 -04:00
|
|
|
className="group"
|
2023-01-06 12:45:34 +11:00
|
|
|
>
|
2023-09-16 01:23:11 -04:00
|
|
|
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
2023-10-11 13:36:54 +11:00
|
|
|
Edit Sketch
|
2023-01-06 12:45:34 +11:00
|
|
|
</button>
|
2023-08-31 09:47:59 -04:00
|
|
|
)}
|
2023-10-11 13:36:54 +11:00
|
|
|
{state.nextEvents.includes('Cancel') && !state.matches('idle') && (
|
|
|
|
<button onClick={() => send({ type: 'Cancel' })} className="group">
|
|
|
|
<ActionIcon icon="exit" className="!p-0.5" size="md" />
|
|
|
|
Exit Sketch
|
|
|
|
</button>
|
|
|
|
)}
|
|
|
|
{state.matches('Sketch') && !state.matches('idle') && (
|
2023-08-31 09:47:59 -04:00
|
|
|
<button
|
2023-10-11 13:36:54 +11:00
|
|
|
onClick={() =>
|
|
|
|
state.matches('Sketch.Line Tool')
|
|
|
|
? send('CancelSketch')
|
|
|
|
: send('Equip tool')
|
|
|
|
}
|
|
|
|
className={
|
|
|
|
'group ' +
|
|
|
|
(state.matches('Sketch.Line Tool')
|
|
|
|
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
|
|
|
|
: '')
|
|
|
|
}
|
2023-08-31 09:47:59 -04:00
|
|
|
>
|
2023-10-11 13:36:54 +11:00
|
|
|
<ActionIcon icon="line" className="!p-0.5" size="md" />
|
|
|
|
Line
|
2023-08-31 09:47:59 -04:00
|
|
|
</button>
|
|
|
|
)}
|
2023-10-11 13:36:54 +11:00
|
|
|
{state.matches('Sketch') && (
|
2023-09-13 08:36:47 +10:00
|
|
|
<button
|
2023-10-11 13:36:54 +11:00
|
|
|
onClick={() =>
|
|
|
|
state.matches('Sketch.Move Tool')
|
|
|
|
? send('CancelSketch')
|
|
|
|
: send('Equip move tool')
|
|
|
|
}
|
|
|
|
className={
|
|
|
|
'group ' +
|
|
|
|
(state.matches('Sketch.Move Tool')
|
|
|
|
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
|
|
|
|
: '')
|
|
|
|
}
|
2023-09-13 08:36:47 +10:00
|
|
|
>
|
2023-10-11 13:36:54 +11:00
|
|
|
<ActionIcon icon="move" className="!p-0.5" size="md" />
|
|
|
|
Move
|
2023-08-31 09:47:59 -04:00
|
|
|
</button>
|
|
|
|
)}
|
2023-10-11 13:36:54 +11:00
|
|
|
{state.matches('Sketch.SketchIdle') &&
|
|
|
|
state.nextEvents
|
|
|
|
.filter(
|
|
|
|
(eventName) =>
|
|
|
|
eventName.includes('Make segment') ||
|
|
|
|
eventName.includes('Constrain')
|
2023-08-31 09:47:59 -04:00
|
|
|
)
|
2023-12-01 20:49:26 +11:00
|
|
|
.sort((a, b) => {
|
|
|
|
const aisEnabled = state.nextEvents
|
|
|
|
.filter((event) => state.can(event as any))
|
|
|
|
.includes(a)
|
|
|
|
const bIsEnabled = state.nextEvents
|
|
|
|
.filter((event) => state.can(event as any))
|
|
|
|
.includes(b)
|
|
|
|
if (aisEnabled && !bIsEnabled) {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
if (!aisEnabled && bIsEnabled) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
})
|
2023-10-11 13:36:54 +11:00
|
|
|
.map((eventName) => (
|
2023-08-31 09:47:59 -04:00
|
|
|
<button
|
2023-10-11 13:36:54 +11:00
|
|
|
key={eventName}
|
|
|
|
onClick={() => send(eventName)}
|
|
|
|
className="group"
|
|
|
|
disabled={
|
|
|
|
!state.nextEvents
|
|
|
|
.filter((event) => state.can(event as any))
|
|
|
|
.includes(eventName)
|
2023-09-16 01:23:11 -04:00
|
|
|
}
|
2023-10-11 13:36:54 +11:00
|
|
|
title={eventName}
|
2023-08-31 09:47:59 -04:00
|
|
|
>
|
2023-09-16 01:23:11 -04:00
|
|
|
<ActionIcon
|
2023-10-11 13:36:54 +11:00
|
|
|
icon={'line'} // TODO
|
2023-09-16 01:23:11 -04:00
|
|
|
bgClassName={sketchButtonClassnames.background}
|
|
|
|
iconClassName={sketchButtonClassnames.icon}
|
|
|
|
size="md"
|
|
|
|
/>
|
2023-10-11 13:36:54 +11:00
|
|
|
{eventName
|
|
|
|
.replace('Make segment ', '')
|
|
|
|
.replace('Constrain ', '')}
|
2023-08-31 09:47:59 -04:00
|
|
|
</button>
|
2023-10-11 13:36:54 +11:00
|
|
|
))}
|
|
|
|
{state.matches('idle') && (
|
|
|
|
<button
|
|
|
|
onClick={() => send('extrude intent')}
|
|
|
|
disabled={!state.can('extrude intent')}
|
|
|
|
className="group"
|
|
|
|
title={
|
|
|
|
state.can('extrude intent')
|
|
|
|
? 'extrude'
|
|
|
|
: 'sketches need to be closed, or not already extruded'
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
|
|
|
|
Extrude
|
|
|
|
</button>
|
|
|
|
)}
|
2023-09-13 08:36:47 +10:00
|
|
|
</span>
|
2023-08-31 09:47:59 -04:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2023-10-11 13:36:54 +11:00
|
|
|
<Popover
|
|
|
|
className={
|
|
|
|
styles.toolbarWrapper + state.matches('Sketch') ? ' sketch' : ''
|
|
|
|
}
|
|
|
|
>
|
2023-08-31 09:47:59 -04:00
|
|
|
<div className={styles.toolbar}>
|
|
|
|
<span className={styles.toolbarCap + ' ' + styles.label}>
|
2023-10-11 13:36:54 +11:00
|
|
|
{state.matches('Sketch') ? '2D' : '3D'}
|
2023-08-31 09:47:59 -04:00
|
|
|
</span>
|
2023-09-16 01:23:11 -04:00
|
|
|
<menu className="flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
|
2023-08-31 09:47:59 -04:00
|
|
|
<ToolbarButtons />
|
|
|
|
</menu>
|
|
|
|
<Popover.Button
|
|
|
|
className={styles.toolbarCap + ' ' + styles.popoverToggle}
|
|
|
|
>
|
|
|
|
<FontAwesomeIcon icon={faSearch} />
|
|
|
|
</Popover.Button>
|
|
|
|
</div>
|
|
|
|
<Transition
|
|
|
|
as={Fragment}
|
|
|
|
enter="transition ease-out duration-200"
|
|
|
|
enterFrom="opacity-0"
|
|
|
|
enterTo="opacity-100"
|
|
|
|
leave="transition ease-out duration-100"
|
|
|
|
leaveFrom="opacity-100"
|
|
|
|
leaveTo="opacity-0"
|
|
|
|
>
|
|
|
|
<Popover.Overlay className="fixed inset-0 bg-chalkboard-110/20 dark:bg-chalkboard-110/50" />
|
|
|
|
</Transition>
|
|
|
|
<Transition
|
|
|
|
as={Fragment}
|
|
|
|
enter="transition ease-out duration-100"
|
|
|
|
enterFrom="opacity-0 translate-y-1 scale-95"
|
|
|
|
enterTo="opacity-100 translate-y-0 scale-100"
|
|
|
|
leave="transition ease-out duration-75"
|
|
|
|
leaveFrom="opacity-100 translate-y-0"
|
|
|
|
leaveTo="opacity-0 translate-y-2"
|
|
|
|
>
|
|
|
|
<Popover.Panel className="absolute top-0 w-screen max-w-xl left-1/2 -translate-x-1/2 flex flex-col gap-8 bg-chalkboard-10 dark:bg-chalkboard-100 p-5 rounded border border-chalkboard-20/30 dark:border-chalkboard-70/50">
|
|
|
|
<section className="flex justify-between items-center">
|
|
|
|
<p
|
|
|
|
className={`${styles.toolbarCap} ${styles.label} !self-center rounded-r-full w-fit`}
|
|
|
|
>
|
2023-10-11 13:36:54 +11:00
|
|
|
You're in {state.matches('Sketch') ? '2D' : '3D'}
|
2023-08-31 09:47:59 -04:00
|
|
|
</p>
|
|
|
|
<Popover.Button className="p-2 flex items-center justify-center rounded-sm bg-chalkboard-20 text-chalkboard-110 dark:bg-chalkboard-70 dark:text-chalkboard-20 border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-60">
|
|
|
|
<FontAwesomeIcon icon={faX} className="w-4 h-4" />
|
|
|
|
</Popover.Button>
|
|
|
|
</section>
|
|
|
|
<section>
|
2023-09-16 01:23:11 -04:00
|
|
|
<ToolbarButtons className="flex-wrap" />
|
2023-08-31 09:47:59 -04:00
|
|
|
</section>
|
|
|
|
</Popover.Panel>
|
|
|
|
</Transition>
|
|
|
|
</Popover>
|
2022-11-27 14:06:33 +11:00
|
|
|
)
|
|
|
|
}
|