feat: add whiteboard fullscreen workspace
This commit is contained in:
@@ -629,6 +629,7 @@ Defined in `src/utils/durableMarkdownBlocks.ts`, `src/utils/editorDurableMarkdow
|
||||
- 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.
|
||||
- Embedded whiteboards expose a session-only full-window workspace that reuses the same tldraw store and Markdown snapshot; expanding or closing it does not persist camera, tool, or size state.
|
||||
- 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.
|
||||
|
||||
|
||||
@@ -534,6 +534,8 @@ files:
|
||||
editor.toolbar.exportPdf: 7a39acf8742a59d1a4f7ae064b6219a0
|
||||
editor.toolbar.moreActions: 89e19353176b94068dec4c768c25e70b
|
||||
editor.toolbar.openProperties: 948b90b60b8ce827f179e8c5b85b112e
|
||||
editor.whiteboard.enterFullscreen: 606bf766125ecff4b33ca98122222c72
|
||||
editor.whiteboard.exitFullscreen: afd0a0414d79d047f00319921e4d4944
|
||||
editor.exportPdf.unavailable: 2fce9834984172b64b66767485d815e9
|
||||
editor.exportPdf.failed: bf3fa78ff5725f5cbde8979b67ec2e2a
|
||||
filePreview.copyDeepLink: b75833669968adbef634495636e3fe50
|
||||
|
||||
@@ -423,6 +423,21 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
body.tldraw-whiteboard-fullscreen-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .tldraw-whiteboard.tldraw-whiteboard--fullscreen {
|
||||
position: fixed;
|
||||
inset: 8px;
|
||||
z-index: 1200;
|
||||
width: auto;
|
||||
max-width: none;
|
||||
height: auto;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 18px 60px rgb(0 0 0 / 24%);
|
||||
}
|
||||
|
||||
.editor__blocknote-container .tldraw-whiteboard .tl-container {
|
||||
--color-background: var(--card);
|
||||
--tl-layer-menu-click-capture: 25;
|
||||
@@ -434,6 +449,20 @@
|
||||
--tl-layer-following-indicator: 80;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .tldraw-whiteboard__fullscreen-button {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 90;
|
||||
background: var(--background);
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 12%);
|
||||
}
|
||||
|
||||
.editor__blocknote-container .tldraw-whiteboard--fullscreen .tldraw-whiteboard__fullscreen-button {
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .tldraw-whiteboard [data-radix-popper-content-wrapper] {
|
||||
position: absolute !important;
|
||||
}
|
||||
@@ -451,6 +480,10 @@
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .tldraw-whiteboard--fullscreen .tldraw-whiteboard__resize-handle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .tldraw-whiteboard__resize-handle--width {
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { act, cleanup, render, screen, waitFor } from '@testing-library/react'
|
||||
import type { ComponentProps } from 'react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { Editor } from 'tldraw'
|
||||
import { TldrawWhiteboard } from './TldrawWhiteboard'
|
||||
import { TooltipProvider } from './ui/tooltip'
|
||||
|
||||
interface MockTldrawProps {
|
||||
assetUrls: MockAssetUrls
|
||||
@@ -157,6 +159,27 @@ function expectBundledTldrawAssetUrls(assetUrls: MockAssetUrls) {
|
||||
expectNoCdnUrls(assetUrls.translations)
|
||||
}
|
||||
|
||||
function whiteboardProps(
|
||||
overrides: Partial<ComponentProps<typeof TldrawWhiteboard>> = {},
|
||||
): ComponentProps<typeof TldrawWhiteboard> {
|
||||
return {
|
||||
boardId: 'board-1',
|
||||
height: '520',
|
||||
snapshot: '',
|
||||
width: '',
|
||||
onSizeChange: vi.fn(),
|
||||
onSnapshotChange: vi.fn(),
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function renderWhiteboard(overrides: Partial<ComponentProps<typeof TldrawWhiteboard>> = {}) {
|
||||
return render(
|
||||
<TldrawWhiteboard {...whiteboardProps(overrides)} />,
|
||||
{ wrapper: TooltipProvider },
|
||||
)
|
||||
}
|
||||
|
||||
describe('TldrawWhiteboard', () => {
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
@@ -166,16 +189,7 @@ describe('TldrawWhiteboard', () => {
|
||||
})
|
||||
|
||||
it('uses bundled tldraw assets instead of CDN URLs', () => {
|
||||
render(
|
||||
<TldrawWhiteboard
|
||||
boardId="board-1"
|
||||
height="520"
|
||||
snapshot=""
|
||||
width=""
|
||||
onSizeChange={vi.fn()}
|
||||
onSnapshotChange={vi.fn()}
|
||||
/>
|
||||
)
|
||||
renderWhiteboard()
|
||||
|
||||
expect(screen.getByTestId('mock-tldraw')).toHaveAttribute('data-draw-font-url')
|
||||
expect(assetImportMock.getAssetUrlsByImport).toHaveBeenCalledWith(expect.any(Function))
|
||||
@@ -186,16 +200,7 @@ describe('TldrawWhiteboard', () => {
|
||||
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()}
|
||||
/>
|
||||
)
|
||||
renderWhiteboard()
|
||||
|
||||
expect(renderedTldrawProps().user?.userPreferences.get().colorScheme).toBe('dark')
|
||||
})
|
||||
@@ -203,16 +208,7 @@ describe('TldrawWhiteboard', () => {
|
||||
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()}
|
||||
/>
|
||||
)
|
||||
renderWhiteboard()
|
||||
|
||||
expect(renderedTldrawProps().user?.userPreferences.get().colorScheme).toBe('light')
|
||||
|
||||
@@ -227,16 +223,7 @@ describe('TldrawWhiteboard', () => {
|
||||
})
|
||||
|
||||
it('installs the text measurement guard when the canvas mounts', () => {
|
||||
render(
|
||||
<TldrawWhiteboard
|
||||
boardId="board-1"
|
||||
height="520"
|
||||
snapshot=""
|
||||
width=""
|
||||
onSizeChange={vi.fn()}
|
||||
onSnapshotChange={vi.fn()}
|
||||
/>
|
||||
)
|
||||
renderWhiteboard()
|
||||
|
||||
const editor = mockEditor()
|
||||
const cleanup = renderedTldrawProps().onMount(editor)
|
||||
@@ -254,16 +241,7 @@ describe('TldrawWhiteboard', () => {
|
||||
})
|
||||
|
||||
it('suppresses whiteboard platform permission rejections while mounted', () => {
|
||||
render(
|
||||
<TldrawWhiteboard
|
||||
boardId="board-1"
|
||||
height="520"
|
||||
snapshot=""
|
||||
width=""
|
||||
onSizeChange={vi.fn()}
|
||||
onSnapshotChange={vi.fn()}
|
||||
/>
|
||||
)
|
||||
renderWhiteboard()
|
||||
|
||||
const cleanup = renderedTldrawProps().onMount(mockEditor())
|
||||
const denied = {
|
||||
@@ -280,28 +258,12 @@ describe('TldrawWhiteboard', () => {
|
||||
|
||||
it('resets the drawing store when switching to a blank board snapshot', () => {
|
||||
const boardASnapshot = { records: { shape: 'from-board-a' } }
|
||||
const { rerender } = render(
|
||||
<TldrawWhiteboard
|
||||
boardId="board-1"
|
||||
height="520"
|
||||
snapshot={JSON.stringify(boardASnapshot)}
|
||||
width=""
|
||||
onSizeChange={vi.fn()}
|
||||
onSnapshotChange={vi.fn()}
|
||||
/>
|
||||
)
|
||||
const { rerender } = renderWhiteboard({ snapshot: JSON.stringify(boardASnapshot) })
|
||||
|
||||
expect(tldrawStoreMock.loadSnapshot).toHaveBeenLastCalledWith(expect.any(Object), boardASnapshot)
|
||||
|
||||
rerender(
|
||||
<TldrawWhiteboard
|
||||
boardId="board-2"
|
||||
height="520"
|
||||
snapshot=""
|
||||
width=""
|
||||
onSizeChange={vi.fn()}
|
||||
onSnapshotChange={vi.fn()}
|
||||
/>
|
||||
<TldrawWhiteboard {...whiteboardProps({ boardId: 'board-2' })} />
|
||||
)
|
||||
|
||||
expect(tldrawStoreMock.loadSnapshot).toHaveBeenLastCalledWith(expect.any(Object), { records: {} })
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type MutableRefObject, type PointerEvent as ReactPointerEvent } from 'react'
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type MouseEvent as ReactMouseEvent, type MutableRefObject, type PointerEvent as ReactPointerEvent } from 'react'
|
||||
import { getAssetUrlsByImport } from '@tldraw/assets/imports.vite'
|
||||
import { ArrowsIn, ArrowsOut } from '@phosphor-icons/react'
|
||||
import { Dialog as DialogPrimitive } from 'radix-ui'
|
||||
import {
|
||||
Box,
|
||||
@@ -19,11 +20,15 @@ import {
|
||||
} from 'tldraw'
|
||||
import 'tldraw/tldraw.css'
|
||||
import { useDocumentThemeMode } from '../hooks/useDocumentThemeMode'
|
||||
import { resolveEffectiveLocale, translate, type AppLocale } from '../lib/i18n'
|
||||
import type { ResolvedThemeMode } from '../lib/themeMode'
|
||||
import { Button } from './ui/button'
|
||||
import { ActionTooltip } from './ui/action-tooltip'
|
||||
import { installTldrawTextMeasurementGuard } from './tldrawTextMeasurementGuard'
|
||||
|
||||
const EMPTY_TLDRAW_TRANSLATION_URL = 'data:application/json;base64,e30K'
|
||||
const TOLARIA_TLDRAW_USER_ID = 'tolaria-whiteboard'
|
||||
const WHITEBOARD_FULLSCREEN_BODY_CLASS = 'tldraw-whiteboard-fullscreen-open'
|
||||
|
||||
function resolveTldrawAssetUrl(assetUrl: string | undefined): string {
|
||||
return assetUrl ?? EMPTY_TLDRAW_TRANSLATION_URL
|
||||
@@ -101,6 +106,28 @@ function ignoreTldrawUserPreferencesUpdate(preferences: TLUserPreferences) {
|
||||
void preferences
|
||||
}
|
||||
|
||||
function readDocumentLocale(): AppLocale {
|
||||
if (typeof document === 'undefined') return 'en'
|
||||
return resolveEffectiveLocale(document.documentElement.lang)
|
||||
}
|
||||
|
||||
function useDocumentLocale(): AppLocale {
|
||||
const [locale, setLocale] = useState(readDocumentLocale)
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document === 'undefined') return
|
||||
|
||||
const syncLocale = () => setLocale(readDocumentLocale())
|
||||
const observer = new MutationObserver(syncLocale)
|
||||
observer.observe(document.documentElement, { attributeFilter: ['lang'], attributes: true })
|
||||
syncLocale()
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
return locale
|
||||
}
|
||||
|
||||
function rejectionName(error: unknown): string {
|
||||
if (error instanceof Error) return error.name
|
||||
if (typeof error !== 'object' || error === null || !('name' in error)) return ''
|
||||
@@ -425,6 +452,40 @@ function TolariaTldrawDialogs() {
|
||||
))
|
||||
}
|
||||
|
||||
function useFullscreenWhiteboard() {
|
||||
const [fullscreen, setFullscreen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!fullscreen) return
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') setFullscreen(false)
|
||||
}
|
||||
document.body.classList.add(WHITEBOARD_FULLSCREEN_BODY_CLASS)
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
|
||||
return () => {
|
||||
document.body.classList.remove(WHITEBOARD_FULLSCREEN_BODY_CLASS)
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
}, [fullscreen])
|
||||
|
||||
useEffect(() => {
|
||||
const animationFrameId = window.requestAnimationFrame(() => {
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
})
|
||||
return () => { window.cancelAnimationFrame(animationFrameId) }
|
||||
}, [fullscreen])
|
||||
|
||||
const toggleFullscreen = useCallback((event: ReactMouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
setFullscreen((current) => !current)
|
||||
}, [])
|
||||
|
||||
return { fullscreen, toggleFullscreen }
|
||||
}
|
||||
|
||||
export function TldrawWhiteboard({
|
||||
boardId,
|
||||
height,
|
||||
@@ -441,6 +502,12 @@ export function TldrawWhiteboard({
|
||||
const persistedSize = useMemo(() => normalizeSize({ height, width }), [height, width])
|
||||
const [resizingSize, setResizingSize] = useState<PixelSize | null>(null)
|
||||
const visibleSize = resizingSize ?? persistedSize
|
||||
const { fullscreen, toggleFullscreen } = useFullscreenWhiteboard()
|
||||
const locale = useDocumentLocale()
|
||||
const fullscreenLabel = translate(
|
||||
locale,
|
||||
fullscreen ? 'editor.whiteboard.exitFullscreen' : 'editor.whiteboard.enterFullscreen',
|
||||
)
|
||||
const themeMode = useDocumentThemeMode()
|
||||
const userPreferences = useMemo(() => tldrawUserPreferences(themeMode), [themeMode])
|
||||
const tldrawUser = useTldrawUser({
|
||||
@@ -540,7 +607,7 @@ export function TldrawWhiteboard({
|
||||
return (
|
||||
<div
|
||||
ref={boardRef}
|
||||
className="tldraw-whiteboard"
|
||||
className={fullscreen ? 'tldraw-whiteboard tldraw-whiteboard--fullscreen' : 'tldraw-whiteboard'}
|
||||
contentEditable={false}
|
||||
data-board-id={boardId}
|
||||
style={cssSize(visibleSize)}
|
||||
@@ -553,6 +620,21 @@ export function TldrawWhiteboard({
|
||||
store={store}
|
||||
user={tldrawUser}
|
||||
/>
|
||||
<ActionTooltip copy={{ label: fullscreenLabel }} side="left">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon-xs"
|
||||
aria-label={fullscreenLabel}
|
||||
aria-pressed={fullscreen}
|
||||
className="tldraw-whiteboard__fullscreen-button"
|
||||
data-testid="tldraw-whiteboard-fullscreen-toggle"
|
||||
title={fullscreenLabel}
|
||||
onClick={toggleFullscreen}
|
||||
>
|
||||
{fullscreen ? <ArrowsIn aria-hidden="true" /> : <ArrowsOut aria-hidden="true" />}
|
||||
</Button>
|
||||
</ActionTooltip>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Resize whiteboard width"
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Экспартаваць нататку ў фармаце PDF",
|
||||
"editor.toolbar.moreActions": "Больш дзеянняў",
|
||||
"editor.toolbar.openProperties": "Адкрыць уласцівасці",
|
||||
"editor.whiteboard.enterFullscreen": "Разгарнуць дошку",
|
||||
"editor.whiteboard.exitFullscreen": "Выйсці з поўнаэкраннай дошкі",
|
||||
"editor.exportPdf.unavailable": "Адкрыйце нататку Markdown перад экспартам у PDF.",
|
||||
"editor.exportPdf.failed": "Немагчыма пачаць экспарт PDF: {error}",
|
||||
"filePreview.copyDeepLink": "Капіраваць спасылку",
|
||||
|
||||
@@ -534,6 +534,8 @@
|
||||
"editor.toolbar.exportPdf": "Ekspartavać natatku ŭ farmacie PDF",
|
||||
"editor.toolbar.moreActions": "Boĺš dziejanniaŭ",
|
||||
"editor.toolbar.openProperties": "Adkryć ulascivasci",
|
||||
"editor.whiteboard.enterFullscreen": "Pašyryć došku",
|
||||
"editor.whiteboard.exitFullscreen": "Vyjsci z poŭnaekrannaj doški",
|
||||
"editor.exportPdf.unavailable": "Adkryjcie natatku Markdown pierad ekspartam u PDF.",
|
||||
"editor.exportPdf.failed": "Nie ŭdalosia pačać ekspart PDF: {error}",
|
||||
"filePreview.copyDeepLink": "Kapijavać spasylku",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Notiz als PDF exportieren",
|
||||
"editor.toolbar.moreActions": "Weitere Notizaktionen",
|
||||
"editor.toolbar.openProperties": "Eigenschaften-Panel öffnen",
|
||||
"editor.whiteboard.enterFullscreen": "Whiteboard erweitern",
|
||||
"editor.whiteboard.exitFullscreen": "Vollbild-Whiteboard verlassen",
|
||||
"editor.exportPdf.unavailable": "Öffne eine Markdown-Notiz, bevor du sie als PDF exportierst.",
|
||||
"editor.exportPdf.failed": "PDF-Export konnte nicht gestartet werden: {error}",
|
||||
"filePreview.copyDeepLink": "Link kopieren",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Export note as PDF",
|
||||
"editor.toolbar.moreActions": "More note actions",
|
||||
"editor.toolbar.openProperties": "Open the properties panel",
|
||||
"editor.whiteboard.enterFullscreen": "Expand whiteboard",
|
||||
"editor.whiteboard.exitFullscreen": "Exit fullscreen whiteboard",
|
||||
"editor.exportPdf.unavailable": "Open a Markdown note before exporting a PDF.",
|
||||
"editor.exportPdf.failed": "Could not start PDF export: {error}",
|
||||
"filePreview.copyDeepLink": "Copy link",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Exportar nota como PDF",
|
||||
"editor.toolbar.moreActions": "Más acciones de nota",
|
||||
"editor.toolbar.openProperties": "Abrir el panel de propiedades",
|
||||
"editor.whiteboard.enterFullscreen": "Expandir pizarra",
|
||||
"editor.whiteboard.exitFullscreen": "Salir de la pizarra en pantalla completa",
|
||||
"editor.exportPdf.unavailable": "Abre una nota de Markdown antes de exportar un PDF.",
|
||||
"editor.exportPdf.failed": "No se pudo iniciar la exportación a PDF: {error}",
|
||||
"filePreview.copyDeepLink": "Copiar enlace",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Exportar nota como PDF",
|
||||
"editor.toolbar.moreActions": "Más acciones de nota",
|
||||
"editor.toolbar.openProperties": "Abrir el panel de propiedades",
|
||||
"editor.whiteboard.enterFullscreen": "Expandir pizarra",
|
||||
"editor.whiteboard.exitFullscreen": "Salir de la pizarra en pantalla completa",
|
||||
"editor.exportPdf.unavailable": "Abre una nota de Markdown antes de exportar un PDF.",
|
||||
"editor.exportPdf.failed": "No se ha podido iniciar la exportación a PDF: {error}",
|
||||
"filePreview.copyDeepLink": "Copiar enlace",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Exporter la note au format PDF",
|
||||
"editor.toolbar.moreActions": "Autres actions de note",
|
||||
"editor.toolbar.openProperties": "Ouvrir le panneau des propriétés",
|
||||
"editor.whiteboard.enterFullscreen": "Agrandir le tableau blanc",
|
||||
"editor.whiteboard.exitFullscreen": "Quitter le tableau blanc en plein écran",
|
||||
"editor.exportPdf.unavailable": "Ouvrez une note Markdown avant d'exporter un PDF.",
|
||||
"editor.exportPdf.failed": "Impossible de lancer l'exportation PDF : {error}",
|
||||
"filePreview.copyDeepLink": "Copier le lien",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Ekspor catatan sebagai PDF",
|
||||
"editor.toolbar.moreActions": "Tindakan catatan lainnya",
|
||||
"editor.toolbar.openProperties": "Buka panel properti",
|
||||
"editor.whiteboard.enterFullscreen": "Perluas papan tulis",
|
||||
"editor.whiteboard.exitFullscreen": "Keluar dari papan tulis layar penuh",
|
||||
"editor.exportPdf.unavailable": "Buka catatan Markdown sebelum mengekspor PDF.",
|
||||
"editor.exportPdf.failed": "Tidak dapat memulai ekspor PDF: {error}",
|
||||
"filePreview.copyDeepLink": "Salin tautan",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Esporta nota in PDF",
|
||||
"editor.toolbar.moreActions": "Altre azioni della nota",
|
||||
"editor.toolbar.openProperties": "Apri il pannello delle proprietà",
|
||||
"editor.whiteboard.enterFullscreen": "Espandi la lavagna",
|
||||
"editor.whiteboard.exitFullscreen": "Esci dalla lavagna a schermo intero",
|
||||
"editor.exportPdf.unavailable": "Apri una nota Markdown prima di esportare un PDF.",
|
||||
"editor.exportPdf.failed": "Impossibile avviare l'esportazione in PDF: {error}",
|
||||
"filePreview.copyDeepLink": "Copia link",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "ノートをPDFとしてエクスポート",
|
||||
"editor.toolbar.moreActions": "その他のノート操作",
|
||||
"editor.toolbar.openProperties": "プロパティパネルを開く",
|
||||
"editor.whiteboard.enterFullscreen": "ホワイトボードを展開",
|
||||
"editor.whiteboard.exitFullscreen": "フルスクリーンホワイトボードを終了",
|
||||
"editor.exportPdf.unavailable": "PDFをエクスポートする前に、Markdownノートを開いてください。",
|
||||
"editor.exportPdf.failed": "PDFエクスポートを開始できませんでした:{error}",
|
||||
"filePreview.copyDeepLink": "リンクをコピー",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "노트를 PDF로 내보내기",
|
||||
"editor.toolbar.moreActions": "추가 노트 작업",
|
||||
"editor.toolbar.openProperties": "속성 패널 열기",
|
||||
"editor.whiteboard.enterFullscreen": "화이트보드 펼치기",
|
||||
"editor.whiteboard.exitFullscreen": "전체 화면 화이트보드에서 나가기",
|
||||
"editor.exportPdf.unavailable": "PDF로 내보내기 전에 Markdown 노트를 여세요.",
|
||||
"editor.exportPdf.failed": "PDF 내보내기를 시작할 수 없습니다: {error}",
|
||||
"filePreview.copyDeepLink": "링크 복사",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Eksportuj notatkę jako PDF",
|
||||
"editor.toolbar.moreActions": "Więcej działań notatki",
|
||||
"editor.toolbar.openProperties": "Otwórz panel właściwości",
|
||||
"editor.whiteboard.enterFullscreen": "Rozwiń tablicę",
|
||||
"editor.whiteboard.exitFullscreen": "Wyjdź z tablicy pełnoekranowej",
|
||||
"editor.exportPdf.unavailable": "Otwórz notatkę Markdown przed wyeksportowaniem pliku PDF.",
|
||||
"editor.exportPdf.failed": "Nie można rozpocząć eksportu do PDF: {error}",
|
||||
"filePreview.copyDeepLink": "Kopiuj link",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Exportar nota como PDF",
|
||||
"editor.toolbar.moreActions": "Mais ações da nota",
|
||||
"editor.toolbar.openProperties": "Abrir o painel de propriedades",
|
||||
"editor.whiteboard.enterFullscreen": "Expandir quadro branco",
|
||||
"editor.whiteboard.exitFullscreen": "Sair do quadro branco em tela cheia",
|
||||
"editor.exportPdf.unavailable": "Abra uma nota Markdown antes de exportar um PDF.",
|
||||
"editor.exportPdf.failed": "Não foi possível iniciar a exportação para PDF: {error}",
|
||||
"filePreview.copyDeepLink": "Copiar link",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Exportar nota como PDF",
|
||||
"editor.toolbar.moreActions": "Mais ações da nota",
|
||||
"editor.toolbar.openProperties": "Abrir o painel de propriedades",
|
||||
"editor.whiteboard.enterFullscreen": "Expandir quadro branco",
|
||||
"editor.whiteboard.exitFullscreen": "Sair do quadro branco em ecrã inteiro",
|
||||
"editor.exportPdf.unavailable": "Abra uma nota Markdown antes de exportar um PDF.",
|
||||
"editor.exportPdf.failed": "Não foi possível iniciar a exportação para PDF: {error}",
|
||||
"filePreview.copyDeepLink": "Copiar link",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Экспортировать заметку в формате PDF",
|
||||
"editor.toolbar.moreActions": "Другие действия с заметкой",
|
||||
"editor.toolbar.openProperties": "Открыть панель свойств",
|
||||
"editor.whiteboard.enterFullscreen": "Развернуть доску",
|
||||
"editor.whiteboard.exitFullscreen": "Выйти из полноэкранной доски",
|
||||
"editor.exportPdf.unavailable": "Откройте заметку Markdown перед экспортом в PDF.",
|
||||
"editor.exportPdf.failed": "Не удалось начать экспорт в PDF: {error}",
|
||||
"filePreview.copyDeepLink": "Копировать ссылку",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "Xuất ghi chú dưới dạng PDF",
|
||||
"editor.toolbar.moreActions": "Thêm hành động ghi chú",
|
||||
"editor.toolbar.openProperties": "Mở bảng thuộc tính",
|
||||
"editor.whiteboard.enterFullscreen": "Mở rộng bảng trắng",
|
||||
"editor.whiteboard.exitFullscreen": "Thoát chế độ bảng trắng toàn màn hình",
|
||||
"editor.exportPdf.unavailable": "Mở ghi chú Markdown trước khi xuất PDF.",
|
||||
"editor.exportPdf.failed": "Không thể bắt đầu xuất PDF: {error}",
|
||||
"filePreview.copyDeepLink": "Sao chép liên kết",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "将笔记导出为 PDF",
|
||||
"editor.toolbar.moreActions": "更多笔记操作",
|
||||
"editor.toolbar.openProperties": "打开属性面板",
|
||||
"editor.whiteboard.enterFullscreen": "展开白板",
|
||||
"editor.whiteboard.exitFullscreen": "退出全屏白板",
|
||||
"editor.exportPdf.unavailable": "在导出 PDF 之前,请先打开 Markdown 笔记。",
|
||||
"editor.exportPdf.failed": "无法开始 PDF 导出:{error}",
|
||||
"filePreview.copyDeepLink": "复制链接",
|
||||
|
||||
@@ -532,6 +532,8 @@
|
||||
"editor.toolbar.exportPdf": "將筆記匯出為 PDF",
|
||||
"editor.toolbar.moreActions": "更多筆記操作",
|
||||
"editor.toolbar.openProperties": "開啟屬性面板",
|
||||
"editor.whiteboard.enterFullscreen": "展開白板",
|
||||
"editor.whiteboard.exitFullscreen": "退出全螢幕白板",
|
||||
"editor.exportPdf.unavailable": "在匯出 PDF 之前,請先開啟 Markdown 筆記。",
|
||||
"editor.exportPdf.failed": "無法開始 PDF 匯出:{error}",
|
||||
"filePreview.copyDeepLink": "複製連結",
|
||||
|
||||
@@ -198,6 +198,41 @@ test('embedded tldraw interactions stay inside the whiteboard', async ({ page })
|
||||
await expectNoEditorNodeSelection(page)
|
||||
})
|
||||
|
||||
test('embedded tldraw whiteboards can expand to a full-window workspace', async ({ page }) => {
|
||||
await openNote(page, 'Whiteboard Embed')
|
||||
|
||||
const whiteboard = page.locator('.tldraw-whiteboard')
|
||||
await expect(whiteboard).toBeVisible({ timeout: 20_000 })
|
||||
const embeddedBox = await whiteboard.boundingBox()
|
||||
expect(embeddedBox).not.toBeNull()
|
||||
|
||||
const fullscreenToggle = page.getByTestId('tldraw-whiteboard-fullscreen-toggle')
|
||||
await expect(fullscreenToggle).toBeVisible({ timeout: 5_000 })
|
||||
await fullscreenToggle.click()
|
||||
await expect(whiteboard).toHaveClass(/tldraw-whiteboard--fullscreen/u)
|
||||
|
||||
const fullscreenBox = await whiteboard.boundingBox()
|
||||
const viewport = page.viewportSize()
|
||||
expect(fullscreenBox).not.toBeNull()
|
||||
expect(viewport).not.toBeNull()
|
||||
expect(fullscreenBox!.x).toBeLessThanOrEqual(8)
|
||||
expect(fullscreenBox!.y).toBeLessThanOrEqual(8)
|
||||
expect(fullscreenBox!.width).toBeGreaterThanOrEqual(viewport!.width - 16)
|
||||
expect(fullscreenBox!.height).toBeGreaterThanOrEqual(viewport!.height - 16)
|
||||
|
||||
await page.getByTestId('tools.more-button').click()
|
||||
await expect(page.getByTestId('tools.more.ellipse')).toBeVisible({ timeout: 5_000 })
|
||||
await expect(whiteboard).toHaveClass(/tldraw-whiteboard--fullscreen/u)
|
||||
await expectNoEditorNodeSelection(page)
|
||||
|
||||
await fullscreenToggle.click()
|
||||
await expect(whiteboard).not.toHaveClass(/tldraw-whiteboard--fullscreen/u)
|
||||
|
||||
const restoredBox = await whiteboard.boundingBox()
|
||||
expect(restoredBox).not.toBeNull()
|
||||
expect(Math.abs(restoredBox!.height - embeddedBox!.height)).toBeLessThan(4)
|
||||
})
|
||||
|
||||
test('embedded tldraw dialogs appear and release focus when closed', async ({ page }) => {
|
||||
await openNote(page, 'Whiteboard Embed')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user