Franknoirot/expandable toolbar (#343)
* Add basic Popover functionality * Fix up light mode of basic bar * Add support for 2D and 3D mode styling * Turn toolbar buttons back on * Remove ActionButton until after tool logic refactor * Add transitions * Add styles to always center toolbar in header
This commit is contained in:
60
src/Toolbar.module.css
Normal file
60
src/Toolbar.module.css
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
.toolbarWrapper {
|
||||||
|
@apply relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
@apply flex gap-4 items-center rounded-full;
|
||||||
|
@apply border border-cool-20/30 bg-cool-10/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .toolbar {
|
||||||
|
@apply border-cool-100/50 bg-cool-120/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.sketch) .toolbar {
|
||||||
|
@apply border-fern-20/20 bg-fern-10/20;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark .sketch) .toolbar {
|
||||||
|
@apply border-fern-120/50 bg-fern-100/30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarCap {
|
||||||
|
@apply text-sm font-bold;
|
||||||
|
@apply bg-cool-20/50 text-cool-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .toolbarCap {
|
||||||
|
@apply bg-cool-90/50 text-cool-30;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.sketch) .toolbarCap {
|
||||||
|
@apply bg-fern-20/50 text-fern-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark .sketch) .toolbarCap {
|
||||||
|
@apply bg-fern-90/50 text-fern-30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
@apply self-stretch flex items-center px-4 py-1;
|
||||||
|
@apply rounded-l-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popoverToggle {
|
||||||
|
@apply self-stretch m-0 flex items-center px-4 py-1;
|
||||||
|
@apply rounded-r-full border-none;
|
||||||
|
@apply hover:bg-cool-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .popoverToggle {
|
||||||
|
@apply hover:bg-cool-90;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.sketch) .popoverToggle {
|
||||||
|
@apply hover:bg-fern-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark .sketch) .popoverToggle {
|
||||||
|
@apply hover:bg-fern-90;
|
||||||
|
}
|
310
src/Toolbar.tsx
310
src/Toolbar.tsx
@ -11,6 +11,11 @@ import { SetAngleLength } from './components/Toolbar/setAngleLength'
|
|||||||
import { ConvertToVariable } from './components/Toolbar/ConvertVariable'
|
import { ConvertToVariable } from './components/Toolbar/ConvertVariable'
|
||||||
import { SetAbsDistance } from './components/Toolbar/SetAbsDistance'
|
import { SetAbsDistance } from './components/Toolbar/SetAbsDistance'
|
||||||
import { SetAngleBetween } from './components/Toolbar/SetAngleBetween'
|
import { SetAngleBetween } from './components/Toolbar/SetAngleBetween'
|
||||||
|
import { Fragment, useEffect } from 'react'
|
||||||
|
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'
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const {
|
const {
|
||||||
@ -29,72 +34,26 @@ export const Toolbar = () => {
|
|||||||
programMemory: s.programMemory,
|
programMemory: s.programMemory,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<div>
|
console.log('guiMode', guiMode)
|
||||||
{guiMode.mode === 'default' && (
|
}, [guiMode])
|
||||||
<button
|
|
||||||
onClick={() => {
|
function ToolbarButtons() {
|
||||||
setGuiMode({
|
return (
|
||||||
mode: 'sketch',
|
<>
|
||||||
sketchMode: 'selectFace',
|
{guiMode.mode === 'default' && (
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Start Sketch
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{guiMode.mode === 'canEditExtrude' && (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
if (!ast) return
|
|
||||||
const pathToNode = getNodePathFromSourceRange(
|
|
||||||
ast,
|
|
||||||
selectionRanges.codeBasedSelections[0].range
|
|
||||||
)
|
|
||||||
const { modifiedAst } = sketchOnExtrudedFace(
|
|
||||||
ast,
|
|
||||||
pathToNode,
|
|
||||||
programMemory
|
|
||||||
)
|
|
||||||
updateAst(modifiedAst)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
SketchOnFace
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{(guiMode.mode === 'canEditSketch' || false) && (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setGuiMode({
|
|
||||||
mode: 'sketch',
|
|
||||||
sketchMode: 'sketchEdit',
|
|
||||||
pathToNode: guiMode.pathToNode,
|
|
||||||
rotation: guiMode.rotation,
|
|
||||||
position: guiMode.position,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Edit Sketch
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{guiMode.mode === 'canEditSketch' && (
|
|
||||||
<>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!ast) return
|
setGuiMode({
|
||||||
const pathToNode = getNodePathFromSourceRange(
|
mode: 'sketch',
|
||||||
ast,
|
sketchMode: 'selectFace',
|
||||||
selectionRanges.codeBasedSelections[0].range
|
})
|
||||||
)
|
|
||||||
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
|
|
||||||
ast,
|
|
||||||
pathToNode
|
|
||||||
)
|
|
||||||
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
ExtrudeSketch
|
Start Sketch
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
|
{guiMode.mode === 'canEditExtrude' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!ast) return
|
if (!ast) return
|
||||||
@ -102,77 +61,182 @@ export const Toolbar = () => {
|
|||||||
ast,
|
ast,
|
||||||
selectionRanges.codeBasedSelections[0].range
|
selectionRanges.codeBasedSelections[0].range
|
||||||
)
|
)
|
||||||
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
|
const { modifiedAst } = sketchOnExtrudedFace(
|
||||||
ast,
|
ast,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
false
|
programMemory
|
||||||
)
|
)
|
||||||
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
|
updateAst(modifiedAst)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
ExtrudeSketch (w/o pipe)
|
SketchOnFace
|
||||||
</button>
|
</button>
|
||||||
</>
|
)}
|
||||||
)}
|
{(guiMode.mode === 'canEditSketch' || false) && (
|
||||||
|
<button
|
||||||
{guiMode.mode === 'sketch' && (
|
onClick={() => {
|
||||||
<button onClick={() => setGuiMode({ mode: 'default' })}>
|
setGuiMode({
|
||||||
Exit sketch
|
mode: 'sketch',
|
||||||
</button>
|
sketchMode: 'sketchEdit',
|
||||||
)}
|
pathToNode: guiMode.pathToNode,
|
||||||
{toolTips
|
rotation: guiMode.rotation,
|
||||||
.filter(
|
position: guiMode.position,
|
||||||
// (sketchFnName) => !['angledLineThatIntersects'].includes(sketchFnName)
|
})
|
||||||
(sketchFnName) => ['line'].includes(sketchFnName)
|
}}
|
||||||
)
|
>
|
||||||
.map((sketchFnName) => {
|
Edit Sketch
|
||||||
if (
|
</button>
|
||||||
guiMode.mode !== 'sketch' ||
|
)}
|
||||||
!('isTooltip' in guiMode || guiMode.sketchMode === 'sketchEdit')
|
{guiMode.mode === 'canEditSketch' && (
|
||||||
)
|
<>
|
||||||
return null
|
|
||||||
return (
|
|
||||||
<button
|
<button
|
||||||
key={sketchFnName}
|
onClick={() => {
|
||||||
onClick={() =>
|
if (!ast) return
|
||||||
setGuiMode({
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
...guiMode,
|
ast,
|
||||||
...(guiMode.sketchMode === sketchFnName
|
selectionRanges.codeBasedSelections[0].range
|
||||||
? {
|
)
|
||||||
sketchMode: 'sketchEdit',
|
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
|
||||||
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
ast,
|
||||||
}
|
pathToNode
|
||||||
: {
|
)
|
||||||
sketchMode: sketchFnName,
|
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
|
||||||
isTooltip: true,
|
}}
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{sketchFnName}
|
ExtrudeSketch
|
||||||
{guiMode.sketchMode === sketchFnName && '✅'}
|
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (!ast) return
|
||||||
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
|
ast,
|
||||||
|
selectionRanges.codeBasedSelections[0].range
|
||||||
|
)
|
||||||
|
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
|
||||||
|
ast,
|
||||||
|
pathToNode,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ExtrudeSketch (w/o pipe)
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{guiMode.mode === 'sketch' && (
|
||||||
|
<button onClick={() => setGuiMode({ mode: 'default' })}>
|
||||||
|
Exit sketch
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{toolTips
|
||||||
|
.filter(
|
||||||
|
// (sketchFnName) => !['angledLineThatIntersects'].includes(sketchFnName)
|
||||||
|
(sketchFnName) => ['line'].includes(sketchFnName)
|
||||||
)
|
)
|
||||||
})}
|
.map((sketchFnName) => {
|
||||||
<br></br>
|
if (
|
||||||
<ConvertToVariable />
|
guiMode.mode !== 'sketch' ||
|
||||||
<HorzVert horOrVert="horizontal" />
|
!('isTooltip' in guiMode || guiMode.sketchMode === 'sketchEdit')
|
||||||
<HorzVert horOrVert="vertical" />
|
)
|
||||||
<EqualLength />
|
return null
|
||||||
<EqualAngle />
|
return (
|
||||||
<SetHorzVertDistance buttonType="alignEndsVertically" />
|
<button
|
||||||
<SetHorzVertDistance buttonType="setHorzDistance" />
|
key={sketchFnName}
|
||||||
<SetAbsDistance buttonType="snapToYAxis" />
|
onClick={() =>
|
||||||
<SetAbsDistance buttonType="xAbs" />
|
setGuiMode({
|
||||||
<SetHorzVertDistance buttonType="alignEndsHorizontally" />
|
...guiMode,
|
||||||
<SetAbsDistance buttonType="snapToXAxis" />
|
...(guiMode.sketchMode === sketchFnName
|
||||||
<SetHorzVertDistance buttonType="setVertDistance" />
|
? {
|
||||||
<SetAbsDistance buttonType="yAbs" />
|
sketchMode: 'sketchEdit',
|
||||||
<SetAngleLength angleOrLength="setAngle" />
|
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
||||||
<SetAngleLength angleOrLength="setLength" />
|
}
|
||||||
<Intersect />
|
: {
|
||||||
<RemoveConstrainingValues />
|
sketchMode: sketchFnName,
|
||||||
<SetAngleBetween />
|
isTooltip: true,
|
||||||
</div>
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{sketchFnName}
|
||||||
|
{guiMode.sketchMode === sketchFnName && '✅'}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<ConvertToVariable />
|
||||||
|
<HorzVert horOrVert="horizontal" />
|
||||||
|
<HorzVert horOrVert="vertical" />
|
||||||
|
<EqualLength />
|
||||||
|
<EqualAngle />
|
||||||
|
<SetHorzVertDistance buttonType="alignEndsVertically" />
|
||||||
|
<SetHorzVertDistance buttonType="setHorzDistance" />
|
||||||
|
<SetAbsDistance buttonType="snapToYAxis" />
|
||||||
|
<SetAbsDistance buttonType="xAbs" />
|
||||||
|
<SetHorzVertDistance buttonType="alignEndsHorizontally" />
|
||||||
|
<SetAbsDistance buttonType="snapToXAxis" />
|
||||||
|
<SetHorzVertDistance buttonType="setVertDistance" />
|
||||||
|
<SetAbsDistance buttonType="yAbs" />
|
||||||
|
<SetAngleLength angleOrLength="setAngle" />
|
||||||
|
<SetAngleLength angleOrLength="setLength" />
|
||||||
|
<Intersect />
|
||||||
|
<RemoveConstrainingValues />
|
||||||
|
<SetAngleBetween />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover className={styles.toolbarWrapper + ' ' + guiMode.mode}>
|
||||||
|
<div className={styles.toolbar}>
|
||||||
|
<span className={styles.toolbarCap + ' ' + styles.label}>
|
||||||
|
{guiMode.mode === 'sketch' ? '2D' : '3D'}
|
||||||
|
</span>
|
||||||
|
<menu className="flex flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
|
||||||
|
<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`}
|
||||||
|
>
|
||||||
|
You're in {guiMode.mode === 'sketch' ? '2D' : '3D'}
|
||||||
|
</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>
|
||||||
|
<ToolbarButtons />
|
||||||
|
</section>
|
||||||
|
</Popover.Panel>
|
||||||
|
</Transition>
|
||||||
|
</Popover>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
8
src/components/AppHeader.module.css
Normal file
8
src/components/AppHeader.module.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Some CSS cannot be represented
|
||||||
|
in Tailwind, such as complex grid layouts.
|
||||||
|
*/
|
||||||
|
.header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
}
|
@ -3,6 +3,7 @@ import UserSidebarMenu from './UserSidebarMenu'
|
|||||||
import { ProjectWithEntryPointMetadata } from '../Router'
|
import { ProjectWithEntryPointMetadata } from '../Router'
|
||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
import styles from './AppHeader.module.css'
|
||||||
|
|
||||||
interface AppHeaderProps extends React.PropsWithChildren {
|
interface AppHeaderProps extends React.PropsWithChildren {
|
||||||
showToolbar?: boolean
|
showToolbar?: boolean
|
||||||
@ -27,7 +28,8 @@ export const AppHeader = ({
|
|||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={
|
className={
|
||||||
'overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/70 backdrop-blur-sm dark:bg-chalkboard-100/50 dark:backdrop-blur-0 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 flex justify-between items-center ' +
|
styles.header +
|
||||||
|
' overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/70 backdrop-blur-sm dark:bg-chalkboard-100/50 dark:backdrop-blur-0 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' +
|
||||||
className
|
className
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -39,7 +41,11 @@ export const AppHeader = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* If there are children, show them, otherwise show User menu */}
|
{/* If there are children, show them, otherwise show User menu */}
|
||||||
{children || <UserSidebarMenu user={user} />}
|
{children || (
|
||||||
|
<div className="ml-auto">
|
||||||
|
<UserSidebarMenu user={user} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user