Add a system for badges to appear on pane buttons (#3208)
This commit is contained in:
@ -8094,3 +8094,34 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
const sketch002 = extrude(${[5, 5]} + 7, sketch002)`
|
const sketch002 = extrude(${[5, 5]} + 7, sketch002)`
|
||||||
await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
|
await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Typing KCL errors induces a badge on the error logs pane button', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
// Load the app with the working starter code
|
||||||
|
await page.addInitScript((code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, bracket)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// Ensure no badge is present
|
||||||
|
const errorLogsButton = page.getByRole('button', { name: 'KCL Errors pane' })
|
||||||
|
await expect(errorLogsButton).not.toContainText('notification')
|
||||||
|
|
||||||
|
// Delete a character to break the KCL
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await page.getByText('extrude(').click()
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
|
// Ensure that a badge appears on the button
|
||||||
|
await expect(errorLogsButton).toContainText('notification')
|
||||||
|
})
|
||||||
|
@ -14,6 +14,7 @@ import { MemoryPane, MemoryPaneMenu } from './MemoryPane'
|
|||||||
import { KclErrorsPane, LogsPane } from './LoggingPanes'
|
import { KclErrorsPane, LogsPane } from './LoggingPanes'
|
||||||
import { DebugPane } from './DebugPane'
|
import { DebugPane } from './DebugPane'
|
||||||
import { FileTreeInner, FileTreeMenu } from 'components/FileTree'
|
import { FileTreeInner, FileTreeMenu } from 'components/FileTree'
|
||||||
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
|
|
||||||
export type SidebarType =
|
export type SidebarType =
|
||||||
| 'code'
|
| 'code'
|
||||||
@ -25,6 +26,14 @@ export type SidebarType =
|
|||||||
| 'lspMessages'
|
| 'lspMessages'
|
||||||
| 'variables'
|
| 'variables'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface can be extended as more context is needed for the panes
|
||||||
|
* to determine if they should show their badges or not.
|
||||||
|
*/
|
||||||
|
interface PaneCallbackProps {
|
||||||
|
kclContext: ReturnType<typeof useKclContext>
|
||||||
|
}
|
||||||
|
|
||||||
export type SidebarPane = {
|
export type SidebarPane = {
|
||||||
id: SidebarType
|
id: SidebarType
|
||||||
title: string
|
title: string
|
||||||
@ -33,6 +42,7 @@ export type SidebarPane = {
|
|||||||
Content: ReactNode | React.FC
|
Content: ReactNode | React.FC
|
||||||
Menu?: ReactNode | React.FC
|
Menu?: ReactNode | React.FC
|
||||||
hideOnPlatform?: 'desktop' | 'web'
|
hideOnPlatform?: 'desktop' | 'web'
|
||||||
|
showBadge?: (props: PaneCallbackProps) => boolean | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sidebarPanes: SidebarPane[] = [
|
export const sidebarPanes: SidebarPane[] = [
|
||||||
@ -74,6 +84,7 @@ export const sidebarPanes: SidebarPane[] = [
|
|||||||
icon: faExclamationCircle,
|
icon: faExclamationCircle,
|
||||||
Content: KclErrorsPane,
|
Content: KclErrorsPane,
|
||||||
keybinding: 'Shift + E',
|
keybinding: 'Shift + E',
|
||||||
|
showBadge: ({ kclContext }) => kclContext.errors.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'debug',
|
id: 'debug',
|
||||||
|
@ -12,6 +12,7 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
|||||||
import { CustomIconName } from 'components/CustomIcon'
|
import { CustomIconName } from 'components/CustomIcon'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
|
|
||||||
interface ModelingSidebarProps {
|
interface ModelingSidebarProps {
|
||||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||||
@ -19,6 +20,7 @@ interface ModelingSidebarProps {
|
|||||||
|
|
||||||
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||||
const { commandBarSend } = useCommandsContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
|
const kclContext = useKclContext()
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const onboardingStatus = settings.context.app.onboardingStatus
|
const onboardingStatus = settings.context.app.onboardingStatus
|
||||||
const { send, context } = useModelingContext()
|
const { send, context } = useModelingContext()
|
||||||
@ -62,6 +64,15 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
[sidebarPanes, showDebugPanel.current]
|
[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(
|
const togglePane = useCallback(
|
||||||
(newPane: SidebarType) => {
|
(newPane: SidebarType) => {
|
||||||
send({
|
send({
|
||||||
@ -120,6 +131,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
paneIsOpen={context.store?.openPanes.includes(pane.id)}
|
paneIsOpen={context.store?.openPanes.includes(pane.id)}
|
||||||
onClick={() => togglePane(pane.id)}
|
onClick={() => togglePane(pane.id)}
|
||||||
aria-pressed={context.store?.openPanes.includes(pane.id)}
|
aria-pressed={context.store?.openPanes.includes(pane.id)}
|
||||||
|
showBadge={paneBadgeMap[pane.id]}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@ -186,12 +198,14 @@ interface ModelingPaneButtonProps
|
|||||||
}
|
}
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
paneIsOpen?: boolean
|
paneIsOpen?: boolean
|
||||||
|
showBadge?: boolean | number
|
||||||
}
|
}
|
||||||
|
|
||||||
function ModelingPaneButton({
|
function ModelingPaneButton({
|
||||||
paneConfig,
|
paneConfig,
|
||||||
onClick,
|
onClick,
|
||||||
paneIsOpen,
|
paneIsOpen,
|
||||||
|
showBadge,
|
||||||
...props
|
...props
|
||||||
}: ModelingPaneButtonProps) {
|
}: ModelingPaneButtonProps) {
|
||||||
useHotkeys(paneConfig.keybinding, onClick, {
|
useHotkeys(paneConfig.keybinding, onClick, {
|
||||||
@ -223,6 +237,23 @@ function ModelingPaneButton({
|
|||||||
{paneConfig.title}
|
{paneConfig.title}
|
||||||
{paneIsOpen !== undefined ? ` pane` : ''}
|
{paneIsOpen !== undefined ? ` pane` : ''}
|
||||||
</span>
|
</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
|
<Tooltip
|
||||||
position="right"
|
position="right"
|
||||||
contentClassName="max-w-none flex items-center gap-4"
|
contentClassName="max-w-none flex items-center gap-4"
|
||||||
|
Reference in New Issue
Block a user