[feature] Iframe warning (#800)

This commit is contained in:
Steve Ruiz 2022-07-08 21:25:08 +01:00 committed by GitHub
parent 1e80d1ac21
commit 2352985e94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 325 additions and 205 deletions

1
.gitignore vendored
View file

@ -18,3 +18,4 @@ apps/www/public/sw.js.map
.env .env
firebase.config.*.turbo firebase.config.*.turbo
.turbo .turbo
apps/new-tab-extension/tsconfig.tsbuildinfo

View file

@ -1,12 +1,5 @@
# @tldraw/new-tab-extension # @tldraw/new-tab-extension
## 0.1.1
### Patch Changes
- Updated dependencies
- @tldraw/tldraw@1.19.1
## 0.1.0 ## 0.1.0
### Minor Changes ### Minor Changes

View file

@ -1,7 +1,7 @@
{ {
"name": "@tldraw/new-tab-extension", "name": "@tldraw/new-tab-extension",
"private": true, "private": true,
"version": "0.1.1", "version": "0.1.0",
"description": "A tiny little new tab extension for tldraw.", "description": "A tiny little new tab extension for tldraw.",
"author": "@steveruizok", "author": "@steveruizok",
"repository": { "repository": {

File diff suppressed because one or more lines are too long

View file

@ -1,11 +1,5 @@
# @tldraw/vscode-editor # @tldraw/vscode-editor
## 1.11.1
### Patch Changes
- Fix toolbar placement.
## 1.11.0 ## 1.11.0
### Minor Changes ### Minor Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "@tldraw/vscode-editor", "name": "@tldraw/vscode-editor",
"version": "1.11.1", "version": "1.11.0",
"private": true, "private": true,
"description": "An an editor for the tldraw vscode extension.", "description": "An an editor for the tldraw vscode extension.",
"author": "@steveruizok", "author": "@steveruizok",

View file

@ -1,13 +1,5 @@
# @tldraw/www # @tldraw/www
## 1.7.9
### Patch Changes
- Updated dependencies
- @tldraw/core@1.14.2
- @tldraw/tldraw@1.19.1
## 1.7.8 ## 1.7.8
### Patch Changes ### Patch Changes

View file

@ -0,0 +1,127 @@
import React from 'react'
import { styled } from 'styles'
export function IFrameWarning({ url = 'https://tldraw.com' }: { url?: string }) {
const [copied, setCopied] = React.useState(false)
const rTimeout = React.useRef<any>(0)
const handleCopy = React.useCallback(() => {
setCopied(true)
clearTimeout(rTimeout.current)
rTimeout.current = setTimeout(() => {
setCopied(false)
}, 1200)
const textarea = document.createElement('textarea')
textarea.setAttribute('position', 'fixed')
textarea.setAttribute('top', '0')
textarea.setAttribute('readonly', 'true')
textarea.setAttribute('contenteditable', 'true')
textarea.style.position = 'fixed'
textarea.value = url
document.body.appendChild(textarea)
textarea.focus()
textarea.select()
//tldraw.com/r/hasdasdsad sad sadaasdasdasdsdasdadssdadadsd as ello
https: try {
const range = document.createRange()
range.selectNodeContents(textarea)
const sel = window.getSelection()
if (sel) {
sel.removeAllRanges()
sel.addRange(range)
textarea.setSelectionRange(0, textarea.value.length)
}
document.execCommand('copy')
} catch (err) {
console.log('nope')
null // Could not copy to clipboard
} finally {
document.body.removeChild(textarea)
}
}, [])
return (
<StyledContainer>
<StyledUrlLink href={url} target="_parent">
Visit this page on tldraw.com{' '}
<svg
width="15"
height="15"
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 2C2.44772 2 2 2.44772 2 3V12C2 12.5523 2.44772 13 3 13H12C12.5523 13 13 12.5523 13 12V8.5C13 8.22386 12.7761 8 12.5 8C12.2239 8 12 8.22386 12 8.5V12H3V3L6.5 3C6.77614 3 7 2.77614 7 2.5C7 2.22386 6.77614 2 6.5 2H3ZM12.8536 2.14645C12.9015 2.19439 12.9377 2.24964 12.9621 2.30861C12.9861 2.36669 12.9996 2.4303 13 2.497L13 2.5V2.50049V5.5C13 5.77614 12.7761 6 12.5 6C12.2239 6 12 5.77614 12 5.5V3.70711L6.85355 8.85355C6.65829 9.04882 6.34171 9.04882 6.14645 8.85355C5.95118 8.65829 5.95118 8.34171 6.14645 8.14645L11.2929 3H9.5C9.22386 3 9 2.77614 9 2.5C9 2.22386 9.22386 2 9.5 2H12.4999H12.5C12.5678 2 12.6324 2.01349 12.6914 2.03794C12.7504 2.06234 12.8056 2.09851 12.8536 2.14645Z"
fill="currentColor"
stroke="black"
strokeWidth=".5"
></path>
</svg>
</StyledUrlLink>
<StyledUrlContainer onClick={handleCopy}>
<span>{url}</span>
<svg
width="18"
height="18"
viewBox="0 0 15 15"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
{copied ? (
<path d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z" />
) : (
<path d="M5 2V1H10V2H5ZM4.75 0C4.33579 0 4 0.335786 4 0.75V1H3.5C2.67157 1 2 1.67157 2 2.5V12.5C2 13.3284 2.67157 14 3.5 14H7V13H3.5C3.22386 13 3 12.7761 3 12.5V2.5C3 2.22386 3.22386 2 3.5 2H4V2.25C4 2.66421 4.33579 3 4.75 3H10.25C10.6642 3 11 2.66421 11 2.25V2H11.5C11.7761 2 12 2.22386 12 2.5V7H13V2.5C13 1.67157 12.3284 1 11.5 1H11V0.75C11 0.335786 10.6642 0 10.25 0H4.75ZM9 8.5C9 8.77614 8.77614 9 8.5 9C8.22386 9 8 8.77614 8 8.5C8 8.22386 8.22386 8 8.5 8C8.77614 8 9 8.22386 9 8.5ZM10.5 9C10.7761 9 11 8.77614 11 8.5C11 8.22386 10.7761 8 10.5 8C10.2239 8 10 8.22386 10 8.5C10 8.77614 10.2239 9 10.5 9ZM13 8.5C13 8.77614 12.7761 9 12.5 9C12.2239 9 12 8.77614 12 8.5C12 8.22386 12.2239 8 12.5 8C12.7761 8 13 8.22386 13 8.5ZM14.5 9C14.7761 9 15 8.77614 15 8.5C15 8.22386 14.7761 8 14.5 8C14.2239 8 14 8.22386 14 8.5C14 8.77614 14.2239 9 14.5 9ZM15 10.5C15 10.7761 14.7761 11 14.5 11C14.2239 11 14 10.7761 14 10.5C14 10.2239 14.2239 10 14.5 10C14.7761 10 15 10.2239 15 10.5ZM14.5 13C14.7761 13 15 12.7761 15 12.5C15 12.2239 14.7761 12 14.5 12C14.2239 12 14 12.2239 14 12.5C14 12.7761 14.2239 13 14.5 13ZM14.5 15C14.7761 15 15 14.7761 15 14.5C15 14.2239 14.7761 14 14.5 14C14.2239 14 14 14.2239 14 14.5C14 14.7761 14.2239 15 14.5 15ZM8.5 11C8.77614 11 9 10.7761 9 10.5C9 10.2239 8.77614 10 8.5 10C8.22386 10 8 10.2239 8 10.5C8 10.7761 8.22386 11 8.5 11ZM9 12.5C9 12.7761 8.77614 13 8.5 13C8.22386 13 8 12.7761 8 12.5C8 12.2239 8.22386 12 8.5 12C8.77614 12 9 12.2239 9 12.5ZM8.5 15C8.77614 15 9 14.7761 9 14.5C9 14.2239 8.77614 14 8.5 14C8.22386 14 8 14.2239 8 14.5C8 14.7761 8.22386 15 8.5 15ZM11 14.5C11 14.7761 10.7761 15 10.5 15C10.2239 15 10 14.7761 10 14.5C10 14.2239 10.2239 14 10.5 14C10.7761 14 11 14.2239 11 14.5ZM12.5 15C12.7761 15 13 14.7761 13 14.5C13 14.2239 12.7761 14 12.5 14C12.2239 14 12 14.2239 12 14.5C12 14.7761 12.2239 15 12.5 15Z" />
)}
</svg>
</StyledUrlContainer>
</StyledContainer>
)
}
const StyledContainer = styled('div', {
position: 'absolute',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontFamily: '$body',
fontWeight: 'normal',
fontSize: '$3',
gap: '$5',
flexDirection: 'column',
})
const StyledUrlLink = styled('a', {
display: 'flex',
alignItems: 'center',
gap: '$2',
padding: '$5',
})
const StyledUrlContainer = styled('a', {
backgroundColor: '$hover',
borderRadius: '$2',
maxWidth: '62%',
minWidth: 320,
gap: '$4',
padding: '$4 $5',
fontSize: '$2',
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
'& span': {
flexGrow: 2,
overflow: 'auto',
whiteSpace: 'nowrap',
'-ms-overflow-style': 'none' /* for Internet Explorer, Edge */,
scrollbarWidth: 'none',
'&::webkit-scrollbar': {
display: 'none',
},
},
})

View file

@ -1,6 +1,9 @@
import { RoomProvider } from '../utils/liveblocks' import { RoomProvider } from '../utils/liveblocks'
import { Tldraw, useFileSystem } from '@tldraw/tldraw' import { Tldraw, useFileSystem } from '@tldraw/tldraw'
import { useAccountHandlers } from 'hooks/useAccountHandlers' import { useAccountHandlers } from 'hooks/useAccountHandlers'
import { useMultiplayerAssets } from 'hooks/useMultiplayerAssets'
import { useMultiplayerState } from 'hooks/useMultiplayerState'
import { useUploadAssets } from 'hooks/useUploadAssets'
import React, { FC } from 'react' import React, { FC } from 'react'
import { styled } from 'styles' import { styled } from 'styles'
import { useReadOnlyMultiplayerState } from 'hooks/useReadOnlyMultiplayerState' import { useReadOnlyMultiplayerState } from 'hooks/useReadOnlyMultiplayerState'

View file

@ -1,6 +1,6 @@
{ {
"name": "@tldraw/www", "name": "@tldraw/www",
"version": "1.7.9", "version": "1.7.8",
"private": true, "private": true,
"description": "A tiny little drawing app (site).", "description": "A tiny little drawing app (site).",
"repository": { "repository": {

View file

@ -2,6 +2,7 @@ import * as React from 'react'
import type { GetServerSideProps } from 'next' import type { GetServerSideProps } from 'next'
import { getSession } from 'next-auth/react' import { getSession } from 'next-auth/react'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { IFrameWarning } from 'components/IFrameWarning'
const MultiplayerEditor = dynamic(() => import('components/MultiplayerEditor'), { const MultiplayerEditor = dynamic(() => import('components/MultiplayerEditor'), {
ssr: false, ssr: false,
}) as any }) as any
@ -13,6 +14,10 @@ interface RoomProps {
} }
export default function Room({ id, isUser, isSponsor }: RoomProps) { export default function Room({ id, isUser, isSponsor }: RoomProps) {
if (typeof window !== 'undefined') {
return <IFrameWarning url={`https://tldraw.com/r/${id}`} />
}
return <MultiplayerEditor isUser={isUser} isSponsor={isSponsor} roomId={id} /> return <MultiplayerEditor isUser={isUser} isSponsor={isSponsor} roomId={id} />
} }

View file

@ -3,6 +3,7 @@ import type { GetServerSideProps } from 'next'
import { getSession } from 'next-auth/react' import { getSession } from 'next-auth/react'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { Utils } from '@tldraw/core' import { Utils } from '@tldraw/core'
import { IFrameWarning } from 'components/IFrameWarning'
const ReadOnlyMultiplayerEditor = dynamic(() => import('components/ReadOnlyMultiplayerEditor'), { const ReadOnlyMultiplayerEditor = dynamic(() => import('components/ReadOnlyMultiplayerEditor'), {
ssr: false, ssr: false,
}) as any }) as any
@ -14,6 +15,10 @@ interface RoomProps {
} }
export default function Room({ id, isUser, isSponsor }: RoomProps) { export default function Room({ id, isUser, isSponsor }: RoomProps) {
if (typeof window !== 'undefined') {
return <IFrameWarning url={`https://tldraw.com/v/${id}`} />
}
return <ReadOnlyMultiplayerEditor isUser={isUser} isSponsor={isSponsor} roomId={id} /> return <ReadOnlyMultiplayerEditor isUser={isUser} isSponsor={isSponsor} roomId={id} />
} }

View file

@ -18,6 +18,7 @@ import UIOptions from './ui-options'
import { Multiplayer } from './multiplayer' import { Multiplayer } from './multiplayer'
import './styles.css' import './styles.css'
import Export from '~export' import Export from '~export'
import IFrame from '~iframe'
const pages: ({ path: string; component: any; title: string } | '---')[] = [ const pages: ({ path: string; component: any; title: string } | '---')[] = [
{ path: '/develop', component: Develop, title: 'Develop' }, { path: '/develop', component: Develop, title: 'Develop' },
@ -42,6 +43,7 @@ const pages: ({ path: string; component: any; title: string } | '---')[] = [
{ path: '/export', component: Export, title: 'Export' }, { path: '/export', component: Export, title: 'Export' },
{ path: '/scroll', component: Scroll, title: 'In a scrolling container' }, { path: '/scroll', component: Scroll, title: 'In a scrolling container' },
{ path: '/multiplayer', component: Multiplayer, title: 'Multiplayer' }, { path: '/multiplayer', component: Multiplayer, title: 'Multiplayer' },
{ path: '/iframe', component: IFrame, title: 'IFrame' },
] ]
export default function App() { export default function App() {

View file

@ -0,0 +1,9 @@
import * as React from 'react'
export default function IFrame() {
return (
<div className="tldraw">
<iframe src="http://localhost:3000/r/hello" style={{ width: '100%', height: '50%' }} />
</div>
)
}

View file

@ -17,7 +17,7 @@
], ],
"scripts": { "scripts": {
"build": "turbo run build", "build": "turbo run build",
"build:www": "turbo run build:www", "build:www": "turbo run build:www --force",
"build:core": "turbo run build:core", "build:core": "turbo run build:core",
"build:packages": "turbo run build:packages --stream", "build:packages": "turbo run build:packages --stream",
"build:apps": "turbo run build:apps", "build:apps": "turbo run build:apps",

View file

@ -1,11 +1,5 @@
# Changelog # Changelog
## 1.14.2
### Patch Changes
- Fix toolbar placement.
## 1.14.1 ## 1.14.1
### Patch Changes ### Patch Changes

View file

@ -1,5 +1,5 @@
{ {
"version": "1.14.2", "version": "1.14.1",
"name": "@tldraw/core", "name": "@tldraw/core",
"description": "The tldraw core renderer and utilities.", "description": "The tldraw core renderer and utilities.",
"author": "@steveruizok", "author": "@steveruizok",

View file

@ -1491,15 +1491,14 @@ left past the initial left edge) then swap points on that axis.
* @param str string * @param str string
*/ */
static lns(str: string) { static lns(str: string) {
const result = str.split('') const result = str
.split('')
.map((n) => (Number.isNaN(+n) ? n : +n < 5 ? 5 + +n : +n > 5 ? +n - 5 : +n))
result.push(...result.splice(0, Math.round(result.length / 5))) result.push(...result.splice(0, Math.round(result.length / 5)))
result.push(...result.splice(0, Math.round(result.length / 4))) result.push(...result.splice(0, Math.round(result.length / 4)))
result.push(...result.splice(0, Math.round(result.length / 3))) result.push(...result.splice(0, Math.round(result.length / 3)))
result.push(...result.splice(0, Math.round(result.length / 2))) result.push(...result.splice(0, Math.round(result.length / 2)))
return result return result.reverse().join('')
.reverse()
.map((n) => (+n ? (+n < 5 ? 5 + +n : +n > 5 ? +n - 5 : n) : n))
.join('')
} }
} }

View file

@ -1,13 +1,5 @@
# Changelog # Changelog
## 1.19.1
### Patch Changes
- Fix toolbar placement.
- Updated dependencies
- @tldraw/core@1.14.2
## 1.19.0 ## 1.19.0
### Minor Changes ### Minor Changes

View file

@ -1,6 +1,6 @@
{ {
"name": "@tldraw/tldraw", "name": "@tldraw/tldraw",
"version": "1.19.1", "version": "1.19.0",
"description": "A tiny little drawing app (editor)", "description": "A tiny little drawing app (editor)",
"author": "@steveruizok", "author": "@steveruizok",
"repository": { "repository": {
@ -47,7 +47,7 @@
"@radix-ui/react-icons": "^1.1.1", "@radix-ui/react-icons": "^1.1.1",
"@radix-ui/react-tooltip": "^0.1.7", "@radix-ui/react-tooltip": "^0.1.7",
"@stitches/react": "^1.2.8", "@stitches/react": "^1.2.8",
"@tldraw/core": "^1.14.2", "@tldraw/core": "^1.14.1",
"@tldraw/intersect": "^1.7.1", "@tldraw/intersect": "^1.7.1",
"@tldraw/vec": "^1.7.1", "@tldraw/vec": "^1.7.1",
"idb-keyval": "^6.1.0", "idb-keyval": "^6.1.0",

View file

@ -618,7 +618,7 @@ const StyledUI = styled('div', {
left: 0, left: 0,
height: '100%', height: '100%',
width: '100%', width: '100%',
padding: '0', padding: '8px 8px 0 8px',
display: 'flex', display: 'flex',
alignItems: 'flex-start', alignItems: 'flex-start',
justifyContent: 'flex-start', justifyContent: 'flex-start',

View file

@ -119,7 +119,7 @@ export const StyledToolButton = styled('button', {
color: '$text', color: '$text',
fontSize: '$0', fontSize: '$0',
background: 'none', background: 'none',
margin: 0, margin: '0',
padding: '$2', padding: '$2',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
@ -158,9 +158,7 @@ export const StyledToolButton = styled('button', {
}, },
}, },
circle: { circle: {
padding: '0', padding: '$2',
height: 32,
width: 32,
[`& ${StyledToolButtonInner}`]: { [`& ${StyledToolButtonInner}`]: {
border: '1px solid $panelContrast', border: '1px solid $panelContrast',
borderRadius: '100%', borderRadius: '100%',

View file

@ -1,13 +1,12 @@
import * as React from 'react' import * as React from 'react'
import { styled } from '~styles' import { styled } from '~styles'
import type { TDSnapshot } from '~types' import type { TDSnapshot } from '~types'
import { useTldrawApp } from '~hooks' import { useMediaQuery, useTldrawApp } from '~hooks'
import { StatusBar } from './StatusBar' import { StatusBar } from './StatusBar'
import { BackToContent } from './BackToContent' import { BackToContent } from './BackToContent'
import { PrimaryTools } from './PrimaryTools' import { PrimaryTools } from './PrimaryTools'
import { ActionButton } from './ActionButton' import { ActionButton } from './ActionButton'
import { DeleteButton } from './DeleteButton' import { DeleteButton } from './DeleteButton'
import { breakpoints } from '~components/breakpoints'
const isDebugModeSelector = (s: TDSnapshot) => s.settings.isDebugMode const isDebugModeSelector = (s: TDSnapshot) => s.settings.isDebugMode
const dockPositionState = (s: TDSnapshot) => s.settings.dockPosition const dockPositionState = (s: TDSnapshot) => s.settings.dockPosition
@ -18,29 +17,74 @@ interface ToolsPanelProps {
export const ToolsPanel = React.memo(function ToolsPanel({ onBlur }: ToolsPanelProps) { export const ToolsPanel = React.memo(function ToolsPanel({ onBlur }: ToolsPanelProps) {
const app = useTldrawApp() const app = useTldrawApp()
const side = app.useStore(dockPositionState)
const isDebugMode = app.useStore(isDebugModeSelector) 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'
return ( return (
<> <StyledToolsPanelContainer
<StyledToolsPanelContainer side={side} onBlur={onBlur} bp={breakpoints} debug={isDebugMode}> style={{
<StyledCenterWrap id="TD-Tools"> ...style,
<BackToContent /> }}
<StyledPrimaryTools onBlur={onBlur}
orientation={side === 'bottom' || side === 'top' ? 'horizontal' : 'vertical'} >
> <StyledCenterWrap
<ActionButton /> id="TD-Tools"
<PrimaryTools /> style={{
<DeleteButton /> ...centerWrapStyle,
</StyledPrimaryTools> }}
</StyledCenterWrap> >
</StyledToolsPanelContainer> <BackToContent />
<StyledPrimaryTools style={{ flexDirection: primaryToolStyle }}>
<ActionButton />
<PrimaryTools />
<DeleteButton />
</StyledPrimaryTools>
</StyledCenterWrap>
{isDebugMode && ( {isDebugMode && (
<StyledStatusWrap> <StyledStatusWrap>
<StatusBar /> <StatusBar />
</StyledStatusWrap> </StyledStatusWrap>
)} )}
</> </StyledToolsPanelContainer>
) )
}) })
@ -49,68 +93,24 @@ const StyledToolsPanelContainer = styled('div', {
width: '100%', width: '100%',
minWidth: 0, minWidth: 0,
maxWidth: '100%', maxWidth: '100%',
height: '64px', display: 'grid',
gridTemplateColumns: 'auto auto auto',
gridTemplateRows: 'auto auto',
justifyContent: 'space-between',
padding: '0',
gap: '$4', gap: '$4',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 200, zIndex: 200,
overflow: 'hidden',
pointerEvents: 'none', pointerEvents: 'none',
'& > div > *': { '& > div > *': {
pointerEvents: 'all', pointerEvents: 'all',
}, },
variants: {
debug: {
true: {},
false: {},
},
bp: {
mobile: {},
small: {},
medium: {},
large: {},
},
side: {
top: {
width: '100%',
height: 64,
left: 0,
right: 0,
top: 45,
},
right: { width: 64, height: '100%', top: 0, right: 0 },
bottom: {
width: '100%',
left: 0,
right: 0,
bottom: 0,
},
left: { width: 64, height: '100%', left: 0 },
},
},
compoundVariants: [
{
side: 'top',
bp: 'large',
css: {
top: '10px',
},
},
{
side: 'bottom',
debug: true,
css: {
bottom: 40,
},
},
],
}) })
const StyledCenterWrap = styled('div', { const StyledCenterWrap = styled('div', {
gridRow: 1,
gridColumn: 2,
display: 'flex', display: 'flex',
width: 'fit-content', width: 'fit-content',
height: 'fit-content',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
flexDirection: 'column', flexDirection: 'column',
@ -118,11 +118,10 @@ const StyledCenterWrap = styled('div', {
}) })
const StyledStatusWrap = styled('div', { const StyledStatusWrap = styled('div', {
position: 'absolute', position: 'fixed',
bottom: 0, bottom: 0,
left: 0, left: 0,
right: 0, right: 0,
height: '40px',
width: '100%', width: '100%',
maxWidth: '100%', maxWidth: '100%',
}) })
@ -131,16 +130,5 @@ const StyledPrimaryTools = styled('div', {
position: 'relative', position: 'relative',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
height: 'fit-content', gap: '$2',
gap: '$3',
variants: {
orientation: {
horizontal: {
flexDirection: 'row',
},
vertical: {
flexDirection: 'column',
},
},
},
}) })

View file

@ -119,7 +119,7 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() {
<DMItem <DMItem
id="TD-Multiplayer-CopyReadOnlyLink" id="TD-Multiplayer-CopyReadOnlyLink"
onClick={handleCopyReadOnlySelect} onClick={handleCopyReadOnlySelect}
disabled={!room} disabled={false}
> >
<FormattedMessage id="copy.readonly.link" /> <FormattedMessage id="copy.readonly.link" />
<SmallIcon>{copied ? <CheckIcon /> : <ClipboardIcon />}</SmallIcon> <SmallIcon>{copied ? <CheckIcon /> : <ClipboardIcon />}</SmallIcon>

View file

@ -5,3 +5,4 @@ export * from './useStylesheet'
export * from './useFileSystemHandlers' export * from './useFileSystemHandlers'
export * from './useFileSystem' export * from './useFileSystem'
export * from './useTranslation' export * from './useTranslation'
export * from './useMediaQuery'

View file

@ -0,0 +1,19 @@
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

View file

@ -1015,6 +1015,7 @@ export class TLDR {
sel.addRange(range) sel.addRange(range)
textarea.setSelectionRange(0, textarea.value.length) textarea.setSelectionRange(0, textarea.value.length)
} }
document.execCommand('copy')
} catch (err) { } catch (err) {
null // Could not copy to clipboard null // Could not copy to clipboard
} finally { } finally {

View file

@ -267,13 +267,13 @@ export class TldrawApp extends StateManager<TDSnapshot> {
constructor(id?: string, callbacks = {} as TDCallbacks) { constructor(id?: string, callbacks = {} as TDCallbacks) {
super(TldrawApp.defaultState, id, TldrawApp.version, (prev, next, prevVersion) => { super(TldrawApp.defaultState, id, TldrawApp.version, (prev, next, prevVersion) => {
return migrate( return {
{ ...next,
...next, document: migrate(
document: { ...next.document, ...prev.document, version: prevVersion }, { ...next.document, ...prev.document, version: prevVersion },
}, TldrawApp.version
TldrawApp.version ),
) }
}) })
this.callbacks = callbacks this.callbacks = callbacks
@ -282,7 +282,10 @@ export class TldrawApp extends StateManager<TDSnapshot> {
/* -------------------- Internal -------------------- */ /* -------------------- Internal -------------------- */
protected migrate = (state: TDSnapshot): TDSnapshot => { protected migrate = (state: TDSnapshot): TDSnapshot => {
return migrate(state, TldrawApp.version) return {
...state,
document: migrate(state.document, TldrawApp.version),
}
} }
protected onReady = () => { protected onReady = () => {
@ -294,10 +297,10 @@ export class TldrawApp extends StateManager<TDSnapshot> {
try { try {
this.patchState({ this.patchState({
...migrate(this.state, TldrawApp.version),
appState: { appState: {
status: TDStatus.Idle, status: TDStatus.Idle,
}, },
document: migrate(this.document, TldrawApp.version),
}) })
} catch (e) { } catch (e) {
console.error('The data appears to be corrupted. Resetting!', e) console.error('The data appears to be corrupted. Resetting!', e)
@ -1229,7 +1232,10 @@ export class TldrawApp extends StateManager<TDSnapshot> {
// Set the default page name to the localized version of "Page" // Set the default page name to the localized version of "Page"
doc.pages['page'].name = 'Page 1' doc.pages['page'].name = 'Page 1'
this.resetHistory().clearSelectHistory().loadDocument(TldrawApp.defaultDocument).persist({}) this.resetHistory()
.clearSelectHistory()
.loadDocument(migrate(doc, TldrawApp.version))
.persist({})
return this return this
} }
@ -1267,17 +1273,12 @@ export class TldrawApp extends StateManager<TDSnapshot> {
// If it's a new document, do a full change. // If it's a new document, do a full change.
if (this.document.id !== document.id) { if (this.document.id !== document.id) {
this.replaceState({ this.replaceState({
...migrate( ...this.state,
{
...this.state,
document,
},
TldrawApp.version
),
appState: { appState: {
...this.appState, ...this.appState,
currentPageId: Object.keys(document.pages)[0], currentPageId: Object.keys(document.pages)[0],
}, },
document: migrate(document, TldrawApp.version),
}) })
return this return this
} }
@ -1339,11 +1340,12 @@ export class TldrawApp extends StateManager<TDSnapshot> {
return this.replaceState( return this.replaceState(
{ {
...migrate( ...this.state,
{ ...this.state, document: { ...document, pageStates: currentPageStates } },
TldrawApp.version
),
appState: nextAppState, appState: nextAppState,
document: {
...migrate(document, TldrawApp.version),
pageStates: currentPageStates,
},
}, },
'merge' 'merge'
) )
@ -1356,7 +1358,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
updateDocument = (document: TDDocument, reason = 'updated_document'): this => { updateDocument = (document: TDDocument, reason = 'updated_document'): this => {
const prevState = this.state const prevState = this.state
let nextState = { ...prevState, document: { ...prevState.document } } const nextState = { ...prevState, document: { ...prevState.document } }
if (!document.pages[this.currentPageId]) { if (!document.pages[this.currentPageId]) {
nextState.appState = { nextState.appState = {
@ -1397,10 +1399,9 @@ export class TldrawApp extends StateManager<TDSnapshot> {
} }
} }
return this.replaceState( nextState.document = migrate(nextState.document, nextState.document.version || 0)
migrate(nextState, nextState.document.version || 0),
`${reason}:${document.id}` return this.replaceState(nextState, `${reason}:${document.id}`)
)
} }
/** /**
@ -1436,21 +1437,22 @@ export class TldrawApp extends StateManager<TDSnapshot> {
this.clearSelectHistory() this.clearSelectHistory()
this.session = undefined this.session = undefined
const state = { this.replaceState(
...TldrawApp.defaultState, {
settings: { ...TldrawApp.defaultState,
...this.state.settings, 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,
},
}, },
document, 'loaded_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 const { point, zoom } = this.camera
this.updateViewport(point, zoom) this.updateViewport(point, zoom)
return this return this
@ -1474,7 +1476,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
if (this.readOnly) return if (this.readOnly) return
try { try {
const fileHandle = await saveToFileSystem( const fileHandle = await saveToFileSystem(
migrate(this.state, TldrawApp.version).document, migrate(this.document, TldrawApp.version),
this.fileSystemHandle this.fileSystemHandle
) )
this.fileSystemHandle = fileHandle this.fileSystemHandle = fileHandle
@ -4119,7 +4121,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
getShapeUtil = TLDR.getShapeUtil getShapeUtil = TLDR.getShapeUtil
static version = 15.4 static version = 15.3
static defaultDocument: TDDocument = { static defaultDocument: TDDocument = {
id: 'doc', id: 'doc',

View file

@ -50,7 +50,7 @@ TldrawTestApp {
"shapes": Object {}, "shapes": Object {},
}, },
}, },
"version": 15.4, "version": 15.3,
}, },
"settings": Object { "settings": Object {
"dockPosition": "bottom", "dockPosition": "bottom",
@ -201,7 +201,7 @@ TldrawTestApp {
}, },
}, },
}, },
"version": 15.4, "version": 15.3,
}, },
"settings": Object { "settings": Object {
"dockPosition": "bottom", "dockPosition": "bottom",
@ -373,7 +373,7 @@ TldrawTestApp {
"shapes": Object {}, "shapes": Object {},
}, },
}, },
"version": 15.4, "version": 15.3,
}, },
"settings": Object { "settings": Object {
"dockPosition": "bottom", "dockPosition": "bottom",

View file

@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
import { Decoration, FontStyle, TDDocument, TDShapeType, TDSnapshot, TextShape } from '~types' import { Decoration, FontStyle, TDDocument, TDShapeType, TextShape } from '~types'
export function migrate(state: TDSnapshot, newVersion: number): TDSnapshot { export function migrate(document: TDDocument, newVersion: number): TDDocument {
const { document, settings } = state
const { version = 0 } = document const { version = 0 } = document
if (!('assets' in document)) { if (!('assets' in document)) {
@ -12,8 +11,8 @@ export function migrate(state: TDSnapshot, newVersion: number): TDSnapshot {
// Remove unused assets when loading a document // Remove unused assets when loading a document
const assetIdsInUse = new Set<string>() const assetIdsInUse = new Set<string>()
Object.values(document.pages).forEach((page) => Object.values(document.pages).forEach(page =>
Object.values(page.shapes).forEach((shape) => { Object.values(page.shapes).forEach(shape => {
const { parentId, children, assetId } = shape const { parentId, children, assetId } = shape
if (assetId) { if (assetId) {
@ -27,7 +26,7 @@ export function migrate(state: TDSnapshot, newVersion: number): TDSnapshot {
} }
if (shape.type === TDShapeType.Group && children) { if (shape.type === TDShapeType.Group && children) {
children.forEach((childId) => { children.forEach(childId => {
if (!page.shapes[childId]) { if (!page.shapes[childId]) {
console.warn('Encountered a parent with a missing child!', shape.id, childId) console.warn('Encountered a parent with a missing child!', shape.id, childId)
children?.splice(children.indexOf(childId), 1) children?.splice(children.indexOf(childId), 1)
@ -39,31 +38,31 @@ export function migrate(state: TDSnapshot, newVersion: number): TDSnapshot {
}) })
) )
Object.keys(document.assets).forEach((assetId) => { Object.keys(document.assets).forEach(assetId => {
if (!assetIdsInUse.has(assetId)) { if (!assetIdsInUse.has(assetId)) {
delete document.assets[assetId] delete document.assets[assetId]
} }
}) })
if (version === newVersion) return state if (version === newVersion) return document
if (version < 14) { if (version < 14) {
Object.values(document.pages).forEach((page) => { Object.values(document.pages).forEach(page => {
Object.values(page.shapes) Object.values(page.shapes)
.filter((shape) => shape.type === TDShapeType.Text) .filter(shape => shape.type === TDShapeType.Text)
.forEach((shape) => (shape as TextShape).style.font === FontStyle.Script) .forEach(shape => (shape as TextShape).style.font === FontStyle.Script)
}) })
} }
// Lowercase styles, move binding meta to binding // Lowercase styles, move binding meta to binding
if (version <= 13) { if (version <= 13) {
Object.values(document.pages).forEach((page) => { Object.values(document.pages).forEach(page => {
Object.values(page.bindings).forEach((binding) => { Object.values(page.bindings).forEach(binding => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
Object.assign(binding, (binding as any).meta) 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]) => { Object.entries(shape.style).forEach(([id, style]) => {
if (typeof style === 'string') { if (typeof style === 'string') {
// @ts-ignore // @ts-ignore
@ -96,8 +95,8 @@ export function migrate(state: TDSnapshot, newVersion: number): TDSnapshot {
document.assets = {} document.assets = {}
} }
Object.values(document.pages).forEach((page) => { Object.values(document.pages).forEach(page => {
Object.values(page.shapes).forEach((shape) => { Object.values(page.shapes).forEach(shape => {
if (version < 15.2) { if (version < 15.2) {
if (shape.type === TDShapeType.Image || shape.type === TDShapeType.Video) { if (shape.type === TDShapeType.Image || shape.type === TDShapeType.Video) {
shape.style.isFilled = true shape.style.isFilled = true
@ -118,13 +117,9 @@ export function migrate(state: TDSnapshot, newVersion: number): TDSnapshot {
}) })
}) })
if (version < 15.4) {
settings.dockPosition = 'bottom'
}
// Cleanup // Cleanup
Object.values(document.pageStates).forEach((pageState) => { Object.values(document.pageStates).forEach(pageState => {
pageState.selectedIds = pageState.selectedIds.filter((id) => { pageState.selectedIds = pageState.selectedIds.filter(id => {
return document.pages[pageState.id].shapes[id] !== undefined return document.pages[pageState.id].shapes[id] !== undefined
}) })
pageState.bindingId = undefined pageState.bindingId = undefined
@ -135,5 +130,5 @@ export function migrate(state: TDSnapshot, newVersion: number): TDSnapshot {
document.version = newVersion document.version = newVersion
return state return document
} }

View file

@ -54,7 +54,7 @@
"duplicate": "Duplicate", "duplicate": "Duplicate",
"cancel": "Cancel", "cancel": "Cancel",
"copy.invite.link": "Copy Invite Link", "copy.invite.link": "Copy Invite Link",
"copy.readonly.link": "Copy Read-only Link", "copy.readonly.link": "Copy ReadOnly Link",
"create.multiplayer.project": "Create a Multiplayer Project", "create.multiplayer.project": "Create a Multiplayer Project",
"copy.multiplayer.project": "Copy to Multiplayer Project", "copy.multiplayer.project": "Copy to Multiplayer Project",
"select": "Select", "select": "Select",