diff --git a/packages/tldraw/src/components/ToolsPanel/ToolsPanel.tsx b/packages/tldraw/src/components/ToolsPanel/ToolsPanel.tsx index d9b891386..a18a2a7d7 100644 --- a/packages/tldraw/src/components/ToolsPanel/ToolsPanel.tsx +++ b/packages/tldraw/src/components/ToolsPanel/ToolsPanel.tsx @@ -1,12 +1,13 @@ import * as React from 'react' import { styled } from '~styles' import type { TDSnapshot } from '~types' -import { useMediaQuery, useTldrawApp } from '~hooks' +import { useTldrawApp } from '~hooks' import { StatusBar } from './StatusBar' import { BackToContent } from './BackToContent' import { PrimaryTools } from './PrimaryTools' import { ActionButton } from './ActionButton' import { DeleteButton } from './DeleteButton' +import { breakpoints } from '~components/breakpoints' const isDebugModeSelector = (s: TDSnapshot) => s.settings.isDebugMode const dockPositionState = (s: TDSnapshot) => s.settings.dockPosition @@ -19,61 +20,20 @@ export const ToolsPanel = React.memo(function ToolsPanel({ onBlur }: ToolsPanelP const app = useTldrawApp() const isDebugMode = app.useStore(isDebugModeSelector) const dockPosition = app.useStore(dockPositionState) - const isMobile = useMediaQuery('(max-width: 900px)') - const bottomStyle = { - width: '100%', - height: 'min-content', - left: 0, - right: 0, - bottom: isDebugMode ? 40 : 0, - } - const topStyle = { - width: '100%', - height: 'min-content', - left: 0, - right: 0, - top: isMobile ? 60 : 10, - } - const rightStyle = { width: 'min-content', height: '100%', right: 0 } - const leftStyle = { width: 'min-content', height: '100%', left: 10 } - - const toolStyle = () => { - switch (dockPosition) { - case 'bottom': - return bottomStyle - case 'left': - return leftStyle - case 'right': - return rightStyle - case 'top': - return topStyle - default: - return bottomStyle - } - } - const style = toolStyle() - const centerWrapStyle = - dockPosition === 'bottom' || dockPosition === 'top' - ? { gridRow: 1, gridColumn: 2 } - : { gridRow: 2, gridColumn: 1 } - const primaryToolStyle = dockPosition === 'bottom' || dockPosition === 'top' ? 'row' : 'column' + const orientation = + dockPosition === 'bottom' || dockPosition === 'top' ? 'horizontal' : 'vertical' return ( - + - + @@ -104,6 +64,52 @@ const StyledToolsPanelContainer = styled('div', { '& > div > *': { pointerEvents: 'all', }, + variants: { + debug: { + true: {}, + false: {}, + }, + bp: { + mobile: {}, + small: {}, + medium: {}, + large: {}, + }, + side: { + top: { + width: '100%', + height: 'min-content', + left: 0, + right: 0, + top: 60, + }, + right: { width: 'min-content', height: '100%', right: 0 }, + bottom: { + width: '100%', + height: 'min-content', + left: 0, + right: 0, + bottom: 0, + }, + left: { width: 'min-content', height: '100%', left: 10 }, + }, + }, + compoundVariants: [ + { + side: 'top', + bp: 'large', + css: { + top: 10, + }, + }, + { + side: 'bottom', + debug: true, + css: { + bottom: 40, + }, + }, + ], }) const StyledCenterWrap = styled('div', { @@ -115,6 +121,12 @@ const StyledCenterWrap = styled('div', { justifyContent: 'center', flexDirection: 'column', gap: '$4', + variants: { + orientation: { + horizontal: { gridRow: 1, gridColumn: 2 }, + vertical: { gridRow: 2, gridColumn: 1 }, + }, + }, }) const StyledStatusWrap = styled('div', { @@ -131,4 +143,14 @@ const StyledPrimaryTools = styled('div', { display: 'flex', alignItems: 'center', gap: '$2', + variants: { + orientation: { + horizontal: { + flexDirection: 'row', + }, + vertical: { + flexDirection: 'column', + }, + }, + }, }) diff --git a/packages/tldraw/src/hooks/index.ts b/packages/tldraw/src/hooks/index.ts index b81b6b17f..b55955477 100644 --- a/packages/tldraw/src/hooks/index.ts +++ b/packages/tldraw/src/hooks/index.ts @@ -5,4 +5,3 @@ export * from './useStylesheet' export * from './useFileSystemHandlers' export * from './useFileSystem' export * from './useTranslation' -export * from './useMediaQuery' diff --git a/packages/tldraw/src/hooks/useMediaQuery.ts b/packages/tldraw/src/hooks/useMediaQuery.ts deleted file mode 100644 index 19a83c413..000000000 --- a/packages/tldraw/src/hooks/useMediaQuery.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as React from 'react' - -export function useMediaQuery(query: string) { - const [matches, setMatches] = React.useState(false) - - React.useEffect(() => { - const media = window.matchMedia(query) - if (media.matches !== matches) { - setMatches(media.matches) - } - const listener = () => setMatches(media.matches) - window.addEventListener('resize', listener) - return () => window.removeEventListener('resize', listener) - }, [matches, query]) - - return matches -} - -export default useMediaQuery diff --git a/packages/tldraw/src/state/TldrawApp.ts b/packages/tldraw/src/state/TldrawApp.ts index 59723126e..1b33ef22c 100644 --- a/packages/tldraw/src/state/TldrawApp.ts +++ b/packages/tldraw/src/state/TldrawApp.ts @@ -267,13 +267,13 @@ export class TldrawApp extends StateManager { constructor(id?: string, callbacks = {} as TDCallbacks) { super(TldrawApp.defaultState, id, TldrawApp.version, (prev, next, prevVersion) => { - return { - ...next, - document: migrate( - { ...next.document, ...prev.document, version: prevVersion }, - TldrawApp.version - ), - } + return migrate( + { + ...next, + document: { ...next.document, ...prev.document, version: prevVersion }, + }, + TldrawApp.version + ) }) this.callbacks = callbacks @@ -282,10 +282,7 @@ export class TldrawApp extends StateManager { /* -------------------- Internal -------------------- */ protected migrate = (state: TDSnapshot): TDSnapshot => { - return { - ...state, - document: migrate(state.document, TldrawApp.version), - } + return migrate(state, TldrawApp.version) } protected onReady = () => { @@ -297,10 +294,10 @@ export class TldrawApp extends StateManager { try { this.patchState({ + ...migrate(this.state, TldrawApp.version), appState: { status: TDStatus.Idle, }, - document: migrate(this.document, TldrawApp.version), }) } catch (e) { console.error('The data appears to be corrupted. Resetting!', e) @@ -1232,10 +1229,7 @@ export class TldrawApp extends StateManager { // Set the default page name to the localized version of "Page" doc.pages['page'].name = 'Page 1' - this.resetHistory() - .clearSelectHistory() - .loadDocument(migrate(doc, TldrawApp.version)) - .persist({}) + this.resetHistory().clearSelectHistory().loadDocument(TldrawApp.defaultDocument).persist({}) return this } @@ -1273,12 +1267,17 @@ export class TldrawApp extends StateManager { // If it's a new document, do a full change. if (this.document.id !== document.id) { this.replaceState({ - ...this.state, + ...migrate( + { + ...this.state, + document, + }, + TldrawApp.version + ), appState: { ...this.appState, currentPageId: Object.keys(document.pages)[0], }, - document: migrate(document, TldrawApp.version), }) return this } @@ -1340,12 +1339,11 @@ export class TldrawApp extends StateManager { return this.replaceState( { - ...this.state, + ...migrate( + { ...this.state, document: { ...document, pageStates: currentPageStates } }, + TldrawApp.version + ), appState: nextAppState, - document: { - ...migrate(document, TldrawApp.version), - pageStates: currentPageStates, - }, }, 'merge' ) @@ -1358,7 +1356,7 @@ export class TldrawApp extends StateManager { updateDocument = (document: TDDocument, reason = 'updated_document'): this => { const prevState = this.state - const nextState = { ...prevState, document: { ...prevState.document } } + let nextState = { ...prevState, document: { ...prevState.document } } if (!document.pages[this.currentPageId]) { nextState.appState = { @@ -1399,9 +1397,10 @@ export class TldrawApp extends StateManager { } } - nextState.document = migrate(nextState.document, nextState.document.version || 0) - - return this.replaceState(nextState, `${reason}:${document.id}`) + return this.replaceState( + migrate(nextState, nextState.document.version || 0), + `${reason}:${document.id}` + ) } /** @@ -1437,22 +1436,21 @@ export class TldrawApp extends StateManager { this.clearSelectHistory() this.session = undefined - this.replaceState( - { - ...TldrawApp.defaultState, - settings: { - ...this.state.settings, - }, - document: migrate(document, TldrawApp.version), - appState: { - ...TldrawApp.defaultState.appState, - ...this.state.appState, - currentPageId: Object.keys(document.pages)[0], - disableAssets: this.disableAssets, - }, + const state = { + ...TldrawApp.defaultState, + settings: { + ...this.state.settings, }, - 'loaded_document' - ) + document, + appState: { + ...TldrawApp.defaultState.appState, + ...this.state.appState, + currentPageId: Object.keys(document.pages)[0], + disableAssets: this.disableAssets, + }, + } + + this.replaceState(migrate(state, TldrawApp.version), 'loaded_document') const { point, zoom } = this.camera this.updateViewport(point, zoom) return this @@ -1476,7 +1474,7 @@ export class TldrawApp extends StateManager { if (this.readOnly) return try { const fileHandle = await saveToFileSystem( - migrate(this.document, TldrawApp.version), + migrate(this.state, TldrawApp.version).document, this.fileSystemHandle ) this.fileSystemHandle = fileHandle @@ -4121,7 +4119,7 @@ export class TldrawApp extends StateManager { getShapeUtil = TLDR.getShapeUtil - static version = 15.3 + static version = 15.4 static defaultDocument: TDDocument = { id: 'doc', diff --git a/packages/tldraw/src/state/__snapshots__/TldrawApp.spec.ts.snap b/packages/tldraw/src/state/__snapshots__/TldrawApp.spec.ts.snap index cd16ac0a4..f3baf2218 100644 --- a/packages/tldraw/src/state/__snapshots__/TldrawApp.spec.ts.snap +++ b/packages/tldraw/src/state/__snapshots__/TldrawApp.spec.ts.snap @@ -50,7 +50,7 @@ TldrawTestApp { "shapes": Object {}, }, }, - "version": 15.3, + "version": 15.4, }, "settings": Object { "dockPosition": "bottom", @@ -201,7 +201,7 @@ TldrawTestApp { }, }, }, - "version": 15.3, + "version": 15.4, }, "settings": Object { "dockPosition": "bottom", @@ -373,7 +373,7 @@ TldrawTestApp { "shapes": Object {}, }, }, - "version": 15.3, + "version": 15.4, }, "settings": Object { "dockPosition": "bottom", diff --git a/packages/tldraw/src/state/data/migrate.ts b/packages/tldraw/src/state/data/migrate.ts index c2b1c4496..f9e0f53dd 100644 --- a/packages/tldraw/src/state/data/migrate.ts +++ b/packages/tldraw/src/state/data/migrate.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { Decoration, FontStyle, TDDocument, TDShapeType, TextShape } from '~types' +import { Decoration, FontStyle, TDDocument, TDShapeType, TDSnapshot, TextShape } from '~types' -export function migrate(document: TDDocument, newVersion: number): TDDocument { +export function migrate(state: TDSnapshot, newVersion: number): TDSnapshot { + const { document, settings } = state const { version = 0 } = document if (!('assets' in document)) { @@ -11,8 +12,8 @@ export function migrate(document: TDDocument, newVersion: number): TDDocument { // Remove unused assets when loading a document const assetIdsInUse = new Set() - Object.values(document.pages).forEach(page => - Object.values(page.shapes).forEach(shape => { + Object.values(document.pages).forEach((page) => + Object.values(page.shapes).forEach((shape) => { const { parentId, children, assetId } = shape if (assetId) { @@ -26,7 +27,7 @@ export function migrate(document: TDDocument, newVersion: number): TDDocument { } if (shape.type === TDShapeType.Group && children) { - children.forEach(childId => { + children.forEach((childId) => { if (!page.shapes[childId]) { console.warn('Encountered a parent with a missing child!', shape.id, childId) children?.splice(children.indexOf(childId), 1) @@ -38,31 +39,31 @@ export function migrate(document: TDDocument, newVersion: number): TDDocument { }) ) - Object.keys(document.assets).forEach(assetId => { + Object.keys(document.assets).forEach((assetId) => { if (!assetIdsInUse.has(assetId)) { delete document.assets[assetId] } }) - if (version === newVersion) return document + if (version === newVersion) return state if (version < 14) { - Object.values(document.pages).forEach(page => { + Object.values(document.pages).forEach((page) => { Object.values(page.shapes) - .filter(shape => shape.type === TDShapeType.Text) - .forEach(shape => (shape as TextShape).style.font === FontStyle.Script) + .filter((shape) => shape.type === TDShapeType.Text) + .forEach((shape) => (shape as TextShape).style.font === FontStyle.Script) }) } // Lowercase styles, move binding meta to binding if (version <= 13) { - Object.values(document.pages).forEach(page => { - Object.values(page.bindings).forEach(binding => { + Object.values(document.pages).forEach((page) => { + Object.values(page.bindings).forEach((binding) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any Object.assign(binding, (binding as any).meta) }) - Object.values(page.shapes).forEach(shape => { + Object.values(page.shapes).forEach((shape) => { Object.entries(shape.style).forEach(([id, style]) => { if (typeof style === 'string') { // @ts-ignore @@ -95,8 +96,8 @@ export function migrate(document: TDDocument, newVersion: number): TDDocument { document.assets = {} } - Object.values(document.pages).forEach(page => { - Object.values(page.shapes).forEach(shape => { + Object.values(document.pages).forEach((page) => { + Object.values(page.shapes).forEach((shape) => { if (version < 15.2) { if (shape.type === TDShapeType.Image || shape.type === TDShapeType.Video) { shape.style.isFilled = true @@ -117,9 +118,13 @@ export function migrate(document: TDDocument, newVersion: number): TDDocument { }) }) + if (version < 15.4) { + settings.dockPosition = 'bottom' + } + // Cleanup - Object.values(document.pageStates).forEach(pageState => { - pageState.selectedIds = pageState.selectedIds.filter(id => { + Object.values(document.pageStates).forEach((pageState) => { + pageState.selectedIds = pageState.selectedIds.filter((id) => { return document.pages[pageState.id].shapes[id] !== undefined }) pageState.bindingId = undefined @@ -130,5 +135,5 @@ export function migrate(document: TDDocument, newVersion: number): TDDocument { document.version = newVersion - return document + return state }