Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Undo/Redo buttons in the viewer toolbar (top-left, alongside the collapse and view-mode controls). The buttons subscribe to the history store via `subscribeHistoryCommandState`, dispatch through `runUndo`/`runRedo` (respecting the collaborative history delegate), and disable when nothing can be undone/redone. Keyboard shortcuts were already wired: Ctrl/Cmd+Z undo, Ctrl/Cmd+Shift+Z redo.

### Fixes

- Preserve custom scene materials across save, load, clone, fork, and live sync. Materials were dropped at every persistence boundary, so a scene reopened with default surfaces. Collections were dropped on MCP import for the same reason ([#597](https://github.com/pascalorg/editor/pull/597)) by [@ShiroKSH](https://github.com/ShiroKSH)
Expand Down
67 changes: 66 additions & 1 deletion apps/editor/components/viewer-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import {
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
getHistoryCommandState,
runRedo,
runUndo,
subscribeHistoryCommandState,
useEditor,
useFloorplanAnnotationVisibility,
useFloorplanMode,
Expand Down Expand Up @@ -37,16 +41,18 @@ import {
Layers3,
Magnet,
PenLine,
Redo2,
Ruler,
ScanLine,
SlidersHorizontal,
Sparkles,
SquareUserRound,
SwatchBook,
Tag,
Undo2,
} from 'lucide-react'
import Image from 'next/image'
import { type ReactNode, useCallback } from 'react'
import { type ReactNode, useCallback, useEffect, useState } from 'react'
import { flushSync } from 'react-dom'
import { cn } from '@/lib/utils'
import { Tooltip, TooltipContent, TooltipTrigger } from './toolbar-tooltip'
Expand Down Expand Up @@ -686,10 +692,69 @@ function PreviewButton() {
)
}

// Undo/Redo toolbar controls. They subscribe to the shared history state
// (`history.ts`) so buttons enable/disable as the scene history grows, and
// route through `runUndo`/`runRedo` so they honor a collaborative host
// delegate when one is installed. Keyboard shortcuts (Ctrl/Cmd+Z and
// Ctrl/Cmd+Shift+Z) are wired separately in `use-keyboard.ts` and land here
// via the same state subscription.
function HistoryControls() {
const [state, setState] = useState(() => getHistoryCommandState())

useEffect(() => {
return subscribeHistoryCommandState(() => setState(getHistoryCommandState()))
}, [])

const undo = () => {
if (!getHistoryCommandState().canUndo) return
runUndo()
}

const redo = () => {
if (!getHistoryCommandState().canRedo) return
runRedo()
}

return (
<div className={TOOLBAR_CONTAINER}>
<ToolbarTooltip label="Undo (Ctrl+Z)">
<button
aria-label="Undo"
aria-disabled={!state.canUndo}
className={cn(
TOOLBAR_BTN,
!state.canUndo && 'cursor-default opacity-40 hover:bg-transparent hover:text-inherit',
)}
onClick={undo}
type="button"
>
<Undo2 className="h-4 w-4" />
</button>
</ToolbarTooltip>
<div className="my-1.5 w-px bg-border/50" />
<ToolbarTooltip label="Redo (Ctrl+Shift+Z)">
<button
aria-label="Redo"
aria-disabled={!state.canRedo}
className={cn(
TOOLBAR_BTN,
!state.canRedo && 'cursor-default opacity-40 hover:bg-transparent hover:text-inherit',
)}
onClick={redo}
type="button"
>
<Redo2 className="h-4 w-4" />
</button>
</ToolbarTooltip>
</div>
)
}

export function CommunityViewerToolbarLeft() {
return (
<>
<CollapseSidebarButton />
<HistoryControls />
<ViewModeControl />
</>
)
Expand Down