* add print button Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * generate more types Signed-off-by: Jess Frazelle <github@jessfraz.com> * add a github action to generate machine api-types Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix Signed-off-by: Jess Frazelle <github@jessfraz.com> * New machine-api types * actually print on the real machine Signed-off-by: Jess Frazelle <github@jessfraz.com> * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * add more Signed-off-by: Jess Frazelle <github@jessfraz.com> * New machine-api types * get the current machine Signed-off-by: Jess Frazelle <github@jessfraz.com> * New machine-api types * know when error Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * add fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * New machine-api types * empty * empty * update machine api Signed-off-by: Jess Frazelle <github@jessfraz.com> * New machine-api types * empty * New machine-api types * emptuy * no circular deps Signed-off-by: Jess Frazelle <github@jessfraz.com> * New machine-api types * remove recursive dep Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
313 lines
10 KiB
TypeScript
313 lines
10 KiB
TypeScript
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|
import { Resizable } from 're-resizable'
|
|
import { useCallback, useMemo } from 'react'
|
|
import { useHotkeys } from 'react-hotkeys-hook'
|
|
import { SidebarType, sidebarPanes } from './ModelingPanes'
|
|
import Tooltip from 'components/Tooltip'
|
|
import { ActionIcon } from 'components/ActionIcon'
|
|
import styles from './ModelingSidebar.module.css'
|
|
import { ModelingPane } from './ModelingPane'
|
|
import { isTauri } from 'lib/isTauri'
|
|
import { useModelingContext } from 'hooks/useModelingContext'
|
|
import { CustomIconName } from 'components/CustomIcon'
|
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
|
import { useKclContext } from 'lang/KclProvider'
|
|
import { machineManager } from 'lib/machineManager'
|
|
|
|
interface ModelingSidebarProps {
|
|
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
|
}
|
|
|
|
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|
const { commandBarSend } = useCommandsContext()
|
|
const kclContext = useKclContext()
|
|
const { settings } = useSettingsAuthContext()
|
|
const onboardingStatus = settings.context.app.onboardingStatus
|
|
const { send, context } = useModelingContext()
|
|
const pointerEventsCssClass =
|
|
context.store?.buttonDownInStream ||
|
|
onboardingStatus.current === 'camera' ||
|
|
context.store?.openPanes.length === 0
|
|
? 'pointer-events-none '
|
|
: 'pointer-events-auto '
|
|
const showDebugPanel = settings.context.modeling.showDebugPanel
|
|
|
|
const sidebarActions: SidebarAction[] = [
|
|
{
|
|
id: 'export',
|
|
title: 'Export part',
|
|
icon: 'floppyDiskArrow',
|
|
iconClassName: '!p-0',
|
|
keybinding: 'Ctrl + Shift + E',
|
|
action: () =>
|
|
commandBarSend({
|
|
type: 'Find and select command',
|
|
data: { name: 'Export', groupId: 'modeling' },
|
|
}),
|
|
},
|
|
{
|
|
id: 'make',
|
|
title: 'Make part',
|
|
icon: 'printer3d',
|
|
iconClassName: '!p-0',
|
|
keybinding: 'Ctrl + Shift + M',
|
|
action: async () => {
|
|
commandBarSend({
|
|
type: 'Find and select command',
|
|
data: { name: 'Make', groupId: 'modeling' },
|
|
})
|
|
},
|
|
hide: () => machineManager.machineCount() === 0,
|
|
hideOnPlatform: 'web',
|
|
},
|
|
]
|
|
const filteredActions: SidebarAction[] = sidebarActions.filter(
|
|
(action) =>
|
|
(!action.hide || (action.hide instanceof Function && !action.hide())) &&
|
|
(!action.hideOnPlatform ||
|
|
(isTauri()
|
|
? action.hideOnPlatform === 'web'
|
|
: action.hideOnPlatform === 'desktop'))
|
|
)
|
|
|
|
// // Filter out the debug panel if it's not supposed to be shown
|
|
// // TODO: abstract out for allowing user to configure which panes to show
|
|
const filteredPanes = useMemo(
|
|
() =>
|
|
(showDebugPanel.current
|
|
? sidebarPanes
|
|
: sidebarPanes.filter((pane) => pane.id !== 'debug')
|
|
).filter(
|
|
(pane) =>
|
|
!pane.hideOnPlatform ||
|
|
(isTauri()
|
|
? pane.hideOnPlatform === 'web'
|
|
: pane.hideOnPlatform === 'desktop')
|
|
),
|
|
[sidebarPanes, showDebugPanel.current]
|
|
)
|
|
|
|
const paneBadgeMap: Record<SidebarType, number | boolean> = useMemo(() => {
|
|
return filteredPanes.reduce((acc, pane) => {
|
|
if (pane.showBadge) {
|
|
acc[pane.id] = pane.showBadge({ kclContext })
|
|
}
|
|
return acc
|
|
}, {} as Record<SidebarType, number | boolean>)
|
|
}, [kclContext.errors])
|
|
|
|
const togglePane = useCallback(
|
|
(newPane: SidebarType) => {
|
|
send({
|
|
type: 'Set context',
|
|
data: {
|
|
openPanes: context.store?.openPanes.includes(newPane)
|
|
? context.store?.openPanes.filter((pane) => pane !== newPane)
|
|
: [...context.store?.openPanes, newPane],
|
|
},
|
|
})
|
|
},
|
|
[context.store?.openPanes, send]
|
|
)
|
|
|
|
return (
|
|
<Resizable
|
|
className={`group flex-1 flex flex-col z-10 my-2 pr-1 ${paneOpacity} ${pointerEventsCssClass}`}
|
|
defaultSize={{
|
|
width: '550px',
|
|
height: 'auto',
|
|
}}
|
|
minWidth={200}
|
|
maxWidth={800}
|
|
handleClasses={{
|
|
right:
|
|
(context.store?.openPanes.length === 0 ? 'hidden ' : 'block ') +
|
|
'translate-x-1/2 hover:bg-chalkboard-10 hover:dark:bg-chalkboard-110 bg-transparent transition-colors duration-75 transition-ease-out delay-100 ',
|
|
left: 'hidden',
|
|
top: 'hidden',
|
|
topLeft: 'hidden',
|
|
topRight: 'hidden',
|
|
bottom: 'hidden',
|
|
bottomLeft: 'hidden',
|
|
bottomRight: 'hidden',
|
|
}}
|
|
>
|
|
<div id="app-sidebar" className={styles.grid + ' flex-1'}>
|
|
<ul
|
|
className={
|
|
(context.store?.openPanes.length === 0 ? 'rounded-r ' : '') +
|
|
'relative z-[2] pointer-events-auto p-0 col-start-1 col-span-1 h-fit w-fit flex flex-col ' +
|
|
'bg-chalkboard-10 border border-solid border-chalkboard-20 dark:bg-chalkboard-90 dark:border-chalkboard-80 group-focus-within:border-primary dark:group-focus-within:border-chalkboard-50 '
|
|
}
|
|
>
|
|
<ul
|
|
id="pane-buttons-section"
|
|
className={
|
|
'w-fit p-2 flex flex-col gap-2 ' +
|
|
(context.store?.openPanes.length >= 1 ? 'pr-0.5' : '')
|
|
}
|
|
>
|
|
{filteredPanes.map((pane) => (
|
|
<ModelingPaneButton
|
|
key={pane.id}
|
|
paneConfig={pane}
|
|
paneIsOpen={context.store?.openPanes.includes(pane.id)}
|
|
onClick={() => togglePane(pane.id)}
|
|
aria-pressed={context.store?.openPanes.includes(pane.id)}
|
|
showBadge={paneBadgeMap[pane.id]}
|
|
/>
|
|
))}
|
|
</ul>
|
|
{filteredActions.length > 0 && (
|
|
<>
|
|
<hr className="w-full border-chalkboard-20 dark:border-chalkboard-80" />
|
|
<ul
|
|
id="sidebar-actions"
|
|
className="w-fit p-2 flex flex-col gap-2"
|
|
>
|
|
{filteredActions.map((action) => (
|
|
<ModelingPaneButton
|
|
key={action.id}
|
|
paneConfig={{
|
|
id: action.id,
|
|
title: action.title,
|
|
icon: action.icon,
|
|
keybinding: action.keybinding,
|
|
iconClassName: action.iconClassName,
|
|
iconSize: 'md',
|
|
}}
|
|
onClick={action.action}
|
|
/>
|
|
))}
|
|
</ul>
|
|
</>
|
|
)}
|
|
</ul>
|
|
<ul
|
|
id="pane-section"
|
|
className={
|
|
'ml-[-1px] col-start-2 col-span-1 flex flex-col gap-2 ' +
|
|
(context.store?.openPanes.length >= 1
|
|
? `row-start-1 row-end-3`
|
|
: `hidden`)
|
|
}
|
|
>
|
|
{filteredPanes
|
|
.filter((pane) => context?.store.openPanes.includes(pane.id))
|
|
.map((pane) => (
|
|
<ModelingPane
|
|
key={pane.id}
|
|
icon={pane.icon}
|
|
id={`${pane.id}-pane`}
|
|
title={pane.title}
|
|
Menu={pane.Menu}
|
|
onClose={() => togglePane(pane.id)}
|
|
>
|
|
{pane.Content instanceof Function ? (
|
|
<pane.Content />
|
|
) : (
|
|
pane.Content
|
|
)}
|
|
</ModelingPane>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</Resizable>
|
|
)
|
|
}
|
|
|
|
interface ModelingPaneButtonProps
|
|
extends React.HTMLAttributes<HTMLButtonElement> {
|
|
paneConfig: {
|
|
id: string
|
|
title: string
|
|
icon: CustomIconName | IconDefinition
|
|
keybinding: string
|
|
iconClassName?: string
|
|
iconSize?: 'sm' | 'md' | 'lg'
|
|
}
|
|
onClick: () => void
|
|
paneIsOpen?: boolean
|
|
showBadge?: boolean | number
|
|
}
|
|
|
|
function ModelingPaneButton({
|
|
paneConfig,
|
|
onClick,
|
|
paneIsOpen,
|
|
showBadge,
|
|
...props
|
|
}: ModelingPaneButtonProps) {
|
|
useHotkeys(paneConfig.keybinding, onClick, {
|
|
scopes: ['modeling'],
|
|
})
|
|
|
|
return (
|
|
<button
|
|
className="pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary"
|
|
onClick={onClick}
|
|
name={paneConfig.title}
|
|
data-testid={paneConfig.id + '-pane-button'}
|
|
{...props}
|
|
>
|
|
<ActionIcon
|
|
icon={paneConfig.icon}
|
|
className={'p-1 ' + paneConfig.iconClassName || ''}
|
|
size={paneConfig.iconSize || 'sm'}
|
|
iconClassName={
|
|
paneIsOpen
|
|
? ' !text-chalkboard-10'
|
|
: '!text-chalkboard-80 dark:!text-chalkboard-30'
|
|
}
|
|
bgClassName={
|
|
'rounded-sm ' + (paneIsOpen ? '!bg-primary' : '!bg-transparent')
|
|
}
|
|
/>
|
|
<span className="sr-only">
|
|
{paneConfig.title}
|
|
{paneIsOpen !== undefined ? ` pane` : ''}
|
|
</span>
|
|
{!!showBadge && (
|
|
<p
|
|
className={
|
|
'absolute m-0 p-0 -top-1 -right-1 w-3 h-3 flex items-center justify-center text-[10px] font-semibold text-white bg-primary hue-rotate-90 rounded-full border border-chalkboard-10 dark:border-chalkboard-80'
|
|
}
|
|
>
|
|
<span className="sr-only"> has </span>
|
|
{typeof showBadge === 'number' ? (
|
|
<span>{showBadge}</span>
|
|
) : (
|
|
<span className="sr-only">a</span>
|
|
)}
|
|
<span className="sr-only">
|
|
notification{Number(showBadge) > 1 ? 's' : ''}
|
|
</span>
|
|
</p>
|
|
)}
|
|
<Tooltip
|
|
position="right"
|
|
contentClassName="max-w-none flex items-center gap-4"
|
|
hoverOnly
|
|
>
|
|
<span className="flex-1">
|
|
{paneConfig.title}
|
|
{paneIsOpen !== undefined ? ` pane` : ''}
|
|
</span>
|
|
<kbd className="hotkey text-xs capitalize">{paneConfig.keybinding}</kbd>
|
|
</Tooltip>
|
|
</button>
|
|
)
|
|
}
|
|
|
|
export type SidebarAction = {
|
|
id: string
|
|
title: string
|
|
icon: CustomIconName
|
|
iconClassName?: string // Just until we get rid of FontAwesome icons
|
|
keybinding: string
|
|
action: () => void
|
|
hideOnPlatform?: 'desktop' | 'web'
|
|
hide?: boolean | (() => boolean)
|
|
}
|