fix: sync whiteboard theme with app mode
This commit is contained in:
@@ -596,6 +596,7 @@ Defined in `src/utils/durableMarkdownBlocks.ts`, `src/utils/editorDurableMarkdow
|
||||
- Each `tldrawBlock` stores a stable `boardId` plus the tldraw document snapshot JSON. Session state such as camera, selected tool, and current selection is not persisted into the note.
|
||||
- The rich editor renders the block with the `tldraw` package and saves debounced document snapshot changes back into the block props, so normal Tolaria autosave writes the board into the `.md` file.
|
||||
- Whiteboard prop writes re-resolve the live BlockNote block by id before mutating it, and disappear as no-ops if a note reload or mode switch has already removed that block.
|
||||
- The tldraw runtime receives Tolaria's resolved light/dark mode as its user color scheme, so embedded whiteboards follow the app appearance and update while mounted.
|
||||
- Mermaid and tldraw both register small codecs with the shared durable fenced-block pipeline; scanner, token, block injection, and mixed serialization mechanics live in one owner.
|
||||
- The `/whiteboard` slash command inserts an empty tldraw block using the same Markdown-durable storage path. Preview images are intentionally omitted; thumbnails can be added later as derived cache artifacts.
|
||||
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { act, cleanup, render, screen, waitFor } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { Editor } from 'tldraw'
|
||||
import { TldrawWhiteboard } from './TldrawWhiteboard'
|
||||
|
||||
interface MockTldrawProps {
|
||||
assetUrls: MockAssetUrls
|
||||
onMount: (editor: Editor) => () => void
|
||||
user?: MockTldrawUser
|
||||
}
|
||||
|
||||
interface MockTldrawUser {
|
||||
userPreferences: {
|
||||
get: () => { colorScheme?: string }
|
||||
}
|
||||
}
|
||||
|
||||
interface MockAssetUrls {
|
||||
@@ -64,8 +71,18 @@ vi.mock('tldraw', async () => {
|
||||
createTLStore: vi.fn(() => ({
|
||||
listen: vi.fn(() => vi.fn()),
|
||||
})),
|
||||
defaultUserPreferences: {
|
||||
colorScheme: 'light',
|
||||
locale: 'en',
|
||||
},
|
||||
getSnapshot: vi.fn(() => ({ document: {} })),
|
||||
loadSnapshot: vi.fn(),
|
||||
useTldrawUser: vi.fn(({ userPreferences }: { userPreferences: { colorScheme: string } }) => ({
|
||||
setUserPreferences: vi.fn(),
|
||||
userPreferences: {
|
||||
get: () => userPreferences,
|
||||
},
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -76,7 +93,7 @@ function renderedTldrawAssetUrls(): MockAssetUrls {
|
||||
}
|
||||
|
||||
function renderedTldrawProps(): MockTldrawProps {
|
||||
const props = tldrawMock.Tldraw.mock.calls[0]?.[0] as MockTldrawProps
|
||||
const props = tldrawMock.Tldraw.mock.lastCall?.[0] as MockTldrawProps
|
||||
expect(props).toBeDefined()
|
||||
return props
|
||||
}
|
||||
@@ -125,6 +142,13 @@ function expectBundledTldrawAssetUrls(assetUrls: MockAssetUrls) {
|
||||
}
|
||||
|
||||
describe('TldrawWhiteboard', () => {
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
document.documentElement.removeAttribute('data-theme')
|
||||
document.documentElement.classList.remove('dark')
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('uses bundled tldraw assets instead of CDN URLs', () => {
|
||||
render(
|
||||
<TldrawWhiteboard
|
||||
@@ -142,6 +166,50 @@ describe('TldrawWhiteboard', () => {
|
||||
expectBundledTldrawAssetUrls(renderedTldrawAssetUrls())
|
||||
})
|
||||
|
||||
it('passes Tolaria dark mode to tldraw', () => {
|
||||
document.documentElement.setAttribute('data-theme', 'dark')
|
||||
document.documentElement.classList.add('dark')
|
||||
|
||||
render(
|
||||
<TldrawWhiteboard
|
||||
boardId="board-1"
|
||||
height="520"
|
||||
snapshot=""
|
||||
width=""
|
||||
onSizeChange={vi.fn()}
|
||||
onSnapshotChange={vi.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(renderedTldrawProps().user?.userPreferences.get().colorScheme).toBe('dark')
|
||||
})
|
||||
|
||||
it('updates the tldraw color scheme when Tolaria theme changes', async () => {
|
||||
document.documentElement.setAttribute('data-theme', 'light')
|
||||
|
||||
render(
|
||||
<TldrawWhiteboard
|
||||
boardId="board-1"
|
||||
height="520"
|
||||
snapshot=""
|
||||
width=""
|
||||
onSizeChange={vi.fn()}
|
||||
onSnapshotChange={vi.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(renderedTldrawProps().user?.userPreferences.get().colorScheme).toBe('light')
|
||||
|
||||
act(() => {
|
||||
document.documentElement.setAttribute('data-theme', 'dark')
|
||||
document.documentElement.classList.add('dark')
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderedTldrawProps().user?.userPreferences.get().colorScheme).toBe('dark')
|
||||
})
|
||||
})
|
||||
|
||||
it('installs the text measurement guard when the canvas mounts', () => {
|
||||
render(
|
||||
<TldrawWhiteboard
|
||||
|
||||
@@ -9,20 +9,26 @@ import {
|
||||
TldrawUiMenuContextProvider,
|
||||
Tldraw,
|
||||
createTLStore,
|
||||
defaultUserPreferences,
|
||||
getSnapshot,
|
||||
loadSnapshot,
|
||||
react as reactToTldrawSignal,
|
||||
useDialogs,
|
||||
useTldrawUser,
|
||||
useTranslation,
|
||||
type Editor,
|
||||
type TLEventInfo,
|
||||
type TLUiDialog,
|
||||
type TLStoreSnapshot,
|
||||
type TLUserPreferences,
|
||||
} from 'tldraw'
|
||||
import 'tldraw/tldraw.css'
|
||||
import { useDocumentThemeMode } from '../hooks/useDocumentThemeMode'
|
||||
import type { ResolvedThemeMode } from '../lib/themeMode'
|
||||
import { installTldrawTextMeasurementGuard } from './tldrawTextMeasurementGuard'
|
||||
|
||||
const EMPTY_TLDRAW_TRANSLATION_URL = 'data:application/json;base64,e30K'
|
||||
const TOLARIA_TLDRAW_USER_ID = 'tolaria-whiteboard'
|
||||
|
||||
function resolveTldrawAssetUrl(assetUrl: string | undefined): string {
|
||||
return assetUrl ?? EMPTY_TLDRAW_TRANSLATION_URL
|
||||
@@ -89,6 +95,18 @@ function cssSize({ height, width }: PixelSize): CSSProperties {
|
||||
} as CSSProperties
|
||||
}
|
||||
|
||||
function tldrawUserPreferences(themeMode: ResolvedThemeMode): TLUserPreferences {
|
||||
return {
|
||||
...defaultUserPreferences,
|
||||
id: TOLARIA_TLDRAW_USER_ID,
|
||||
colorScheme: themeMode,
|
||||
}
|
||||
}
|
||||
|
||||
function ignoreTldrawUserPreferencesUpdate(preferences: TLUserPreferences) {
|
||||
void preferences
|
||||
}
|
||||
|
||||
function parseSnapshot(source: string): TLStoreSnapshot | null {
|
||||
if (!source.trim()) return null
|
||||
|
||||
@@ -387,6 +405,12 @@ export function TldrawWhiteboard({
|
||||
const persistedSize = useMemo(() => normalizeSize({ height, width }), [height, width])
|
||||
const [resizingSize, setResizingSize] = useState<PixelSize | null>(null)
|
||||
const visibleSize = resizingSize ?? persistedSize
|
||||
const themeMode = useDocumentThemeMode()
|
||||
const userPreferences = useMemo(() => tldrawUserPreferences(themeMode), [themeMode])
|
||||
const tldrawUser = useTldrawUser({
|
||||
setUserPreferences: ignoreTldrawUserPreferencesUpdate,
|
||||
userPreferences,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
onSnapshotChangeRef.current = onSnapshotChange
|
||||
@@ -483,6 +507,7 @@ export function TldrawWhiteboard({
|
||||
components={tldrawUiComponents}
|
||||
onMount={installWhiteboardRuntimeGuards}
|
||||
store={store}
|
||||
user={tldrawUser}
|
||||
/>
|
||||
<div
|
||||
aria-label="Resize whiteboard width"
|
||||
|
||||
@@ -140,6 +140,26 @@ test('tldraw whiteboard fences render as embedded canvases and remain Markdown-d
|
||||
expect(rawAfterRichMode).not.toContain('@@TOLARIA_TLDRAW')
|
||||
})
|
||||
|
||||
test('embedded tldraw whiteboards follow Tolaria theme changes', async ({ page }) => {
|
||||
await openNote(page, 'Whiteboard Embed')
|
||||
|
||||
const tldrawContainer = page.locator('.tldraw-whiteboard .tl-container').first()
|
||||
await expect(tldrawContainer).toBeVisible({ timeout: 20_000 })
|
||||
|
||||
const initialMode = await tldrawContainer.evaluate((element) =>
|
||||
element.classList.contains('tl-theme__dark') ? 'dark' : 'light'
|
||||
)
|
||||
|
||||
await page.getByTestId('status-theme-mode').click()
|
||||
const toggledMode = initialMode === 'dark' ? 'light' : 'dark'
|
||||
await expect(tldrawContainer).toHaveClass(new RegExp(`tl-theme__${toggledMode}`))
|
||||
await expect(tldrawContainer).toHaveAttribute('data-color-mode', toggledMode)
|
||||
|
||||
await page.getByTestId('status-theme-mode').click()
|
||||
await expect(tldrawContainer).toHaveClass(new RegExp(`tl-theme__${initialMode}`))
|
||||
await expect(tldrawContainer).toHaveAttribute('data-color-mode', initialMode)
|
||||
})
|
||||
|
||||
test('embedded tldraw interactions stay inside the whiteboard', async ({ page }) => {
|
||||
await openNote(page, 'Whiteboard Embed')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user