Merge branch 'main' into feat/save-project-as-support
This commit is contained in:
commit
d816c4567f
10 changed files with 297 additions and 3 deletions
|
@ -24,7 +24,7 @@
|
||||||
"start": "turbo run dev --filter=@tldraw/tldraw-example... --parallel",
|
"start": "turbo run dev --filter=@tldraw/tldraw-example... --parallel",
|
||||||
"start:core": "turbo run dev --filter=@tldraw/core-example-advanced*... --parallel",
|
"start:core": "turbo run dev --filter=@tldraw/core-example-advanced*... --parallel",
|
||||||
"start:www": "turbo run dev --filter=@tldraw/www... --parallel",
|
"start:www": "turbo run dev --filter=@tldraw/www... --parallel",
|
||||||
"start:vscode": "turbo run dev --filter=./apps/vscode/*... --parallel",
|
"start:vscode": "code apps/vscode/extension & turbo run dev --filter=./apps/vscode/*... --parallel",
|
||||||
"start:extension": "turbo run dev --filter=@tldraw/new-tab-extension... --parallel",
|
"start:extension": "turbo run dev --filter=@tldraw/new-tab-extension... --parallel",
|
||||||
"package:vscode": "turbo run package --filter=tldraw-vscode",
|
"package:vscode": "turbo run package --filter=tldraw-vscode",
|
||||||
"publish:vscode": "turbo run publish --filter=tldraw-vscode",
|
"publish:vscode": "turbo run publish --filter=tldraw-vscode",
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
"@tldraw/vec": "^1.7.1",
|
"@tldraw/vec": "^1.7.1",
|
||||||
"browser-fs-access": "^0.31.0",
|
"browser-fs-access": "^0.31.0",
|
||||||
"idb-keyval": "^6.1.0",
|
"idb-keyval": "^6.1.0",
|
||||||
|
"jsoncrush": "^1.1.8",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"perfect-freehand": "^1.1.0",
|
"perfect-freehand": "^1.1.0",
|
||||||
"react-error-boundary": "^3.1.4",
|
"react-error-boundary": "^3.1.4",
|
||||||
|
@ -99,7 +100,10 @@
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
"@tldraw/tldraw": "<rootDir>/src",
|
"@tldraw/tldraw": "<rootDir>/src",
|
||||||
"\\~(.*)": "<rootDir>/src/$1"
|
"\\~(.*)": "<rootDir>/src/$1"
|
||||||
}
|
},
|
||||||
|
"transformIgnorePatterns": [
|
||||||
|
"node_modules/(?!jsoncrush/JSONCrush.js)"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296"
|
"gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Renderer } from '@tldraw/core'
|
import { Renderer } from '@tldraw/core'
|
||||||
|
import JSONCrush from 'jsoncrush'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { ErrorBoundary as _Errorboundary } from 'react-error-boundary'
|
import { ErrorBoundary as _Errorboundary } from 'react-error-boundary'
|
||||||
import { IntlProvider } from 'react-intl'
|
import { IntlProvider } from 'react-intl'
|
||||||
|
@ -24,7 +25,7 @@ import { TDCallbacks, TldrawApp } from '~state'
|
||||||
import { TLDR } from '~state/TLDR'
|
import { TLDR } from '~state/TLDR'
|
||||||
import { shapeUtils } from '~state/shapes'
|
import { shapeUtils } from '~state/shapes'
|
||||||
import { dark, styled } from '~styles'
|
import { dark, styled } from '~styles'
|
||||||
import { TDDocument, TDStatus } from '~types'
|
import { TDDocument, TDPage, TDShape, TDStatus } from '~types'
|
||||||
|
|
||||||
const ErrorBoundary = _Errorboundary as any
|
const ErrorBoundary = _Errorboundary as any
|
||||||
|
|
||||||
|
@ -58,6 +59,12 @@ export interface TldrawProps extends TDCallbacks {
|
||||||
* (optional) Whether to show the multiplayer menu.
|
* (optional) Whether to show the multiplayer menu.
|
||||||
*/
|
*/
|
||||||
showMultiplayerMenu?: boolean
|
showMultiplayerMenu?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (optional) Whether to show the share menu.
|
||||||
|
*/
|
||||||
|
showShareMenu?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (optional) Whether to show the pages UI.
|
* (optional) Whether to show the pages UI.
|
||||||
*/
|
*/
|
||||||
|
@ -113,6 +120,7 @@ export function Tldraw({
|
||||||
autofocus = true,
|
autofocus = true,
|
||||||
showMenu = true,
|
showMenu = true,
|
||||||
showMultiplayerMenu = true,
|
showMultiplayerMenu = true,
|
||||||
|
showShareMenu = true,
|
||||||
showPages = true,
|
showPages = true,
|
||||||
showTools = true,
|
showTools = true,
|
||||||
showZoom = true,
|
showZoom = true,
|
||||||
|
@ -216,6 +224,36 @@ export function Tldraw({
|
||||||
setApp(newApp)
|
setApp(newApp)
|
||||||
}, [sId, id])
|
}, [sId, id])
|
||||||
|
|
||||||
|
// In dev, we need to delete the prefixed
|
||||||
|
const entry =
|
||||||
|
window.location.port === '5420'
|
||||||
|
? window.location.hash.replace('#/develop/', '')
|
||||||
|
: window.location.search
|
||||||
|
const urlSearchParams = new URLSearchParams(entry)
|
||||||
|
|
||||||
|
const encodedPage = urlSearchParams.get('d')
|
||||||
|
|
||||||
|
const decodedPage = JSONCrush.uncrush((encodedPage as string) ?? '')
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (decodedPage.length === 0) return
|
||||||
|
const state = JSON.parse(decodedPage) as Record<string, any>
|
||||||
|
if (Object.keys(state).length) {
|
||||||
|
app.ready.then(() => {
|
||||||
|
if ('page' in state) {
|
||||||
|
app.loadPageFromURL(state.page, state.pageState)
|
||||||
|
} else {
|
||||||
|
const nextDocument = state as TDDocument
|
||||||
|
if (nextDocument.id === app.document.id) {
|
||||||
|
app.updateDocument(nextDocument)
|
||||||
|
} else {
|
||||||
|
app.loadDocument(nextDocument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [app])
|
||||||
|
|
||||||
// Update the document if the `document` prop changes but the ids,
|
// Update the document if the `document` prop changes but the ids,
|
||||||
// are the same, or else load a new document if the ids are different.
|
// are the same, or else load a new document if the ids are different.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -329,6 +367,7 @@ export function Tldraw({
|
||||||
showPages={showPages}
|
showPages={showPages}
|
||||||
showMenu={showMenu}
|
showMenu={showMenu}
|
||||||
showMultiplayerMenu={showMultiplayerMenu}
|
showMultiplayerMenu={showMultiplayerMenu}
|
||||||
|
showShareMenu={showShareMenu}
|
||||||
showStyles={showStyles}
|
showStyles={showStyles}
|
||||||
showZoom={showZoom}
|
showZoom={showZoom}
|
||||||
showTools={showTools}
|
showTools={showTools}
|
||||||
|
@ -351,6 +390,7 @@ interface InnerTldrawProps {
|
||||||
showStyles: boolean
|
showStyles: boolean
|
||||||
showUI: boolean
|
showUI: boolean
|
||||||
showTools: boolean
|
showTools: boolean
|
||||||
|
showShareMenu: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const InnerTldraw = React.memo(function InnerTldraw({
|
const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
|
@ -359,6 +399,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
showPages,
|
showPages,
|
||||||
showMenu,
|
showMenu,
|
||||||
showMultiplayerMenu,
|
showMultiplayerMenu,
|
||||||
|
showShareMenu,
|
||||||
showZoom,
|
showZoom,
|
||||||
showStyles,
|
showStyles,
|
||||||
showTools,
|
showTools,
|
||||||
|
@ -561,6 +602,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
showPages={showPages}
|
showPages={showPages}
|
||||||
showMenu={showMenu}
|
showMenu={showMenu}
|
||||||
showMultiplayerMenu={showMultiplayerMenu}
|
showMultiplayerMenu={showMultiplayerMenu}
|
||||||
|
showShareMenu={showShareMenu}
|
||||||
showStyles={showStyles}
|
showStyles={showStyles}
|
||||||
showZoom={showZoom}
|
showZoom={showZoom}
|
||||||
/>
|
/>
|
||||||
|
|
114
packages/tldraw/src/components/Primitives/AlertDialog/Alert.tsx
Normal file
114
packages/tldraw/src/components/Primitives/AlertDialog/Alert.tsx
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
|
||||||
|
import * as React from 'react'
|
||||||
|
import { styled } from '~styles'
|
||||||
|
|
||||||
|
interface ContentProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
onClose?: () => void
|
||||||
|
container: any
|
||||||
|
}
|
||||||
|
|
||||||
|
function Content({ children, onClose, container }: ContentProps) {
|
||||||
|
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Escape':
|
||||||
|
onClose?.()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AlertDialogPrimitive.Portal container={container}>
|
||||||
|
<StyledOverlay />
|
||||||
|
<StyledContent onKeyDown={handleKeyDown}>{children}</StyledContent>
|
||||||
|
</AlertDialogPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledDescription = styled(AlertDialogPrimitive.Description, {
|
||||||
|
marginBottom: 20,
|
||||||
|
color: '$text',
|
||||||
|
fontSize: '$2',
|
||||||
|
lineHeight: 1.5,
|
||||||
|
textAlign: 'center',
|
||||||
|
minWidth: 0,
|
||||||
|
alignSelf: 'center',
|
||||||
|
maxWidth: '62%',
|
||||||
|
})
|
||||||
|
|
||||||
|
const AlertDialogRoot = AlertDialogPrimitive.Root
|
||||||
|
const AlertDialogContent = Content
|
||||||
|
const AlertDialogDescription = StyledDescription
|
||||||
|
const AlertDialogAction = AlertDialogPrimitive.Action
|
||||||
|
|
||||||
|
interface AlertProps {
|
||||||
|
container: any
|
||||||
|
description: string
|
||||||
|
open: boolean
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Alert = ({ container, description, open, onClose }: AlertProps) => {
|
||||||
|
return (
|
||||||
|
<AlertDialogRoot open={open}>
|
||||||
|
<AlertDialogContent onClose={onClose} container={container}>
|
||||||
|
<AlertDialogDescription>{description}</AlertDialogDescription>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AlertDialogAction asChild>
|
||||||
|
<Button css={{ backgroundColor: '#2F80ED', color: 'White' }} onClick={onClose}>
|
||||||
|
Ok
|
||||||
|
</Button>
|
||||||
|
</AlertDialogAction>
|
||||||
|
</div>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogRoot>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledOverlay = styled(AlertDialogPrimitive.Overlay, {
|
||||||
|
position: 'fixed',
|
||||||
|
inset: 0,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, .15)',
|
||||||
|
pointerEvents: 'all',
|
||||||
|
})
|
||||||
|
|
||||||
|
const StyledContent = styled(AlertDialogPrimitive.Content, {
|
||||||
|
position: 'fixed',
|
||||||
|
font: '$ui',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 'max-content',
|
||||||
|
padding: '$3',
|
||||||
|
pointerEvents: 'all',
|
||||||
|
backgroundColor: '$panel',
|
||||||
|
borderRadius: '$3',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontFamily: '$ui',
|
||||||
|
border: '1px solid $panelContrast',
|
||||||
|
boxShadow: '$panel',
|
||||||
|
})
|
||||||
|
|
||||||
|
const Button = styled('button', {
|
||||||
|
all: 'unset',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
borderRadius: '$2',
|
||||||
|
padding: '0 15px',
|
||||||
|
fontSize: '$1',
|
||||||
|
lineHeight: 1,
|
||||||
|
fontWeight: 'normal',
|
||||||
|
height: 36,
|
||||||
|
color: '$text',
|
||||||
|
cursor: 'pointer',
|
||||||
|
minWidth: 48,
|
||||||
|
width: 'max-content',
|
||||||
|
})
|
|
@ -1,2 +1,3 @@
|
||||||
export * from './AlertDialog'
|
export * from './AlertDialog'
|
||||||
export * from './FilenameDialog'
|
export * from './FilenameDialog'
|
||||||
|
export * from './Alert'
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
|
import { ClipboardIcon, Share1Icon } from '@radix-ui/react-icons'
|
||||||
|
import JSONCrush from 'jsoncrush'
|
||||||
|
import * as React from 'react'
|
||||||
|
import { FormattedMessage, useIntl } from 'react-intl'
|
||||||
|
import { Alert } from '~components/Primitives/AlertDialog'
|
||||||
|
import { DMContent, DMItem, DMTriggerIcon } from '~components/Primitives/DropdownMenu'
|
||||||
|
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
||||||
|
import { useTldrawApp } from '~hooks'
|
||||||
|
|
||||||
|
const ShareMenu = () => {
|
||||||
|
const app = useTldrawApp()
|
||||||
|
const intl = useIntl()
|
||||||
|
const currentPageId = app.appState.currentPageId
|
||||||
|
const pageDocument = app.document.pages[currentPageId]
|
||||||
|
const pageState = app.document.pageStates[currentPageId]
|
||||||
|
const [container, setContainer] = React.useState<any>(null)
|
||||||
|
const [openDialog, setOpenDialog] = React.useState(false)
|
||||||
|
|
||||||
|
const toggleOpenDialog = () => setOpenDialog(!openDialog)
|
||||||
|
|
||||||
|
const copyCurrentPageLink = () => {
|
||||||
|
const hasAsset = Object.entries(pageDocument.shapes).filter(
|
||||||
|
([_, value]) => value.assetId
|
||||||
|
).length
|
||||||
|
if (hasAsset) {
|
||||||
|
toggleOpenDialog()
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const state = {
|
||||||
|
page: {
|
||||||
|
...pageDocument,
|
||||||
|
},
|
||||||
|
pageState: {
|
||||||
|
...pageState,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const crushed = JSONCrush.crush(JSON.stringify(state))
|
||||||
|
const link = `${window.location.href}/?d=${encodeURIComponent(crushed)}`
|
||||||
|
navigator.clipboard.writeText(link)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyProjectLink = () => {
|
||||||
|
if (Object.keys(app.document.assets).length) {
|
||||||
|
toggleOpenDialog()
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const crushed = JSONCrush.crush(JSON.stringify(app.document))
|
||||||
|
const link = `${window.location.href}/?d=${encodeURIComponent(crushed)}`
|
||||||
|
navigator.clipboard.writeText(link)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropdownMenu.Root dir="ltr">
|
||||||
|
<DMTriggerIcon id="TD-MultiplayerMenuIcon">
|
||||||
|
<Share1Icon />
|
||||||
|
</DMTriggerIcon>
|
||||||
|
<DMContent
|
||||||
|
variant="menu"
|
||||||
|
id="TD-MultiplayerMenu"
|
||||||
|
side="bottom"
|
||||||
|
align="start"
|
||||||
|
sideOffset={4}
|
||||||
|
>
|
||||||
|
<DMItem id="TD-Multiplayer-CopyInviteLink" onClick={copyCurrentPageLink}>
|
||||||
|
<FormattedMessage id="copy.current.page.link" />
|
||||||
|
<SmallIcon>
|
||||||
|
<ClipboardIcon />
|
||||||
|
</SmallIcon>
|
||||||
|
</DMItem>
|
||||||
|
<DMItem id="TD-Multiplayer-CopyReadOnlyLink" onClick={copyProjectLink}>
|
||||||
|
<FormattedMessage id="copy.project.link" />
|
||||||
|
<SmallIcon>
|
||||||
|
<ClipboardIcon />
|
||||||
|
</SmallIcon>
|
||||||
|
</DMItem>
|
||||||
|
</DMContent>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
<div ref={setContainer} />
|
||||||
|
<Alert
|
||||||
|
container={container}
|
||||||
|
description={intl.formatMessage({ id: 'data.too.big.encoded' })}
|
||||||
|
open={openDialog}
|
||||||
|
onClose={toggleOpenDialog}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShareMenu
|
|
@ -7,6 +7,7 @@ import { styled } from '~styles'
|
||||||
import { Menu } from './Menu/Menu'
|
import { Menu } from './Menu/Menu'
|
||||||
import { MultiplayerMenu } from './MultiplayerMenu'
|
import { MultiplayerMenu } from './MultiplayerMenu'
|
||||||
import { PageMenu } from './PageMenu'
|
import { PageMenu } from './PageMenu'
|
||||||
|
import ShareMenu from './ShareMenu/ShareMenu'
|
||||||
import { StyleMenu } from './StyleMenu'
|
import { StyleMenu } from './StyleMenu'
|
||||||
import { ZoomMenu } from './ZoomMenu'
|
import { ZoomMenu } from './ZoomMenu'
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ interface TopPanelProps {
|
||||||
showStyles: boolean
|
showStyles: boolean
|
||||||
showZoom: boolean
|
showZoom: boolean
|
||||||
showMultiplayerMenu: boolean
|
showMultiplayerMenu: boolean
|
||||||
|
showShareMenu: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TopPanel({
|
export function TopPanel({
|
||||||
|
@ -26,6 +28,7 @@ export function TopPanel({
|
||||||
showStyles,
|
showStyles,
|
||||||
showZoom,
|
showZoom,
|
||||||
showMultiplayerMenu,
|
showMultiplayerMenu,
|
||||||
|
showShareMenu,
|
||||||
}: TopPanelProps) {
|
}: TopPanelProps) {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
|
||||||
|
@ -35,6 +38,7 @@ export function TopPanel({
|
||||||
<Panel side="left" id="TD-MenuPanel">
|
<Panel side="left" id="TD-MenuPanel">
|
||||||
{showMenu && <Menu readOnly={readOnly} />}
|
{showMenu && <Menu readOnly={readOnly} />}
|
||||||
{showMultiplayerMenu && <MultiplayerMenu />}
|
{showMultiplayerMenu && <MultiplayerMenu />}
|
||||||
|
{showShareMenu && <ShareMenu />}
|
||||||
{showPages && <PageMenu />}
|
{showPages && <PageMenu />}
|
||||||
</Panel>
|
</Panel>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1362,6 +1362,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
* @param document The document to load
|
* @param document The document to load
|
||||||
*/
|
*/
|
||||||
loadDocument = (document: TDDocument): this => {
|
loadDocument = (document: TDDocument): this => {
|
||||||
|
this.setIsLoading(true)
|
||||||
this.selectNone()
|
this.selectNone()
|
||||||
this.resetHistory()
|
this.resetHistory()
|
||||||
this.clearSelectHistory()
|
this.clearSelectHistory()
|
||||||
|
@ -1384,9 +1385,33 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
this.replaceState(migrate(state, TldrawApp.version), 'loaded_document')
|
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)
|
||||||
|
this.setIsLoading(false)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load content from URL
|
||||||
|
* @param page
|
||||||
|
* @param pageState
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
loadPageFromURL = (page: TDPage, pageState: Record<string, TLPageState>) => {
|
||||||
|
const pageId = page.id
|
||||||
|
const nextDocument = {
|
||||||
|
...this.state.document,
|
||||||
|
pageStates: {
|
||||||
|
...this.state.document.pageStates,
|
||||||
|
[pageId]: pageState,
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
...this.document.pages,
|
||||||
|
[pageId]: page,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.loadDocument(nextDocument as TDDocument)
|
||||||
|
this.persist({})
|
||||||
|
}
|
||||||
|
|
||||||
// Should we move this to the app layer? onSave, onSaveAs, etc?
|
// Should we move this to the app layer? onSave, onSaveAs, etc?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -406,6 +406,7 @@ TldrawTestApp {
|
||||||
"isPointing": false,
|
"isPointing": false,
|
||||||
"justSent": false,
|
"justSent": false,
|
||||||
"loadDocument": [Function],
|
"loadDocument": [Function],
|
||||||
|
"loadPageFromURL": [Function],
|
||||||
"loadRoom": [Function],
|
"loadRoom": [Function],
|
||||||
"mergeDocument": [Function],
|
"mergeDocument": [Function],
|
||||||
"metaKey": false,
|
"metaKey": false,
|
||||||
|
|
|
@ -118,6 +118,10 @@
|
||||||
"distribute.y": "Distribute Vertical",
|
"distribute.y": "Distribute Vertical",
|
||||||
"stretch.x": "Stretch Horizontal",
|
"stretch.x": "Stretch Horizontal",
|
||||||
"stretch.y": "Stretch Vertical",
|
"stretch.y": "Stretch Vertical",
|
||||||
|
"share": "Share",
|
||||||
|
"copy.current.page.link": "Copy Current Page Link",
|
||||||
|
"copy.project.link": "Copy Project Link",
|
||||||
|
"data.too.big.encoded": "Data is too big to be encoded into an URL. Do not include image or video!",
|
||||||
"dialog.save.firsttime": "Do you want to save your current project?",
|
"dialog.save.firsttime": "Do you want to save your current project?",
|
||||||
"dialog.save.again": "Do you want to save changes to your current project?",
|
"dialog.save.again": "Do you want to save changes to your current project?",
|
||||||
"dialog.cancel": "Cancel",
|
"dialog.cancel": "Cancel",
|
||||||
|
|
Loading…
Reference in a new issue