[improvement] Quality and UI cleanup (#846)
* remove sponsors, ui cleanup * fix radius * improve panel * remove cursor spline animations * migrate options * Switch hrs to divider * fix text color on menu dark mode * Remove option for clone handles * fix wheel * remove unused translations
This commit is contained in:
parent
014b07d417
commit
f0f545806a
78 changed files with 185 additions and 1191 deletions
|
@ -6,16 +6,9 @@ declare const window: Window & { app: TldrawApp }
|
||||||
|
|
||||||
interface EditorProps {
|
interface EditorProps {
|
||||||
id?: string
|
id?: string
|
||||||
isUser?: boolean
|
|
||||||
isSponsor?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Editor = ({
|
const Editor = ({ id = 'home', ...rest }: EditorProps & Partial<TldrawProps>) => {
|
||||||
id = 'home',
|
|
||||||
isUser = false,
|
|
||||||
isSponsor = false,
|
|
||||||
...rest
|
|
||||||
}: EditorProps & Partial<TldrawProps>) => {
|
|
||||||
const handleMount = React.useCallback((app: TldrawApp) => {
|
const handleMount = React.useCallback((app: TldrawApp) => {
|
||||||
window.app = app
|
window.app = app
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -30,7 +23,6 @@ const Editor = ({
|
||||||
id={id}
|
id={id}
|
||||||
autofocus
|
autofocus
|
||||||
onMount={handleMount}
|
onMount={handleMount}
|
||||||
showSponsorLink={!isSponsor}
|
|
||||||
onAssetUpload={onAssetUpload}
|
onAssetUpload={onAssetUpload}
|
||||||
{...fileSystemEvents}
|
{...fileSystemEvents}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,4 @@
|
||||||
import { Tldraw, TldrawApp, TldrawProps, useFileSystem } from '@tldraw/tldraw'
|
import { Tldraw, TldrawApp, TldrawProps, useFileSystem } from '@tldraw/tldraw'
|
||||||
import { useAccountHandlers } from 'hooks/useAccountHandlers'
|
|
||||||
import { useUploadAssets } from 'hooks/useUploadAssets'
|
import { useUploadAssets } from 'hooks/useUploadAssets'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as gtag from 'utils/gtag'
|
import * as gtag from 'utils/gtag'
|
||||||
|
@ -8,16 +7,9 @@ declare const window: Window & { app: TldrawApp }
|
||||||
|
|
||||||
interface EditorProps {
|
interface EditorProps {
|
||||||
id?: string
|
id?: string
|
||||||
isUser?: boolean
|
|
||||||
isSponsor?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Editor = ({
|
const Editor = ({ id = 'home', ...rest }: EditorProps & Partial<TldrawProps>) => {
|
||||||
id = 'home',
|
|
||||||
isUser = false,
|
|
||||||
isSponsor = false,
|
|
||||||
...rest
|
|
||||||
}: EditorProps & Partial<TldrawProps>) => {
|
|
||||||
const handleMount = React.useCallback((app: TldrawApp) => {
|
const handleMount = React.useCallback((app: TldrawApp) => {
|
||||||
window.app = app
|
window.app = app
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -34,8 +26,6 @@ const Editor = ({
|
||||||
|
|
||||||
const fileSystemEvents = useFileSystem()
|
const fileSystemEvents = useFileSystem()
|
||||||
|
|
||||||
const { onSignIn, onSignOut } = useAccountHandlers()
|
|
||||||
|
|
||||||
const { onAssetUpload } = useUploadAssets()
|
const { onAssetUpload } = useUploadAssets()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -45,9 +35,6 @@ const Editor = ({
|
||||||
autofocus
|
autofocus
|
||||||
onMount={handleMount}
|
onMount={handleMount}
|
||||||
onPersist={handlePersist}
|
onPersist={handlePersist}
|
||||||
showSponsorLink={!isSponsor}
|
|
||||||
onSignIn={isSponsor ? undefined : onSignIn}
|
|
||||||
onSignOut={isUser ? onSignOut : undefined}
|
|
||||||
onAssetUpload={onAssetUpload}
|
onAssetUpload={onAssetUpload}
|
||||||
{...fileSystemEvents}
|
{...fileSystemEvents}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
|
|
@ -1,39 +1,27 @@
|
||||||
|
import * as React from 'react'
|
||||||
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 { useMultiplayerAssets } from 'hooks/useMultiplayerAssets'
|
import { useMultiplayerAssets } from 'hooks/useMultiplayerAssets'
|
||||||
import { useMultiplayerState } from 'hooks/useMultiplayerState'
|
import { useMultiplayerState } from 'hooks/useMultiplayerState'
|
||||||
import { useUploadAssets } from 'hooks/useUploadAssets'
|
import { useUploadAssets } from 'hooks/useUploadAssets'
|
||||||
import React, { FC } from 'react'
|
|
||||||
import { styled } from 'styles'
|
import { styled } from 'styles'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
roomId: string
|
roomId: string
|
||||||
isUser: boolean
|
|
||||||
isSponsor: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MultiplayerEditor: FC<Props> = ({
|
const MultiplayerEditor = ({ roomId }: Props) => {
|
||||||
roomId,
|
|
||||||
isUser = false,
|
|
||||||
isSponsor = false,
|
|
||||||
}: {
|
|
||||||
roomId: string
|
|
||||||
isUser: boolean
|
|
||||||
isSponsor: boolean
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<RoomProvider id={roomId}>
|
<RoomProvider id={roomId}>
|
||||||
<Editor roomId={roomId} isSponsor={isSponsor} isUser={isUser} />
|
<Editor roomId={roomId} />
|
||||||
</RoomProvider>
|
</RoomProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inner Editor
|
// Inner Editor
|
||||||
|
|
||||||
function Editor({ roomId, isUser, isSponsor }: Props) {
|
function Editor({ roomId }: Props) {
|
||||||
const fileSystemEvents = useFileSystem()
|
const fileSystemEvents = useFileSystem()
|
||||||
const { onSignIn, onSignOut } = useAccountHandlers()
|
|
||||||
const { error, ...events } = useMultiplayerState(roomId)
|
const { error, ...events } = useMultiplayerState(roomId)
|
||||||
const { onAssetCreate, onAssetDelete } = useMultiplayerAssets()
|
const { onAssetCreate, onAssetDelete } = useMultiplayerAssets()
|
||||||
const { onAssetUpload } = useUploadAssets()
|
const { onAssetUpload } = useUploadAssets()
|
||||||
|
@ -46,9 +34,6 @@ function Editor({ roomId, isUser, isSponsor }: Props) {
|
||||||
autofocus
|
autofocus
|
||||||
disableAssets={false}
|
disableAssets={false}
|
||||||
showPages={false}
|
showPages={false}
|
||||||
showSponsorLink={!isSponsor}
|
|
||||||
onSignIn={isSponsor ? undefined : onSignIn}
|
|
||||||
onSignOut={isUser ? onSignOut : undefined}
|
|
||||||
onAssetCreate={onAssetCreate}
|
onAssetCreate={onAssetCreate}
|
||||||
onAssetDelete={onAssetDelete}
|
onAssetDelete={onAssetDelete}
|
||||||
onAssetUpload={onAssetUpload}
|
onAssetUpload={onAssetUpload}
|
||||||
|
|
|
@ -1,37 +1,25 @@
|
||||||
|
import * as React from 'react'
|
||||||
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 React, { FC } from 'react'
|
|
||||||
import { styled } from 'styles'
|
import { styled } from 'styles'
|
||||||
import { useReadOnlyMultiplayerState } from 'hooks/useReadOnlyMultiplayerState'
|
import { useReadOnlyMultiplayerState } from 'hooks/useReadOnlyMultiplayerState'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
roomId: string
|
roomId: string
|
||||||
isUser: boolean
|
|
||||||
isSponsor: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReadOnlyMultiplayerEditor: FC<Props> = ({
|
const ReadOnlyMultiplayerEditor = ({ roomId }: Props) => {
|
||||||
roomId,
|
|
||||||
isUser = false,
|
|
||||||
isSponsor = false,
|
|
||||||
}: {
|
|
||||||
roomId: string
|
|
||||||
isUser: boolean
|
|
||||||
isSponsor: boolean
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<RoomProvider id={roomId}>
|
<RoomProvider id={roomId}>
|
||||||
<ReadOnlyEditor roomId={roomId} isSponsor={isSponsor} isUser={isUser} />
|
<ReadOnlyEditor roomId={roomId} />
|
||||||
</RoomProvider>
|
</RoomProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inner Editor
|
// Inner Editor
|
||||||
|
|
||||||
function ReadOnlyEditor({ roomId, isUser, isSponsor }: Props) {
|
function ReadOnlyEditor({ roomId }: Props) {
|
||||||
const { onSaveProjectAs, onSaveProject } = useFileSystem()
|
const { onSaveProjectAs, onSaveProject } = useFileSystem()
|
||||||
const { onSignIn, onSignOut } = useAccountHandlers()
|
|
||||||
const { error, ...events } = useReadOnlyMultiplayerState(roomId)
|
const { error, ...events } = useReadOnlyMultiplayerState(roomId)
|
||||||
|
|
||||||
if (error) return <LoadingScreen>Error: {error.message}</LoadingScreen>
|
if (error) return <LoadingScreen>Error: {error.message}</LoadingScreen>
|
||||||
|
@ -42,9 +30,6 @@ function ReadOnlyEditor({ roomId, isUser, isSponsor }: Props) {
|
||||||
autofocus
|
autofocus
|
||||||
disableAssets={false}
|
disableAssets={false}
|
||||||
showPages={false}
|
showPages={false}
|
||||||
showSponsorLink={!isSponsor}
|
|
||||||
onSignIn={isSponsor ? undefined : onSignIn}
|
|
||||||
onSignOut={isUser ? onSignOut : undefined}
|
|
||||||
onSaveProjectAs={onSaveProjectAs}
|
onSaveProjectAs={onSaveProjectAs}
|
||||||
onSaveProject={onSaveProject}
|
onSaveProject={onSaveProject}
|
||||||
readOnly
|
readOnly
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import * as React from 'react'
|
|
||||||
import { signIn, signOut } from 'next-auth/react'
|
|
||||||
|
|
||||||
export function useAccountHandlers() {
|
|
||||||
const onSignIn = React.useCallback(() => {
|
|
||||||
signIn()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onSignOut = React.useCallback(() => {
|
|
||||||
signOut()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { onSignIn, onSignOut }
|
|
||||||
}
|
|
|
@ -28,7 +28,6 @@
|
||||||
"@stitches/react": "^1.2.8",
|
"@stitches/react": "^1.2.8",
|
||||||
"@tldraw/core": "*",
|
"@tldraw/core": "*",
|
||||||
"@tldraw/tldraw": "*",
|
"@tldraw/tldraw": "*",
|
||||||
"@types/next-auth": "^3.15.0",
|
|
||||||
"@types/react": "^18.0.12",
|
"@types/react": "^18.0.12",
|
||||||
"@types/react-dom": "^18.0.5",
|
"@types/react-dom": "^18.0.5",
|
||||||
"aws-sdk": "^2.1053.0",
|
"aws-sdk": "^2.1053.0",
|
||||||
|
@ -37,7 +36,6 @@
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "^3.3.4",
|
||||||
"next": "^12.1.6",
|
"next": "^12.1.6",
|
||||||
"next-auth": "^4.0.5",
|
|
||||||
"next-pwa": "^5.5.4",
|
"next-pwa": "^5.5.4",
|
||||||
"next-themes": "^0.0.15",
|
"next-themes": "^0.0.15",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
|
@ -49,4 +47,4 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"next-transpile-modules": "^9.0.0"
|
"next-transpile-modules": "^9.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
import { isSignedInUserSponsoringMe } from 'utils/github'
|
|
||||||
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
|
|
||||||
import NextAuth from 'next-auth'
|
|
||||||
import GithubProvider from 'next-auth/providers/github'
|
|
||||||
|
|
||||||
export default function Auth(
|
|
||||||
req: NextApiRequest,
|
|
||||||
res: NextApiResponse
|
|
||||||
): ReturnType<NextApiHandler> {
|
|
||||||
return NextAuth(req, res, {
|
|
||||||
theme: {
|
|
||||||
colorScheme: 'light',
|
|
||||||
},
|
|
||||||
providers: [
|
|
||||||
GithubProvider({
|
|
||||||
clientId: process.env.GITHUB_ID,
|
|
||||||
clientSecret: process.env.GITHUB_SECRET,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
scope: 'read:user',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
callbacks: {
|
|
||||||
async redirect({ baseUrl }) {
|
|
||||||
return baseUrl
|
|
||||||
},
|
|
||||||
async signIn() {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
async session({ session, token }) {
|
|
||||||
if (token) {
|
|
||||||
session.isSponsor = await isSignedInUserSponsoringMe()
|
|
||||||
}
|
|
||||||
return session
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
||||||
|
|
||||||
const AV_SIZE = 32
|
|
||||||
const PADDING = 4
|
|
||||||
const COLS = 16
|
|
||||||
|
|
||||||
type SponsorResult = { avatarUrl: string; login: string }
|
|
||||||
|
|
||||||
type QueryResult = {
|
|
||||||
node: { sponsorEntity: { avatarUrl: string; login: string } }
|
|
||||||
}
|
|
||||||
|
|
||||||
function getXY(i: number) {
|
|
||||||
return [(i % COLS) * (AV_SIZE + PADDING), Math.floor(i / COLS) * (AV_SIZE + PADDING)]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function GetSponsors(_req: NextApiRequest, res: NextApiResponse) {
|
|
||||||
const sponsorInfo = await fetch('https://api.github.com/graphql', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: 'bearer ' + process.env.GITHUB_SECRET,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
query: `{
|
|
||||||
viewer {
|
|
||||||
sponsors(first: 0) {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
sponsorshipsAsMaintainer(first: 100, orderBy: {
|
|
||||||
field:CREATED_AT,
|
|
||||||
direction:DESC
|
|
||||||
}) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
sponsorEntity {
|
|
||||||
...on User {
|
|
||||||
avatarUrl
|
|
||||||
login
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
}),
|
|
||||||
}).then((res) => res.json())
|
|
||||||
|
|
||||||
// Get the total count of sponsors
|
|
||||||
const totalCount: number = sponsorInfo.data.viewer.sponsors.totalCount
|
|
||||||
|
|
||||||
// Map out the login and avatarUrl for each sponsor
|
|
||||||
const sponsors = (
|
|
||||||
sponsorInfo.data.viewer.sponsorshipsAsMaintainer.edges as QueryResult[]
|
|
||||||
).map<SponsorResult>((edge) => ({
|
|
||||||
login: edge.node.sponsorEntity.login,
|
|
||||||
avatarUrl: edge.node.sponsorEntity.avatarUrl?.replace(/&/g, '&') ?? '',
|
|
||||||
}))
|
|
||||||
|
|
||||||
// If we're going to create a more link (see below), then make room for it if necessary
|
|
||||||
|
|
||||||
if (totalCount > 100 && sponsors.length % COLS <= 2) {
|
|
||||||
sponsors.pop()
|
|
||||||
sponsors.pop()
|
|
||||||
sponsors.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate images for each of the first 100 sponsors.
|
|
||||||
|
|
||||||
const avatars = sponsors
|
|
||||||
.map(({ avatarUrl, login }, i) => {
|
|
||||||
const [x, y] = getXY(i)
|
|
||||||
return `<image alt="${login}" href="${avatarUrl}" x="${x}" y="${y}" width="${AV_SIZE}" height="${AV_SIZE}"/>`
|
|
||||||
})
|
|
||||||
.join('')
|
|
||||||
|
|
||||||
// If there are more than 100 sponsors, generate some text to list how many more.
|
|
||||||
|
|
||||||
let more = ''
|
|
||||||
|
|
||||||
if (totalCount > sponsors.length) {
|
|
||||||
// More text
|
|
||||||
const [x, y] = getXY(sponsors.length)
|
|
||||||
const width = (AV_SIZE + PADDING) * 3
|
|
||||||
more = `<g transform="translate(${x},${y})"><text text-lenth="${width}" font-family="Arial" font-size="12px" font-weight="bold" text-anchor="middle" text-align="center" x="${
|
|
||||||
width / 2
|
|
||||||
}" y="${AV_SIZE / 2 + 3}">...and ${totalCount - sponsors.length} more!</text></g>`
|
|
||||||
}
|
|
||||||
|
|
||||||
const svgImage = `
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"><a href="https://github.com/sponsors/steveruizok">${avatars}${more}</a></svg>`
|
|
||||||
|
|
||||||
res
|
|
||||||
.status(200)
|
|
||||||
.setHeader('Cache-Control', 'max-age=604800')
|
|
||||||
.setHeader('Content-Type', 'image/svg+xml')
|
|
||||||
.send(svgImage)
|
|
||||||
}
|
|
|
@ -1,18 +1,11 @@
|
||||||
import type { GetServerSideProps } from 'next'
|
|
||||||
import { getSession } from 'next-auth/react'
|
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { FC, useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
const Editor = dynamic(() => import('components/Editor'), { ssr: false }) as any
|
const Editor = dynamic(() => import('components/Editor'), { ssr: false }) as any
|
||||||
|
|
||||||
interface PageProps {
|
const Home = () => {
|
||||||
isUser: boolean
|
|
||||||
isSponsor: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const Home: FC<PageProps> = ({ isUser, isSponsor }) => {
|
|
||||||
const { query } = useRouter()
|
const { query } = useRouter()
|
||||||
const isExportMode = useMemo(() => 'exportMode' in query, [query])
|
const isExportMode = useMemo(() => 'exportMode' in query, [query])
|
||||||
|
|
||||||
|
@ -21,20 +14,9 @@ const Home: FC<PageProps> = ({ isUser, isSponsor }) => {
|
||||||
<Head>
|
<Head>
|
||||||
<title>tldraw</title>
|
<title>tldraw</title>
|
||||||
</Head>
|
</Head>
|
||||||
<Editor id="home" isUser={isUser} isSponsor={isSponsor} showUI={!isExportMode} />
|
<Editor id="home" showUI={!isExportMode} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home
|
export default Home
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
|
||||||
const session = await getSession(context)
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isUser: session?.user ? true : false,
|
|
||||||
isSponsor: session?.isSponsor || false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import type { GetServerSideProps } from 'next'
|
import type { GetServerSideProps } from 'next'
|
||||||
import { getSession } from 'next-auth/react'
|
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
|
|
||||||
const IFrameWarning = dynamic(() => import('components/IFrameWarning'), {
|
const IFrameWarning = dynamic(() => import('components/IFrameWarning'), {
|
||||||
|
@ -13,27 +12,22 @@ const MultiplayerEditor = dynamic(() => import('components/MultiplayerEditor'),
|
||||||
|
|
||||||
interface RoomProps {
|
interface RoomProps {
|
||||||
id: string
|
id: string
|
||||||
isSponsor: boolean
|
|
||||||
isUser: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Room({ id, isUser, isSponsor }: RoomProps) {
|
export default function Room({ id }: RoomProps) {
|
||||||
if (typeof window !== 'undefined' && window.self !== window.top) {
|
if (typeof window !== 'undefined' && window.self !== window.top) {
|
||||||
return <IFrameWarning url={`https://tldraw.com/r/${id}`} />
|
return <IFrameWarning url={`https://tldraw.com/r/${id}`} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <MultiplayerEditor isUser={isUser} isSponsor={isSponsor} roomId={id} />
|
return <MultiplayerEditor roomId={id} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||||
const session = await getSession(context)
|
|
||||||
const id = context.query.id?.toString()
|
const id = context.query.id?.toString()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
id,
|
id,
|
||||||
isUser: session?.user ? true : false,
|
|
||||||
isSponsor: session?.isSponsor ?? false,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,205 +0,0 @@
|
||||||
import type { GetServerSideProps } from 'next'
|
|
||||||
import { getSession, signIn, signOut, useSession } from 'next-auth/react'
|
|
||||||
import Head from 'next/head'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import React, { FC } from 'react'
|
|
||||||
import { styled } from 'styles'
|
|
||||||
|
|
||||||
const Sponsorware: FC = () => {
|
|
||||||
const { data, status } = useSession()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>tldraw</title>
|
|
||||||
</Head>
|
|
||||||
<StyledOuterContent>
|
|
||||||
<StyledContent
|
|
||||||
size={{
|
|
||||||
'@sm': 'small',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h1>Tldraw (is sponsorware)</h1>
|
|
||||||
<p>
|
|
||||||
Hey, thanks for visiting <Link href="/">Tldraw</Link>, a tiny little drawing app by{' '}
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer nofollow noopener"
|
|
||||||
href="https://twitter.com/steveruizok"
|
|
||||||
>
|
|
||||||
steveruizok
|
|
||||||
</a>{' '}
|
|
||||||
and friends .
|
|
||||||
</p>
|
|
||||||
<video autoPlay muted playsInline onClick={(e) => e.currentTarget.play()}>
|
|
||||||
<source src="images/hello.mp4" type="video/mp4" />
|
|
||||||
</video>
|
|
||||||
<p>This project is currently: </p>
|
|
||||||
<ul>
|
|
||||||
<li>in development</li>
|
|
||||||
<li>only available for my sponsors</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
If you'd like to try it out,{' '}
|
|
||||||
<a
|
|
||||||
href="https://github.com/sponsors/steveruizok"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
sponsor me on GitHub
|
|
||||||
</a>{' '}
|
|
||||||
(at any level) and sign in below.
|
|
||||||
</p>
|
|
||||||
<StyledButtonGroup>
|
|
||||||
{data ? (
|
|
||||||
<>
|
|
||||||
<StyledButton variant="secondary" onClick={() => signOut()}>
|
|
||||||
Sign Out
|
|
||||||
</StyledButton>
|
|
||||||
<StyledDetail>
|
|
||||||
Signed in as {data.user?.name} ({data.user?.email}), but it looks like you're
|
|
||||||
not yet a sponsor.
|
|
||||||
<br />
|
|
||||||
Something wrong? Try <Link href="/">reloading the page</Link> or DM me on{' '}
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer nofollow noopener"
|
|
||||||
href="https://twitter.com/steveruizok"
|
|
||||||
>
|
|
||||||
Twitter
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</StyledDetail>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<StyledButton variant="primary" onClick={() => signIn('github')}>
|
|
||||||
{status === 'loading' ? 'Loading...' : 'Sign in with GitHub'}
|
|
||||||
</StyledButton>
|
|
||||||
<StyledDetail>Already a sponsor? Just sign in to visit the app.</StyledDetail>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</StyledButtonGroup>
|
|
||||||
</StyledContent>
|
|
||||||
</StyledOuterContent>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Sponsorware
|
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
|
||||||
const session = await getSession(context)
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
session,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const StyledOuterContent = styled('div', {
|
|
||||||
backgroundColor: '$canvas',
|
|
||||||
padding: '8px 8px 64px 8px',
|
|
||||||
margin: '0 auto',
|
|
||||||
overflow: 'scroll',
|
|
||||||
position: 'fixed',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
})
|
|
||||||
|
|
||||||
const StyledContent = styled('div', {
|
|
||||||
width: '720px',
|
|
||||||
padding: '8px 16px',
|
|
||||||
maxWidth: '100%',
|
|
||||||
backgroundColor: '$panel',
|
|
||||||
borderRadius: '4px',
|
|
||||||
boxShadow: '$12',
|
|
||||||
color: '$text',
|
|
||||||
fontSize: '$2',
|
|
||||||
fontFamily: '$body',
|
|
||||||
lineHeight: 1.5,
|
|
||||||
|
|
||||||
'& a': {
|
|
||||||
color: '$bounds',
|
|
||||||
backgroundColor: '$boundsBg',
|
|
||||||
padding: '2px 4px',
|
|
||||||
margin: '0 -3px',
|
|
||||||
borderRadius: '2px',
|
|
||||||
},
|
|
||||||
|
|
||||||
'& p': {
|
|
||||||
borderRadius: '8px',
|
|
||||||
},
|
|
||||||
|
|
||||||
'& video': {
|
|
||||||
maxWidth: '100%',
|
|
||||||
border: '1px solid $overlay',
|
|
||||||
borderRadius: '4px',
|
|
||||||
overflow: 'hidden',
|
|
||||||
margin: '16px 0',
|
|
||||||
},
|
|
||||||
|
|
||||||
'& iframe': {
|
|
||||||
border: 'none',
|
|
||||||
backgroundColor: 'none',
|
|
||||||
background: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
variants: {
|
|
||||||
size: {
|
|
||||||
small: {
|
|
||||||
fontSize: '$3',
|
|
||||||
padding: '32px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const StyledButtonGroup = styled('div', {
|
|
||||||
display: 'grid',
|
|
||||||
gap: '16px',
|
|
||||||
margin: '40px 0 32px 0',
|
|
||||||
})
|
|
||||||
|
|
||||||
const StyledDetail = styled('p', {
|
|
||||||
fontSize: '$2',
|
|
||||||
textAlign: 'center',
|
|
||||||
})
|
|
||||||
|
|
||||||
const StyledButton = styled('button', {
|
|
||||||
cursor: 'pointer',
|
|
||||||
width: '100%',
|
|
||||||
padding: '12px 0',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
font: '$ui',
|
|
||||||
fontSize: '$3',
|
|
||||||
color: '$panel',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '4px',
|
|
||||||
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
primary: {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
background: '$bounds',
|
|
||||||
color: '$panel',
|
|
||||||
boxShadow: '$4',
|
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
border: '1px solid $overlay',
|
|
||||||
background: 'transparent',
|
|
||||||
color: '$muted',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -1,6 +1,5 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import type { GetServerSideProps } from 'next'
|
import type { GetServerSideProps } from 'next'
|
||||||
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'
|
||||||
|
|
||||||
|
@ -14,27 +13,22 @@ const ReadOnlyMultiplayerEditor = dynamic(() => import('components/ReadOnlyMulti
|
||||||
|
|
||||||
interface RoomProps {
|
interface RoomProps {
|
||||||
id: string
|
id: string
|
||||||
isSponsor: boolean
|
|
||||||
isUser: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Room({ id, isUser, isSponsor }: RoomProps) {
|
export default function Room({ id }: RoomProps) {
|
||||||
if (typeof window !== 'undefined' && window.self !== window.top) {
|
if (typeof window !== 'undefined' && window.self !== window.top) {
|
||||||
return <IFrameWarning url={`https://tldraw.com/v/${id}`} />
|
return <IFrameWarning url={`https://tldraw.com/v/${id}`} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ReadOnlyMultiplayerEditor isUser={isUser} isSponsor={isSponsor} roomId={id} />
|
return <ReadOnlyMultiplayerEditor roomId={id} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||||
const session = await getSession(context)
|
|
||||||
const id = context.query.id?.toString()
|
const id = context.query.id?.toString()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
id: Utils.lns(id),
|
id: Utils.lns(id),
|
||||||
isUser: session?.user ? true : false,
|
|
||||||
isSponsor: session?.isSponsor ?? false,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M12.5 2.49538C12.2239 2.49538 12 2.71923 12 2.99538V5.49559H9.49979C9.22364 5.49559 8.99979 5.71945 8.99979 5.99559C8.99979 6.27173 9.22364 6.49559 9.49979 6.49559H12.5C12.7761 6.49559 13 6.27173 13 5.99559V2.99538C13 2.71923 12.7761 2.49538 12.5 2.49538Z" fill="black"/>
|
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M7.69698 2.04877C6.62345 1.89773 5.52991 2.09968 4.58113 2.62417C3.63236 3.14867 2.87973 3.9673 2.43667 4.95673C1.99361 5.94616 1.8841 7.05278 2.12465 8.10985C2.3652 9.16693 2.94278 10.1172 3.77036 10.8175C4.59794 11.5177 5.63069 11.9301 6.713 11.9924C7.79531 12.0547 8.86855 11.7635 9.77101 11.1628C10.6735 10.5621 11.3563 9.68441 11.7165 8.66191C11.8083 8.40146 11.6715 8.11593 11.4111 8.02417C11.1506 7.93241 10.8651 8.06916 10.7733 8.32961C10.4851 9.14762 9.93888 9.84981 9.21691 10.3304C8.49493 10.811 7.63632 11.0439 6.77046 10.994C5.9046 10.9442 5.07839 10.6143 4.41631 10.0541C3.75424 9.49386 3.29217 8.73363 3.09972 7.88796C2.90728 7.04229 2.99488 6.15698 3.34934 5.36542C3.7038 4.57387 4.30591 3.91895 5.06494 3.49935C5.82398 3.07974 6.69882 2.91819 7.55765 3.03902C8.41649 3.15985 9.21279 3.55653 9.82658 4.16928L9.83745 4.17981L12.1576 6.35996C12.3588 6.54906 12.6753 6.53921 12.8644 6.33797C13.0535 6.13673 13.0436 5.8203 12.8424 5.63121L10.5276 3.4561C9.76111 2.69329 8.76794 2.19945 7.69698 2.04877Z" fill="black"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,6 +0,0 @@
|
||||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M2 4.65555C2 4.37941 2.22386 4.15555 2.5 4.15555H12.2C12.4761 4.15555 12.7 4.37941 12.7 4.65555C12.7 4.93169 12.4761 5.15555 12.2 5.15555H2.5C2.22386 5.15555 2 4.93169 2 4.65555Z" fill="black"/>
|
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M6.27208 3C6.11885 3 5.97189 3.06087 5.86353 3.16923C5.75518 3.27758 5.6943 3.42454 5.6943 3.57778V4.15556H9.00542V3.57778C9.00542 3.42454 8.94454 3.27758 8.83619 3.16923C8.72783 3.06087 8.58087 3 8.42764 3H6.27208ZM10.0054 4.15556V3.57778C10.0054 3.15933 9.83919 2.75801 9.54329 2.46212C9.2474 2.16623 8.84609 2 8.42764 2H6.27208C5.85363 2 5.45232 2.16623 5.15642 2.46212C4.86053 2.75801 4.6943 3.15933 4.6943 3.57778V4.15556H3.57764C3.30149 4.15556 3.07764 4.37941 3.07764 4.65556V12.2C3.07764 12.6185 3.24387 13.0198 3.53976 13.3157C3.83565 13.6115 4.23696 13.7778 4.65541 13.7778H10.0443C10.4628 13.7778 10.8641 13.6115 11.16 13.3157C11.4559 13.0198 11.6221 12.6185 11.6221 12.2V4.65556C11.6221 4.37941 11.3982 4.15556 11.1221 4.15556H10.0054ZM4.07764 5.15556V12.2C4.07764 12.3532 4.13851 12.5002 4.24686 12.6086C4.35522 12.7169 4.50218 12.7778 4.65541 12.7778H10.0443C10.1975 12.7778 10.3445 12.7169 10.4529 12.6086C10.5612 12.5002 10.6221 12.3532 10.6221 12.2V5.15556H4.07764Z" fill="black"/>
|
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M6.27246 6.85001C6.5486 6.85001 6.77246 7.07386 6.77246 7.35001V10.5833C6.77246 10.8595 6.5486 11.0833 6.27246 11.0833C5.99632 11.0833 5.77246 10.8595 5.77246 10.5833V7.35001C5.77246 7.07386 5.99632 6.85001 6.27246 6.85001Z" fill="black"/>
|
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M8.42773 6.85001C8.70388 6.85001 8.92773 7.07386 8.92773 7.35001V10.5833C8.92773 10.8595 8.70388 11.0833 8.42773 11.0833C8.15159 11.0833 7.92773 10.8595 7.92773 10.5833V7.35001C7.92773 7.07386 8.15159 6.85001 8.42773 6.85001Z" fill="black"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.9 KiB |
|
@ -1,4 +0,0 @@
|
||||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M2.5 2.49538C2.77614 2.49538 3 2.71923 3 2.99538V5.49559H5.50021C5.77636 5.49559 6.00021 5.71945 6.00021 5.99559C6.00021 6.27173 5.77636 6.49559 5.50021 6.49559H2.5C2.22386 6.49559 2 6.27173 2 5.99559V2.99538C2 2.71923 2.22386 2.49538 2.5 2.49538Z" fill="black"/>
|
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M7.30302 2.04877C8.37655 1.89773 9.47009 2.09968 10.4189 2.62417C11.3676 3.14867 12.1203 3.9673 12.5633 4.95673C13.0064 5.94616 13.1159 7.05278 12.8753 8.10985C12.6348 9.16693 12.0572 10.1172 11.2296 10.8175C10.4021 11.5177 9.36931 11.9301 8.287 11.9924C7.20469 12.0547 6.13145 11.7635 5.22899 11.1628C4.32653 10.5621 3.64374 9.68441 3.2835 8.66191C3.19174 8.40146 3.32849 8.11593 3.58894 8.02417C3.84939 7.93241 4.13492 8.06916 4.22668 8.32961C4.51488 9.14762 5.06112 9.84981 5.78309 10.3304C6.50507 10.811 7.36368 11.0439 8.22954 10.994C9.0954 10.9442 9.92161 10.6143 10.5837 10.0541C11.2458 9.49386 11.7078 8.73363 11.9003 7.88796C12.0927 7.04229 12.0051 6.15698 11.6507 5.36542C11.2962 4.57387 10.6941 3.91895 9.93506 3.49935C9.17602 3.07974 8.30118 2.91819 7.44235 3.03902C6.58351 3.15985 5.78721 3.55653 5.17342 4.16928L5.16255 4.17981L2.84239 6.35996C2.64115 6.54906 2.32472 6.53921 2.13562 6.33797C1.94653 6.13673 1.95637 5.8203 2.15761 5.63121L4.47241 3.4561C5.23889 2.69329 6.23206 2.19945 7.30302 2.04877Z" fill="black"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,16 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg width="35px" height="35px" viewBox="0 0 35 35" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<defs></defs>
|
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fillRule="evenodd">
|
|
||||||
<g id="openhand">
|
|
||||||
<g id="bg-copy" fill="#FFFFFF" opacity="1">
|
|
||||||
<rect id="bg" x="0" y="0" width="35" height="35"></rect>
|
|
||||||
</g>
|
|
||||||
<path d="M13.5557,17.5742 C13.4577,17.1992 13.3597,16.7272 13.1497,16.0222 C12.9827,15.4652 12.8077,15.1632 12.6797,14.7892 C12.5247,14.3342 12.3767,14.0682 12.1837,13.6082 C12.0447,13.2792 11.8197,12.5602 11.7267,12.1682 C11.6077,11.6592 11.7597,11.2442 11.9707,10.9622 C12.2237,10.6232 12.9327,10.4722 13.3277,10.6112 C13.6987,10.7412 14.0717,11.1232 14.2437,11.3992 C14.5317,11.8592 14.6007,12.0312 14.9607,12.9412 C15.3537,13.9332 15.5247,14.8592 15.5717,15.1722 L15.6567,15.6242 C15.6557,15.5842 15.6137,14.5022 15.6127,14.4622 C15.5777,13.4332 15.5527,12.6392 15.5747,11.5232 C15.5767,11.3972 15.6387,10.9362 15.6587,10.8082 C15.7367,10.3082 15.9637,10.0082 16.3317,9.8292 C16.7437,9.6282 17.2577,9.6142 17.7327,9.8122 C18.1557,9.9852 18.3587,10.3622 18.4197,10.8342 C18.4337,10.9432 18.5137,11.8212 18.5127,11.9412 C18.4997,12.9662 18.5187,13.5822 18.5277,14.1152 C18.5317,14.3462 18.5307,15.7402 18.5447,15.5842 C18.6057,14.9282 18.6387,12.3952 18.8887,11.6422 C19.0327,11.2092 19.2937,10.8962 19.6827,10.7132 C20.1137,10.5102 20.7957,10.6432 21.0867,10.9562 C21.3717,11.2612 21.5327,11.6482 21.5687,12.1092 C21.6007,12.5142 21.5497,13.0062 21.5487,13.3542 C21.5487,14.2212 21.5277,14.6782 21.5117,15.4752 C21.5107,15.5132 21.4967,15.7732 21.5347,15.6572 C21.6287,15.3772 21.7227,15.1152 21.8007,14.9122 C21.8497,14.7872 22.0417,14.2982 22.1597,14.0532 C22.2737,13.8192 22.3707,13.6842 22.5747,13.3652 C22.7747,13.0522 22.9897,12.9172 23.2427,12.8042 C23.7827,12.5692 24.3517,12.9162 24.5437,13.3952 C24.6297,13.6102 24.5527,14.1082 24.5157,14.5002 C24.4547,15.1472 24.2617,15.8062 24.1637,16.1482 C24.0357,16.5952 23.8897,17.3832 23.8237,17.7492 C23.7517,18.1432 23.5897,19.1312 23.4647,19.5692 C23.3787,19.8702 23.0937,20.5472 22.8127,20.9532 C22.8127,20.9532 21.7387,22.2032 21.6207,22.7652 C21.5037,23.3282 21.5427,23.3322 21.5197,23.7302 C21.4957,24.1292 21.6407,24.6532 21.6407,24.6532 C21.6407,24.6532 20.8387,24.7572 20.4067,24.6872 C20.0157,24.6252 19.5317,23.8462 19.4067,23.6092 C19.2347,23.2812 18.8677,23.3442 18.7247,23.5862 C18.4997,23.9692 18.0157,24.6562 17.6737,24.6992 C17.0057,24.7832 15.6197,24.7292 14.5347,24.7192 C14.5347,24.7192 14.7197,23.7082 14.3077,23.3612 C14.0027,23.1012 13.4777,22.5772 13.1637,22.3012 L12.3317,21.3802 C12.0477,21.0202 11.7027,20.2872 11.0887,19.3952 C10.7407,18.8912 10.0617,18.3102 9.8047,17.8162 C9.5817,17.3912 9.4737,16.8622 9.6147,16.4912 C9.8397,15.8972 10.2897,15.5942 10.9767,15.6592 C11.4957,15.7092 11.8247,15.8652 12.2147,16.1962 C12.4397,16.3862 12.7877,16.7302 12.9647,16.9442 C13.1277,17.1392 13.1677,17.2202 13.3417,17.4532 C13.5717,17.7602 13.6437,17.9122 13.5557,17.5742" id="hand" fill="#FFFFFF"></path>
|
|
||||||
<path d="M13.5557,17.5742 C13.4577,17.1992 13.3597,16.7272 13.1497,16.0222 C12.9827,15.4652 12.8077,15.1632 12.6797,14.7892 C12.5247,14.3342 12.3767,14.0682 12.1837,13.6082 C12.0447,13.2792 11.8197,12.5602 11.7267,12.1682 C11.6077,11.6592 11.7597,11.2442 11.9707,10.9622 C12.2237,10.6232 12.9327,10.4722 13.3277,10.6112 C13.6987,10.7412 14.0717,11.1232 14.2437,11.3992 C14.5317,11.8592 14.6007,12.0312 14.9607,12.9412 C15.3537,13.9332 15.5247,14.8592 15.5717,15.1722 L15.6567,15.6242 C15.6557,15.5842 15.6137,14.5022 15.6127,14.4622 C15.5777,13.4332 15.5527,12.6392 15.5747,11.5232 C15.5767,11.3972 15.6387,10.9362 15.6587,10.8082 C15.7367,10.3082 15.9637,10.0082 16.3317,9.8292 C16.7437,9.6282 17.2577,9.6142 17.7327,9.8122 C18.1557,9.9852 18.3587,10.3622 18.4197,10.8342 C18.4337,10.9432 18.5137,11.8212 18.5127,11.9412 C18.4997,12.9662 18.5187,13.5822 18.5277,14.1152 C18.5317,14.3462 18.5307,15.7402 18.5447,15.5842 C18.6057,14.9282 18.6387,12.3952 18.8887,11.6422 C19.0327,11.2092 19.2937,10.8962 19.6827,10.7132 C20.1137,10.5102 20.7957,10.6432 21.0867,10.9562 C21.3717,11.2612 21.5327,11.6482 21.5687,12.1092 C21.6007,12.5142 21.5497,13.0062 21.5487,13.3542 C21.5487,14.2212 21.5277,14.6782 21.5117,15.4752 C21.5107,15.5132 21.4967,15.7732 21.5347,15.6572 C21.6287,15.3772 21.7227,15.1152 21.8007,14.9122 C21.8497,14.7872 22.0417,14.2982 22.1597,14.0532 C22.2737,13.8192 22.3707,13.6842 22.5747,13.3652 C22.7747,13.0522 22.9897,12.9172 23.2427,12.8042 C23.7827,12.5692 24.3517,12.9162 24.5437,13.3952 C24.6297,13.6102 24.5527,14.1082 24.5157,14.5002 C24.4547,15.1472 24.2617,15.8062 24.1637,16.1482 C24.0357,16.5952 23.8897,17.3832 23.8237,17.7492 C23.7517,18.1432 23.5897,19.1312 23.4647,19.5692 C23.3787,19.8702 23.0937,20.5472 22.8127,20.9532 C22.8127,20.9532 21.7387,22.2032 21.6207,22.7652 C21.5037,23.3282 21.5427,23.3322 21.5197,23.7302 C21.4957,24.1292 21.6407,24.6532 21.6407,24.6532 C21.6407,24.6532 20.8387,24.7572 20.4067,24.6872 C20.0157,24.6252 19.5317,23.8462 19.4067,23.6092 C19.2347,23.2812 18.8677,23.3442 18.7247,23.5862 C18.4997,23.9692 18.0157,24.6562 17.6737,24.6992 C17.0057,24.7832 15.6197,24.7292 14.5347,24.7192 C14.5347,24.7192 14.7197,23.7082 14.3077,23.3612 C14.0027,23.1012 13.4777,22.5772 13.1637,22.3012 L12.3317,21.3802 C12.0477,21.0202 11.7027,20.2872 11.0887,19.3952 C10.7407,18.8912 10.0617,18.3102 9.8047,17.8162 C9.5817,17.3912 9.4737,16.8622 9.6147,16.4912 C9.8397,15.8972 10.2897,15.5942 10.9767,15.6592 C11.4957,15.7092 11.8247,15.8652 12.2147,16.1962 C12.4397,16.3862 12.7877,16.7302 12.9647,16.9442 C13.1277,17.1392 13.1677,17.2202 13.3417,17.4532 C13.5717,17.7602 13.6437,17.9122 13.5557,17.5742" id="hand-border" stroke="#000000" stroke-width="0.75" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
||||||
<path d="M20.5664,21.7344 L20.5664,18.2754" id="line3" stroke="#000000" stroke-width="0.75" stroke-linecap="round"></path>
|
|
||||||
<path d="M18.5508,21.7461 L18.5348,18.2731" id="line2" stroke="#000000" stroke-width="0.75" stroke-linecap="round"></path>
|
|
||||||
<path d="M16.5547,18.3047 L16.5757,21.7307" id="line1" stroke="#000000" stroke-width="0.75" stroke-linecap="round"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 6.2 KiB |
|
@ -1,15 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg width="35px" height="35px" viewBox="0 0 35 35" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<defs></defs>
|
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fillRule="evenodd">
|
|
||||||
<g id="pointer">
|
|
||||||
<g id="bg" fill="#FFFFFF" opacity="0.00999999978">
|
|
||||||
<rect x="0" y="0" width="35" height="35"></rect>
|
|
||||||
</g>
|
|
||||||
<path d="M12,24.4219 L12,8.4069 L23.591,20.0259 L16.81,20.0259 L16.399,20.1499 L12,24.4219 Z" id="point-border" fill="#FFFFFF"></path>
|
|
||||||
<path d="M21.0845,25.0962 L17.4795,26.6312 L12.7975,15.5422 L16.4835,13.9892 L21.0845,25.0962 Z" id="stem-border" fill="#FFFFFF"></path>
|
|
||||||
<path d="M19.751,24.4155 L17.907,25.1895 L14.807,17.8155 L16.648,17.0405 L19.751,24.4155 Z" id="stem" fill="#000000"></path>
|
|
||||||
<path d="M13,10.814 L13,22.002 L15.969,19.136 L16.397,18.997 L21.165,18.997 L13,10.814 Z" id="point" fill="#000000"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1 KiB |
|
@ -1,13 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg width="35px" height="35px" viewBox="0 0 35 35" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<defs></defs>
|
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fillRule="evenodd">
|
|
||||||
<g id="resizenortheastsouthwest">
|
|
||||||
<g id="bg-copy" fill="#FFFFFF" opacity="1">
|
|
||||||
<rect id="bg" x="0" y="0" width="35" height="35"></rect>
|
|
||||||
</g>
|
|
||||||
<path d="M19.7432,17.0869 L15.6712,21.1549 L18.5002,23.9829 L10.0272,23.9699 L10.0142,15.4999 L12.8552,18.3419 L16.9302,14.2739 L18.3442,12.8589 L15.5002,10.0169 L23.9862,10.0169 L23.9862,18.5009 L21.1562,15.6739 L19.7432,17.0869 Z" id="resize-border" fill="#FFFFFF"></path>
|
|
||||||
<path d="M18.6826,16.7334 L14.2556,21.1574 L16.0836,22.9854 L11.0276,22.9694 L11.0136,17.9154 L12.8556,19.7564 L17.2836,15.3344 L19.7576,12.8594 L17.9136,11.0164 L22.9866,11.0164 L22.9866,16.0874 L21.1566,14.2594 L18.6826,16.7334 Z" id="resize" fill="#000000"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -35,15 +35,7 @@ export default function Develop() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tldraw">
|
<div className="tldraw">
|
||||||
<Tldraw
|
<Tldraw id="develop" {...fileSystemEvents} onMount={handleMount} onPersist={handlePersist} />
|
||||||
id="develop"
|
|
||||||
{...fileSystemEvents}
|
|
||||||
onMount={handleMount}
|
|
||||||
onSignIn={handleSignIn}
|
|
||||||
onSignOut={handleSignOut}
|
|
||||||
onPersist={handlePersist}
|
|
||||||
showSponsorLink={true}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ export default function Scroll() {
|
||||||
return (
|
return (
|
||||||
<div style={{ height: 1600, width: 1600, padding: 200 }}>
|
<div style={{ height: 1600, width: 1600, padding: 200 }}>
|
||||||
<div style={{ width: '100%', height: '100%', position: 'relative' }}>
|
<div style={{ width: '100%', height: '100%', position: 'relative' }}>
|
||||||
<Tldraw id="develop" onMount={handleMount} showSponsorLink={true} />
|
<Tldraw id="develop" onMount={handleMount} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
/* eslint-disable no-inner-declarations */
|
/* eslint-disable no-inner-declarations */
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
import Vec from '@tldraw/vec'
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCursorAnimation } from '~hooks'
|
|
||||||
import type { TLShape, TLUser } from '~types'
|
import type { TLShape, TLUser } from '~types'
|
||||||
|
|
||||||
interface UserProps {
|
interface UserProps {
|
||||||
|
@ -11,12 +9,17 @@ interface UserProps {
|
||||||
|
|
||||||
export function User({ user }: UserProps) {
|
export function User({ user }: UserProps) {
|
||||||
const rCursor = React.useRef<SVGSVGElement>(null)
|
const rCursor = React.useRef<SVGSVGElement>(null)
|
||||||
useCursorAnimation(rCursor, user.point, user.session)
|
|
||||||
|
React.useLayoutEffect(() => {
|
||||||
|
if (rCursor.current) {
|
||||||
|
rCursor.current.style.transform = `translate(${user.point[0]}px, ${user.point[1]}px)`
|
||||||
|
}
|
||||||
|
}, [user.point])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
ref={rCursor}
|
ref={rCursor}
|
||||||
className="tl-absolute tl-user tl-counter-scaled"
|
className={`tl-absolute tl-user tl-counter-scaled ${user.session ? '' : 'tl-animated'}`}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 35 35"
|
viewBox="0 0 35 35"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
|
|
@ -15,5 +15,4 @@ export * from './usePreventNavigationCss'
|
||||||
export * from './useBoundsEvents'
|
export * from './useBoundsEvents'
|
||||||
export * from './usePosition'
|
export * from './usePosition'
|
||||||
export * from './useKeyEvents'
|
export * from './useKeyEvents'
|
||||||
export * from './useCursorAnimation'
|
|
||||||
export * from './usePerformanceCss'
|
export * from './usePerformanceCss'
|
||||||
|
|
|
@ -1,163 +0,0 @@
|
||||||
import Vec from '@tldraw/vec'
|
|
||||||
import * as React from 'react'
|
|
||||||
|
|
||||||
type AnimationState = 'stopped' | 'idle' | 'animating'
|
|
||||||
|
|
||||||
type Animation = {
|
|
||||||
curve: boolean
|
|
||||||
from: number[]
|
|
||||||
to: number[]
|
|
||||||
start: number
|
|
||||||
distance: number
|
|
||||||
timeStamp: number
|
|
||||||
duration: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCursorAnimation(ref: any, point: number[], skip = false) {
|
|
||||||
const rState = React.useRef<AnimationState>('idle')
|
|
||||||
const rPrevPoint = React.useRef(point)
|
|
||||||
const rQueue = React.useRef<Animation[]>([])
|
|
||||||
const rTimestamp = React.useRef(performance.now())
|
|
||||||
const rLastRequestId = React.useRef<any>(0)
|
|
||||||
const rTimeoutId = React.useRef<any>(0)
|
|
||||||
const [spline] = React.useState(() => new Spline())
|
|
||||||
|
|
||||||
// When the point changes, add a new animation
|
|
||||||
React.useLayoutEffect(() => {
|
|
||||||
if (skip) {
|
|
||||||
const elm = ref.current
|
|
||||||
if (!elm) return
|
|
||||||
|
|
||||||
rState.current = 'stopped'
|
|
||||||
rPrevPoint.current = point
|
|
||||||
elm.style.setProperty('transform', `translate(${point[0]}px, ${point[1]}px)`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const animateNext = (animation: Animation) => {
|
|
||||||
const start = performance.now()
|
|
||||||
function loop() {
|
|
||||||
const t = (performance.now() - start) / animation.duration
|
|
||||||
if (t <= 1) {
|
|
||||||
const elm = ref.current
|
|
||||||
if (!elm) return
|
|
||||||
const point = animation.curve
|
|
||||||
? spline.getSplinePoint(t + animation.start)
|
|
||||||
: Vec.lrp(animation.from, animation.to, t)
|
|
||||||
elm.style.setProperty('transform', `translate(${point[0]}px, ${point[1]}px)`)
|
|
||||||
rLastRequestId.current = requestAnimationFrame(loop)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const next = rQueue.current.shift()
|
|
||||||
if (next) {
|
|
||||||
rState.current = 'animating'
|
|
||||||
animateNext(next)
|
|
||||||
} else {
|
|
||||||
rState.current = 'idle'
|
|
||||||
rTimeoutId.current = setTimeout(() => {
|
|
||||||
rState.current = 'stopped'
|
|
||||||
}, 250)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loop()
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = performance.now()
|
|
||||||
|
|
||||||
if (rState.current === 'stopped') {
|
|
||||||
rTimestamp.current = now
|
|
||||||
rPrevPoint.current = point
|
|
||||||
spline.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
spline.addPoint(point)
|
|
||||||
|
|
||||||
const animation: Animation = {
|
|
||||||
distance: spline.totalLength,
|
|
||||||
curve: spline.points.length > 3,
|
|
||||||
start: spline.points.length - 3,
|
|
||||||
from: rPrevPoint.current,
|
|
||||||
to: point,
|
|
||||||
timeStamp: now,
|
|
||||||
duration: Math.min(now - rTimestamp.current, 300),
|
|
||||||
}
|
|
||||||
|
|
||||||
rTimestamp.current = now
|
|
||||||
|
|
||||||
switch (rState.current) {
|
|
||||||
case 'stopped': {
|
|
||||||
rPrevPoint.current = point
|
|
||||||
rState.current = 'idle'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'idle': {
|
|
||||||
rState.current = 'animating'
|
|
||||||
animateNext(animation)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'animating': {
|
|
||||||
rPrevPoint.current = point
|
|
||||||
rQueue.current.push(animation)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return () => clearTimeout(rTimeoutId.current)
|
|
||||||
}, [skip, point, spline])
|
|
||||||
|
|
||||||
// React.useLayoutEffect(() => {
|
|
||||||
// const cursor = rCursor.current
|
|
||||||
// if (!cursor) return
|
|
||||||
|
|
||||||
// const [x, y] = user.point
|
|
||||||
// cursor.style.transform = `translate(${x}px, ${y}px)`
|
|
||||||
// }, [skip, point])
|
|
||||||
}
|
|
||||||
|
|
||||||
class Spline {
|
|
||||||
points: number[][] = []
|
|
||||||
lengths: number[] = []
|
|
||||||
totalLength = 0
|
|
||||||
|
|
||||||
private prev?: number[]
|
|
||||||
|
|
||||||
addPoint(point: number[]) {
|
|
||||||
if (this.prev) {
|
|
||||||
const length = Vec.dist(this.prev, point)
|
|
||||||
this.lengths.push(length)
|
|
||||||
this.totalLength += length
|
|
||||||
this.points.push(point)
|
|
||||||
}
|
|
||||||
this.prev = point
|
|
||||||
}
|
|
||||||
|
|
||||||
getSplinePoint(t: number): number[] {
|
|
||||||
const { points } = this
|
|
||||||
const l = points.length - 1
|
|
||||||
const d = Math.trunc(t)
|
|
||||||
const p1 = Math.min(d + 1, l)
|
|
||||||
const p2 = Math.min(p1 + 1, l)
|
|
||||||
const p3 = Math.min(p2 + 1, l)
|
|
||||||
const p0 = p1 - 1
|
|
||||||
t = t - d
|
|
||||||
const tt = t * t,
|
|
||||||
ttt = tt * t,
|
|
||||||
q1 = -ttt + 2 * tt - t,
|
|
||||||
q2 = 3 * ttt - 5 * tt + 2,
|
|
||||||
q3 = -3 * ttt + 4 * tt + t,
|
|
||||||
q4 = ttt - tt
|
|
||||||
|
|
||||||
if (!(points[p0] && points[p1] && points[p2] && points[p3])) {
|
|
||||||
return [0, 0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
0.5 * (points[p0][0] * q1 + points[p1][0] * q2 + points[p2][0] * q3 + points[p3][0] * q4),
|
|
||||||
0.5 * (points[p0][1] * q1 + points[p1][1] * q2 + points[p2][1] * q3 + points[p3][1] * q4),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.points = []
|
|
||||||
this.totalLength = 0
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -249,6 +249,9 @@ export const TLCSS = css`
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
.tl-animated {
|
||||||
|
transition: transform 200ms linear;
|
||||||
|
}
|
||||||
.tl-indicator {
|
.tl-indicator {
|
||||||
fill: transparent;
|
fill: transparent;
|
||||||
stroke-width: calc(1.5px * var(--tl-scale));
|
stroke-width: calc(1.5px * var(--tl-scale));
|
||||||
|
|
|
@ -36,12 +36,12 @@ export function useZoomEvents<T extends HTMLElement>(
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (inputs.isPinching) return
|
if (inputs.isPinching) return
|
||||||
|
|
||||||
const { offset } = normalizeWheel(e)
|
const [x, y, z] = normalizeWheel(e)
|
||||||
|
|
||||||
// alt+scroll or ctrl+scroll = zoom
|
// alt+scroll or ctrl+scroll = zoom
|
||||||
if ((e.altKey || e.ctrlKey || e.metaKey) && e.buttons === 0) {
|
if ((e.altKey || e.ctrlKey || e.metaKey) && e.buttons === 0) {
|
||||||
const point = inputs.pointer?.point ?? [bounds.width / 2, bounds.height / 2]
|
const point = inputs.pointer?.point ?? [bounds.width / 2, bounds.height / 2]
|
||||||
const delta = [...point, offset[1] * 0.618]
|
const delta = [...point, z * 0.618]
|
||||||
const info = inputs.pan(delta, e)
|
const info = inputs.pan(delta, e)
|
||||||
|
|
||||||
callbacks.onZoom?.({ ...info, delta }, e)
|
callbacks.onZoom?.({ ...info, delta }, e)
|
||||||
|
@ -52,9 +52,9 @@ export function useZoomEvents<T extends HTMLElement>(
|
||||||
const delta = Vec.mul(
|
const delta = Vec.mul(
|
||||||
e.shiftKey && !Utils.isDarwin
|
e.shiftKey && !Utils.isDarwin
|
||||||
? // shift+scroll = pan horizontally
|
? // shift+scroll = pan horizontally
|
||||||
[offset[1], 0]
|
[y, 0]
|
||||||
: // scroll = pan vertically (or in any direction on a trackpad)
|
: // scroll = pan vertically (or in any direction on a trackpad)
|
||||||
[...offset],
|
[x, y],
|
||||||
0.5
|
0.5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -146,45 +146,26 @@ export function useZoomEvents<T extends HTMLElement>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reasonable defaults
|
// Reasonable defaults
|
||||||
const PIXEL_STEP = 10
|
const MAX_ZOOM_STEP = 10
|
||||||
const LINE_HEIGHT = 40
|
|
||||||
const PAGE_HEIGHT = 800
|
|
||||||
|
|
||||||
function normalizeWheel(event: any) {
|
// Adapted from https://stackoverflow.com/a/13650579
|
||||||
let sX = 0,
|
function normalizeWheel(event: WheelEvent) {
|
||||||
sY = 0, // spinX, spinY
|
const { deltaY, deltaX } = event
|
||||||
pX = 0,
|
|
||||||
pY = 0 // pixelX, pixelY
|
|
||||||
|
|
||||||
// Legacy
|
let deltaZ = 0
|
||||||
if ('detail' in event) sY = event.detail
|
|
||||||
if ('wheelDelta' in event) sY = -event.wheelDelta / 120
|
|
||||||
if ('wheelDeltaY' in event) sY = -event.wheelDeltaY / 120
|
|
||||||
if ('wheelDeltaX' in event) sX = -event.wheelDeltaX / 120
|
|
||||||
|
|
||||||
// side scrolling on FF with DOMMouseScroll
|
if (event.ctrlKey || event.metaKey) {
|
||||||
if ('axis' in event && event.axis === event.HORIZONTAL_AXIS) {
|
const signY = Math.sign(event.deltaY)
|
||||||
sX = sY
|
const absDeltaY = Math.abs(event.deltaY)
|
||||||
sY = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pX = 'deltaX' in event ? event.deltaX : sX * PIXEL_STEP
|
let dy = deltaY
|
||||||
pY = 'deltaY' in event ? event.deltaY : sY * PIXEL_STEP
|
|
||||||
|
|
||||||
if ((pX || pY) && event.deltaMode) {
|
if (absDeltaY > MAX_ZOOM_STEP) {
|
||||||
if (event.deltaMode == 1) {
|
dy = MAX_ZOOM_STEP * signY
|
||||||
// delta in LINE units
|
|
||||||
pX *= LINE_HEIGHT
|
|
||||||
pY *= LINE_HEIGHT
|
|
||||||
} else {
|
|
||||||
// delta in PAGE units
|
|
||||||
pX *= PAGE_HEIGHT
|
|
||||||
pY *= PAGE_HEIGHT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deltaZ = dy
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall-back if spin cannot be determined
|
return [deltaX, deltaY, deltaZ]
|
||||||
if (pX && !sX) sX = pX < 1 ? -1 : 1
|
|
||||||
if (pY && !sY) sY = pY < 1 ? -1 : 1
|
|
||||||
return { spin: [sX, sY], offset: [pX, pY] }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1426,11 +1426,6 @@ left past the initial left edge) then swap points on that axis.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static isMobileSize() {
|
|
||||||
if (typeof window === 'undefined') return false
|
|
||||||
return window.innerWidth < 768
|
|
||||||
}
|
|
||||||
|
|
||||||
static isMobileSafari() {
|
static isMobileSafari() {
|
||||||
if (typeof window === 'undefined') return false
|
if (typeof window === 'undefined') return false
|
||||||
const ua = window.navigator.userAgent
|
const ua = window.navigator.userAgent
|
||||||
|
|
|
@ -74,11 +74,6 @@ export interface TldrawProps extends TDCallbacks {
|
||||||
*/
|
*/
|
||||||
showTools?: boolean
|
showTools?: boolean
|
||||||
|
|
||||||
/**
|
|
||||||
* (optional) Whether to show a sponsor link for Tldraw.
|
|
||||||
*/
|
|
||||||
showSponsorLink?: boolean
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (optional) Whether to show the UI.
|
* (optional) Whether to show the UI.
|
||||||
*/
|
*/
|
||||||
|
@ -118,7 +113,6 @@ export function Tldraw({
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
disableAssets = false,
|
disableAssets = false,
|
||||||
darkMode = false,
|
darkMode = false,
|
||||||
showSponsorLink,
|
|
||||||
onMount,
|
onMount,
|
||||||
onChange,
|
onChange,
|
||||||
onChangePresence,
|
onChangePresence,
|
||||||
|
@ -127,8 +121,6 @@ export function Tldraw({
|
||||||
onSaveProjectAs,
|
onSaveProjectAs,
|
||||||
onOpenProject,
|
onOpenProject,
|
||||||
onOpenMedia,
|
onOpenMedia,
|
||||||
onSignOut,
|
|
||||||
onSignIn,
|
|
||||||
onUndo,
|
onUndo,
|
||||||
onRedo,
|
onRedo,
|
||||||
onPersist,
|
onPersist,
|
||||||
|
@ -155,8 +147,6 @@ export function Tldraw({
|
||||||
onSaveProjectAs,
|
onSaveProjectAs,
|
||||||
onOpenProject,
|
onOpenProject,
|
||||||
onOpenMedia,
|
onOpenMedia,
|
||||||
onSignOut,
|
|
||||||
onSignIn,
|
|
||||||
onUndo,
|
onUndo,
|
||||||
onRedo,
|
onRedo,
|
||||||
onPersist,
|
onPersist,
|
||||||
|
@ -184,8 +174,6 @@ export function Tldraw({
|
||||||
onSaveProjectAs,
|
onSaveProjectAs,
|
||||||
onOpenProject,
|
onOpenProject,
|
||||||
onOpenMedia,
|
onOpenMedia,
|
||||||
onSignOut,
|
|
||||||
onSignIn,
|
|
||||||
onUndo,
|
onUndo,
|
||||||
onRedo,
|
onRedo,
|
||||||
onPersist,
|
onPersist,
|
||||||
|
@ -256,8 +244,6 @@ export function Tldraw({
|
||||||
onSaveProjectAs,
|
onSaveProjectAs,
|
||||||
onOpenProject,
|
onOpenProject,
|
||||||
onOpenMedia,
|
onOpenMedia,
|
||||||
onSignOut,
|
|
||||||
onSignIn,
|
|
||||||
onUndo,
|
onUndo,
|
||||||
onRedo,
|
onRedo,
|
||||||
onPersist,
|
onPersist,
|
||||||
|
@ -280,8 +266,6 @@ export function Tldraw({
|
||||||
onSaveProjectAs,
|
onSaveProjectAs,
|
||||||
onOpenProject,
|
onOpenProject,
|
||||||
onOpenMedia,
|
onOpenMedia,
|
||||||
onSignOut,
|
|
||||||
onSignIn,
|
|
||||||
onUndo,
|
onUndo,
|
||||||
onRedo,
|
onRedo,
|
||||||
onPersist,
|
onPersist,
|
||||||
|
@ -323,7 +307,6 @@ export function Tldraw({
|
||||||
showZoom={showZoom}
|
showZoom={showZoom}
|
||||||
showTools={showTools}
|
showTools={showTools}
|
||||||
showUI={showUI}
|
showUI={showUI}
|
||||||
showSponsorLink={showSponsorLink}
|
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
</TldrawContext.Provider>
|
</TldrawContext.Provider>
|
||||||
|
@ -341,7 +324,6 @@ interface InnerTldrawProps {
|
||||||
showStyles: boolean
|
showStyles: boolean
|
||||||
showUI: boolean
|
showUI: boolean
|
||||||
showTools: boolean
|
showTools: boolean
|
||||||
showSponsorLink?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const InnerTldraw = React.memo(function InnerTldraw({
|
const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
|
@ -353,7 +335,6 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
showZoom,
|
showZoom,
|
||||||
showStyles,
|
showStyles,
|
||||||
showTools,
|
showTools,
|
||||||
showSponsorLink,
|
|
||||||
readOnly,
|
readOnly,
|
||||||
showUI,
|
showUI,
|
||||||
}: InnerTldrawProps) {
|
}: InnerTldrawProps) {
|
||||||
|
@ -438,8 +419,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
const hideIndicators =
|
const hideIndicators =
|
||||||
(isInSession && state.appState.status !== TDStatus.Brushing) || !isSelecting
|
(isInSession && state.appState.status !== TDStatus.Brushing) || !isSelecting
|
||||||
|
|
||||||
const hideCloneHandles =
|
const hideCloneHandles = isInSession || !isSelecting || pageState.camera.zoom < 0.2
|
||||||
isInSession || !isSelecting || !settings.showCloneHandles || pageState.camera.zoom < 0.2
|
|
||||||
|
|
||||||
const translation = useTranslation(settings.language)
|
const translation = useTranslation(settings.language)
|
||||||
|
|
||||||
|
@ -551,7 +531,6 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||||
showMultiplayerMenu={showMultiplayerMenu}
|
showMultiplayerMenu={showMultiplayerMenu}
|
||||||
showStyles={showStyles}
|
showStyles={showStyles}
|
||||||
showZoom={showZoom}
|
showZoom={showZoom}
|
||||||
sponsor={showSponsorLink}
|
|
||||||
/>
|
/>
|
||||||
<StyledSpacer />
|
<StyledSpacer />
|
||||||
{showTools && !readOnly && <ToolsPanel />}
|
{showTools && !readOnly && <ToolsPanel />}
|
||||||
|
|
|
@ -2,11 +2,10 @@ import * as React from 'react'
|
||||||
import { styled } from '~styles'
|
import { styled } from '~styles'
|
||||||
|
|
||||||
export const Divider = styled('hr', {
|
export const Divider = styled('hr', {
|
||||||
height: 1,
|
height: 0,
|
||||||
marginTop: '$1',
|
paddingTop: 1,
|
||||||
marginRight: '-$0',
|
width: 'calc(100%+8px)',
|
||||||
marginBottom: '$1',
|
backgroundColor: '$hover',
|
||||||
marginLeft: '-$0',
|
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderBottom: '1px solid $hover',
|
margin: '$2 -4px',
|
||||||
})
|
})
|
||||||
|
|
|
@ -20,7 +20,7 @@ export const DMRadioItem = styled(RadioItem, {
|
||||||
isActive: {
|
isActive: {
|
||||||
true: {
|
true: {
|
||||||
backgroundColor: '$selected',
|
backgroundColor: '$selected',
|
||||||
color: '$panel',
|
color: 'white',
|
||||||
},
|
},
|
||||||
false: {},
|
false: {},
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,6 +10,7 @@ export const MenuContent = styled('div', {
|
||||||
minWidth: 180,
|
minWidth: 180,
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
backgroundColor: '$panel',
|
backgroundColor: '$panel',
|
||||||
|
border: '1px solid $panelContrast',
|
||||||
boxShadow: '$panel',
|
boxShadow: '$panel',
|
||||||
padding: '$2 $2',
|
padding: '$2 $2',
|
||||||
borderRadius: '$3',
|
borderRadius: '$3',
|
||||||
|
|
|
@ -8,27 +8,34 @@ export const Panel = styled('div', {
|
||||||
padding: '$2',
|
padding: '$2',
|
||||||
border: '1px solid $panelContrast',
|
border: '1px solid $panelContrast',
|
||||||
gap: 0,
|
gap: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
variants: {
|
variants: {
|
||||||
side: {
|
side: {
|
||||||
center: {
|
center: {
|
||||||
borderRadius: '$4',
|
borderRadius: 9,
|
||||||
},
|
},
|
||||||
left: {
|
left: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
borderTop: 0,
|
borderTop: 0,
|
||||||
borderLeft: 0,
|
borderLeft: 0,
|
||||||
borderTopRightRadius: '$1',
|
borderTopRightRadius: 0,
|
||||||
borderBottomRightRadius: '$3',
|
borderBottomRightRadius: 9,
|
||||||
borderBottomLeftRadius: '$1',
|
borderBottomLeftRadius: 0,
|
||||||
},
|
},
|
||||||
right: {
|
right: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
borderTop: 0,
|
borderTop: 0,
|
||||||
borderRight: 0,
|
borderRight: 0,
|
||||||
borderTopLeftRadius: '$1',
|
borderTopLeftRadius: 0,
|
||||||
borderBottomLeftRadius: '$3',
|
borderBottomLeftRadius: 9,
|
||||||
borderBottomRightRadius: '$1',
|
borderBottomRightRadius: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'& hr': {
|
||||||
|
height: 10,
|
||||||
|
width: '100%',
|
||||||
|
backgroundColor: 'red',
|
||||||
|
border: 'none',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,7 +12,6 @@ export interface RowButtonProps {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
kbd?: string
|
kbd?: string
|
||||||
variant?: 'wide' | 'styleMenu'
|
variant?: 'wide' | 'styleMenu'
|
||||||
isSponsor?: boolean
|
|
||||||
isActive?: boolean
|
isActive?: boolean
|
||||||
isWarning?: boolean
|
isWarning?: boolean
|
||||||
hasIndicator?: boolean
|
hasIndicator?: boolean
|
||||||
|
@ -147,12 +146,6 @@ export const StyledRowButton = styled('button', {
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
isSponsor: {
|
|
||||||
true: {
|
|
||||||
color: '#eb30a2',
|
|
||||||
},
|
|
||||||
false: {},
|
|
||||||
},
|
|
||||||
isWarning: {
|
isWarning: {
|
||||||
true: {
|
true: {
|
||||||
color: '$warn',
|
color: '$warn',
|
||||||
|
@ -165,34 +158,4 @@ export const StyledRowButton = styled('button', {
|
||||||
false: {},
|
false: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
compoundVariants: [
|
|
||||||
{
|
|
||||||
isActive: false,
|
|
||||||
isSponsor: true,
|
|
||||||
bp: 'small',
|
|
||||||
css: {
|
|
||||||
[`&:hover:not(:disabled) ${StyledRowButtonInner}`]: {
|
|
||||||
backgroundColor: '$sponsorContrast',
|
|
||||||
border: '1px solid $panel',
|
|
||||||
'& *[data-shy="true"]': {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isActive: false,
|
|
||||||
isSponsor: false,
|
|
||||||
bp: 'small',
|
|
||||||
css: {
|
|
||||||
[`&:hover:not(:disabled) ${StyledRowButtonInner}`]: {
|
|
||||||
backgroundColor: '$hover',
|
|
||||||
border: '1px solid $panel',
|
|
||||||
'& *[data-shy="true"]': {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,7 +10,6 @@ export interface ToolButtonProps {
|
||||||
onDoubleClick?: () => void
|
onDoubleClick?: () => void
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
isActive?: boolean
|
isActive?: boolean
|
||||||
isSponsor?: boolean
|
|
||||||
isToolLocked?: boolean
|
isToolLocked?: boolean
|
||||||
variant?: 'icon' | 'text' | 'circle' | 'primary'
|
variant?: 'icon' | 'text' | 'circle' | 'primary'
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
@ -29,7 +28,6 @@ export const ToolButton = React.forwardRef<HTMLButtonElement, ToolButtonProps>(
|
||||||
isToolLocked = false,
|
isToolLocked = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
isActive = false,
|
isActive = false,
|
||||||
isSponsor = false,
|
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
id,
|
id,
|
||||||
...rest
|
...rest
|
||||||
|
@ -40,7 +38,6 @@ export const ToolButton = React.forwardRef<HTMLButtonElement, ToolButtonProps>(
|
||||||
<StyledToolButton
|
<StyledToolButton
|
||||||
ref={ref}
|
ref={ref}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
isSponsor={isSponsor}
|
|
||||||
variant={variant}
|
variant={variant}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -129,9 +126,9 @@ export const StyledToolButton = styled('button', {
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
border: 'none',
|
|
||||||
height: '40px',
|
height: '40px',
|
||||||
width: '40px',
|
width: '40px',
|
||||||
|
border: '1px solid $panel',
|
||||||
'-webkit-tap-highlight-color': 'transparent',
|
'-webkit-tap-highlight-color': 'transparent',
|
||||||
'tap-highlight-color': 'transparent',
|
'tap-highlight-color': 'transparent',
|
||||||
|
|
||||||
|
@ -165,6 +162,7 @@ export const StyledToolButton = styled('button', {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
height: 32,
|
height: 32,
|
||||||
width: 32,
|
width: 32,
|
||||||
|
border: 'none',
|
||||||
[`& ${StyledToolButtonInner}`]: {
|
[`& ${StyledToolButtonInner}`]: {
|
||||||
border: '1px solid $panelContrast',
|
border: '1px solid $panelContrast',
|
||||||
borderRadius: '100%',
|
borderRadius: '100%',
|
||||||
|
@ -176,13 +174,6 @@ export const StyledToolButton = styled('button', {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
isSponsor: {
|
|
||||||
true: {
|
|
||||||
[`${StyledToolButtonInner}`]: {
|
|
||||||
backgroundColor: '$sponsorContrast',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
isActive: {
|
isActive: {
|
||||||
true: {},
|
true: {},
|
||||||
false: {},
|
false: {},
|
||||||
|
@ -234,7 +225,6 @@ export const StyledToolButton = styled('button', {
|
||||||
css: {
|
css: {
|
||||||
[`&:hover:not(:disabled) ${StyledToolButtonInner}`]: {
|
[`&:hover:not(:disabled) ${StyledToolButtonInner}`]: {
|
||||||
backgroundColor: '$hover',
|
backgroundColor: '$hover',
|
||||||
border: '1px solid $panel',
|
|
||||||
},
|
},
|
||||||
[`&:focus:not(:disabled) ${StyledToolButtonInner}`]: {
|
[`&:focus:not(:disabled) ${StyledToolButtonInner}`]: {
|
||||||
backgroundColor: '$hover',
|
backgroundColor: '$hover',
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import * as React from 'react'
|
|
||||||
|
|
||||||
export function HeartIcon() {
|
|
||||||
return (
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
import * as React from 'react'
|
|
||||||
|
|
||||||
export function IsFilledIcon() {
|
|
||||||
return (
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect
|
|
||||||
x="4"
|
|
||||||
y="4"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
rx="2"
|
|
||||||
strokeWidth="2"
|
|
||||||
fill="currentColor"
|
|
||||||
opacity=".9"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import * as React from 'react'
|
|
||||||
|
|
||||||
export function MultiplayerIcon() {
|
|
||||||
return (
|
|
||||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
clipRule="evenodd"
|
|
||||||
d="M5.73545 0.0494623C5.93029 -0.0395285 6.15915 -0.00663838 6.32104 0.133622L14.8111 7.48911C14.9804 7.63581 15.0432 7.87091 14.9696 8.08249C14.8959 8.29408 14.7007 8.4394 14.4769 8.44927L11.4564 8.58248L13.2249 12.4682C13.35 12.7431 13.2286 13.0675 12.9537 13.1927L10.9304 14.1145C10.6554 14.2397 10.331 14.1184 10.2057 13.8435L8.43441 9.95846L6.42883 12.0731L7.0482 13.434C7.17335 13.709 7.05195 14.0333 6.77702 14.1586L5.03829 14.9506C4.76335 15.0759 4.43894 14.9546 4.31361 14.6797L2.83886 11.445L1.13458 13.242C0.980476 13.4045 0.742914 13.4566 0.534904 13.3737C0.326893 13.2907 0.19043 13.0894 0.19043 12.8654V3.2218C0.19043 3.0076 0.315424 2.81309 0.510266 2.7241C0.705108 2.63511 0.933961 2.668 1.09586 2.80826L5.41561 6.55076V0.547159C5.41561 0.332956 5.54061 0.138453 5.73545 0.0494623ZM6.74741 10.147L6.50992 10.3974V10.1575V9.0621V8.94669V7.49882V1.74513L13.0561 7.41656L10.5986 7.52495C10.417 7.53296 10.2512 7.63066 10.1562 7.78567C10.1444 7.80505 10.1338 7.825 10.1245 7.84541C10.0596 7.98828 10.0589 8.15342 10.1247 8.2982V8.2982L12.0023 12.4236L10.9745 12.8919L9.09356 8.7663C9.0181 8.60079 8.86531 8.4836 8.68589 8.45361C8.50647 8.42363 8.32389 8.48478 8.19871 8.61676L8.019 8.80624L6.74741 10.147ZM5.41561 9.11037V7.99863L1.28473 4.41977V11.4934L2.60315 10.1033C2.72833 9.97131 2.91091 9.91016 3.09033 9.94014C3.26975 9.97013 3.42254 10.0873 3.498 10.2528L5.08241 13.728L5.82563 13.3895L4.24404 9.91441C4.16873 9.74894 4.18053 9.55687 4.27553 9.40186C4.37052 9.24685 4.53631 9.14915 4.71793 9.14114L5.41561 9.11037Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
import * as React from 'react'
|
|
||||||
|
|
||||||
export function QuestionMarkIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
width="8"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 4 8"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M1.15948 5.85192C1.05082 5.66856 0.972717 5.50556 0.925178 5.36295C0.884431 5.21354 0.864057 5.06074 0.864057 4.90454C0.864057 4.69401 0.921782 4.50046 1.03723 4.32389C1.15269 4.14731 1.2953 3.98093 1.46508 3.82473C1.63486 3.66174 1.80125 3.50214 1.96424 3.34594C2.13402 3.18295 2.27664 3.01317 2.39209 2.8366C2.50754 2.65324 2.56527 2.45629 2.56527 2.24576C2.56527 1.98769 2.48038 1.78056 2.31059 1.62436C2.1476 1.46137 1.93028 1.37988 1.65863 1.37988C1.48206 1.37988 1.32586 1.41044 1.19004 1.47156C1.05421 1.52589 0.911596 1.55305 0.762188 1.55305C0.572033 1.55305 0.429416 1.50551 0.334339 1.41044C0.239261 1.31536 0.191722 1.21009 0.191722 1.09464C0.191722 0.958818 0.252844 0.843366 0.375086 0.748289C0.50412 0.64642 0.701067 0.595485 0.965926 0.595485C1.30549 0.595485 1.6111 0.64642 1.88275 0.748289C2.1544 0.850157 2.38869 0.996169 2.58564 1.18632C2.78938 1.36969 2.94218 1.5904 3.04405 1.84847C3.15271 2.10654 3.20704 2.39517 3.20704 2.71436C3.20704 2.98601 3.14932 3.22031 3.03386 3.41725C2.91841 3.60741 2.7758 3.77719 2.60601 3.9266C2.44302 4.07601 2.27664 4.21862 2.10686 4.35445C1.93708 4.49027 1.79446 4.63628 1.67901 4.79248C1.56356 4.94189 1.50583 5.12186 1.50583 5.33239C1.50583 5.42067 1.50923 5.50896 1.51602 5.59725C1.5296 5.68553 1.54658 5.77042 1.56695 5.85192H1.15948ZM1.3734 7.8078C1.25116 7.8078 1.1391 7.78064 1.03723 7.72631C0.942156 7.67198 0.867452 7.59727 0.813122 7.50219C0.758792 7.40712 0.731627 7.30185 0.731627 7.1864C0.731627 7.06416 0.758792 6.95889 0.813122 6.87061C0.867452 6.77553 0.942156 6.70083 1.03723 6.6465C1.1391 6.59217 1.25116 6.565 1.3734 6.565C1.55677 6.565 1.70957 6.62612 1.83181 6.74837C1.95405 6.86382 2.01518 7.00983 2.01518 7.1864C2.01518 7.36297 1.95405 7.51238 1.83181 7.63462C1.70957 7.75008 1.55677 7.8078 1.3734 7.8078Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import * as React from 'react'
|
|
||||||
|
|
||||||
export function RedoIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
width={32}
|
|
||||||
height={32}
|
|
||||||
viewBox="0 0 15 15"
|
|
||||||
fill="currentColor"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path d="M4.32978 8.5081C4.32978 10.1923 5.70009 11.5625 7.38418 11.5625H8.46539C8.64456 11.5625 8.78975 11.4173 8.78975 11.2382V11.13C8.78975 10.9508 8.64457 10.8057 8.46539 10.8057H7.38418C6.11736 10.8057 5.08662 9.77492 5.08662 8.5081C5.08662 7.24128 6.11736 6.21054 7.38418 6.21054H9.37894L8.00515 7.58433C7.8576 7.73183 7.8576 7.97195 8.00515 8.11944C8.14833 8.26251 8.39751 8.2623 8.54036 8.11944L10.56 6.09971C10.6315 6.02824 10.6709 5.93321 10.6709 5.8321C10.6709 5.73106 10.6315 5.63598 10.56 5.56454L8.54025 3.54472C8.3974 3.40176 8.14801 3.40176 8.00513 3.54472C7.85758 3.69218 7.85758 3.93234 8.00513 4.07979L9.37892 5.45368H7.38418C5.70009 5.45368 4.32978 6.82393 4.32978 8.5081Z" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,14 +1,18 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
export function UndoIcon(props: React.SVGProps<SVGSVGElement>) {
|
export function UndoIcon({
|
||||||
|
flipHorizontal,
|
||||||
|
...props
|
||||||
|
}: React.SVGProps<SVGSVGElement> & { flipHorizontal?: boolean }) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width={32}
|
width={24}
|
||||||
height={32}
|
height={24}
|
||||||
viewBox="0 0 15 15"
|
viewBox="0 0 15 15"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
{...props}
|
{...props}
|
||||||
|
transform={flipHorizontal ? 'scale(-1, 1)' : undefined}
|
||||||
>
|
>
|
||||||
<path d="M10.6707 8.5081C10.6707 10.1923 9.3004 11.5625 7.61631 11.5625H6.5351C6.35593 11.5625 6.21074 11.4173 6.21074 11.2382V11.13C6.21074 10.9508 6.35591 10.8057 6.5351 10.8057H7.61631C8.88313 10.8057 9.91387 9.77492 9.91387 8.5081C9.91387 7.24128 8.88313 6.21054 7.61631 6.21054H5.62155L6.99534 7.58433C7.14289 7.73183 7.14289 7.97195 6.99534 8.11944C6.85216 8.26251 6.60298 8.2623 6.46013 8.11944L4.44045 6.09971C4.36898 6.02824 4.32959 5.93321 4.32959 5.8321C4.32959 5.73106 4.36898 5.63598 4.44045 5.56454L6.46024 3.54472C6.60309 3.40176 6.85248 3.40176 6.99535 3.54472C7.14291 3.69218 7.14291 3.93234 6.99535 4.07979L5.62156 5.45368H7.61631C9.3004 5.45368 10.6707 6.82393 10.6707 8.5081Z" />
|
<path d="M10.6707 8.5081C10.6707 10.1923 9.3004 11.5625 7.61631 11.5625H6.5351C6.35593 11.5625 6.21074 11.4173 6.21074 11.2382V11.13C6.21074 10.9508 6.35591 10.8057 6.5351 10.8057H7.61631C8.88313 10.8057 9.91387 9.77492 9.91387 8.5081C9.91387 7.24128 8.88313 6.21054 7.61631 6.21054H5.62155L6.99534 7.58433C7.14289 7.73183 7.14289 7.97195 6.99534 8.11944C6.85216 8.26251 6.60298 8.2623 6.46013 8.11944L4.44045 6.09971C4.36898 6.02824 4.32959 5.93321 4.32959 5.8321C4.32959 5.73106 4.36898 5.63598 4.44045 5.56454L6.46024 3.54472C6.60309 3.40176 6.85248 3.40176 6.99535 3.54472C7.14291 3.69218 7.14291 3.93234 6.99535 4.07979L5.62156 5.45368H7.61631C9.3004 5.45368 10.6707 6.82393 10.6707 8.5081Z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
@ -4,15 +4,11 @@ export * from './DashDashedIcon'
|
||||||
export * from './DashDottedIcon'
|
export * from './DashDottedIcon'
|
||||||
export * from './DashDrawIcon'
|
export * from './DashDrawIcon'
|
||||||
export * from './DashSolidIcon'
|
export * from './DashSolidIcon'
|
||||||
export * from './IsFilledIcon'
|
|
||||||
export * from './RedoIcon'
|
|
||||||
export * from './TrashIcon'
|
export * from './TrashIcon'
|
||||||
export * from './UndoIcon'
|
export * from './UndoIcon'
|
||||||
export * from './SizeSmallIcon'
|
export * from './SizeSmallIcon'
|
||||||
export * from './SizeMediumIcon'
|
export * from './SizeMediumIcon'
|
||||||
export * from './SizeLargeIcon'
|
export * from './SizeLargeIcon'
|
||||||
export * from './EraserIcon'
|
export * from './EraserIcon'
|
||||||
export * from './MultiplayerIcon'
|
|
||||||
export * from './DiscordIcon'
|
export * from './DiscordIcon'
|
||||||
export * from './LineIcon'
|
export * from './LineIcon'
|
||||||
export * from './QuestionMarkIcon'
|
|
||||||
|
|
|
@ -6,14 +6,19 @@ import { styled } from '~styles'
|
||||||
import { useTldrawApp } from '~hooks'
|
import { useTldrawApp } from '~hooks'
|
||||||
import { TDSnapshot } from '~types'
|
import { TDSnapshot } from '~types'
|
||||||
import { breakpoints } from '~components/breakpoints'
|
import { breakpoints } from '~components/breakpoints'
|
||||||
import { GitHubLogoIcon, QuestionMarkIcon, TwitterLogoIcon } from '@radix-ui/react-icons'
|
import {
|
||||||
|
GitHubLogoIcon,
|
||||||
|
HeartFilledIcon,
|
||||||
|
QuestionMarkIcon,
|
||||||
|
TwitterLogoIcon,
|
||||||
|
} from '@radix-ui/react-icons'
|
||||||
import { RowButton } from '~components/Primitives/RowButton'
|
import { RowButton } from '~components/Primitives/RowButton'
|
||||||
import { MenuContent } from '~components/Primitives/MenuContent'
|
import { MenuContent } from '~components/Primitives/MenuContent'
|
||||||
import { DMContent, DMDivider } from '~components/Primitives/DropdownMenu'
|
|
||||||
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
||||||
import { DiscordIcon } from '~components/Primitives/icons'
|
import { DiscordIcon } from '~components/Primitives/icons'
|
||||||
import { LanguageMenu } from '~components/TopPanel/LanguageMenu/LanguageMenu'
|
import { LanguageMenu } from '~components/TopPanel/LanguageMenu/LanguageMenu'
|
||||||
import { KeyboardShortcutDialog } from './keyboardShortcutDialog'
|
import { KeyboardShortcutDialog } from './keyboardShortcutDialog'
|
||||||
|
import { Divider } from '~components/Primitives/Divider'
|
||||||
|
|
||||||
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
|
||||||
|
@ -28,17 +33,17 @@ export function HelpPanel() {
|
||||||
return (
|
return (
|
||||||
<Popover.Root>
|
<Popover.Root>
|
||||||
<PopoverAnchor dir="ltr">
|
<PopoverAnchor dir="ltr">
|
||||||
<Popover.Trigger asChild dir="ltr">
|
<Popover.Trigger dir="ltr" asChild>
|
||||||
<HelpButton side={side} debug={isDebugMode} bp={breakpoints}>
|
<HelpButton side={side} debug={isDebugMode} bp={breakpoints}>
|
||||||
<QuestionMarkIcon />
|
<QuestionMarkIcon />
|
||||||
</HelpButton>
|
</HelpButton>
|
||||||
</Popover.Trigger>
|
</Popover.Trigger>
|
||||||
</PopoverAnchor>
|
</PopoverAnchor>
|
||||||
<Popover.Content dir="ltr">
|
<Popover.Content dir="ltr" asChild>
|
||||||
<StyledContent style={{ visibility: isKeyboardShortcutsOpen ? 'hidden' : 'visible' }}>
|
<StyledContent style={{ visibility: isKeyboardShortcutsOpen ? 'hidden' : 'visible' }}>
|
||||||
<LanguageMenuDropdown />
|
<LanguageMenuDropdown />
|
||||||
<KeyboardShortcutDialog onOpenChange={setIsKeyboardShortcutsOpen} />
|
<KeyboardShortcutDialog onOpenChange={setIsKeyboardShortcutsOpen} />
|
||||||
<DMDivider />
|
<Divider />
|
||||||
<Links />
|
<Links />
|
||||||
</StyledContent>
|
</StyledContent>
|
||||||
</Popover.Content>
|
</Popover.Content>
|
||||||
|
@ -60,9 +65,14 @@ const LanguageMenuDropdown = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const linksData = [
|
const linksData = [
|
||||||
{ id: 'github', title: 'Github', icon: GitHubLogoIcon, url: 'https://github.com/tldraw/tldraw' },
|
{ id: 'github', icon: GitHubLogoIcon, url: 'https://github.com/tldraw/tldraw' },
|
||||||
{ id: 'twitter', title: 'Twitter', icon: TwitterLogoIcon, url: 'https://twitter.com/tldraw' },
|
{ id: 'twitter', icon: TwitterLogoIcon, url: 'https://twitter.com/tldraw' },
|
||||||
{ id: 'discord', title: 'Discord', icon: DiscordIcon, url: 'https://discord.gg/SBBEVCA4PG' },
|
{ id: 'discord', icon: DiscordIcon, url: 'https://discord.gg/SBBEVCA4PG' },
|
||||||
|
{
|
||||||
|
id: 'become.a.sponsor',
|
||||||
|
icon: HeartFilledIcon,
|
||||||
|
url: 'https://github.com/sponsors/steveruizok',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const Links = () => {
|
const Links = () => {
|
||||||
|
@ -71,7 +81,7 @@ const Links = () => {
|
||||||
{linksData.map((item) => (
|
{linksData.map((item) => (
|
||||||
<a key={item.id} href={item.url} target="_blank" rel="nofollow">
|
<a key={item.id} href={item.url} target="_blank" rel="nofollow">
|
||||||
<RowButton id={`TD-Link-${item.id}`} variant="wide">
|
<RowButton id={`TD-Link-${item.id}`} variant="wide">
|
||||||
{item.title}
|
<FormattedMessage id={item.id} />
|
||||||
<SmallIcon>
|
<SmallIcon>
|
||||||
<item.icon />
|
<item.icon />
|
||||||
</SmallIcon>
|
</SmallIcon>
|
||||||
|
@ -92,10 +102,10 @@ const HelpButton = styled('button', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
border: 'none',
|
backgroundColor: '$panel',
|
||||||
backgroundColor: 'white',
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
boxShadow: '$panel',
|
boxShadow: '$panel',
|
||||||
|
border: '1px solid $panelContrast',
|
||||||
bottom: 10,
|
bottom: 10,
|
||||||
color: '$text',
|
color: '$text',
|
||||||
variants: {
|
variants: {
|
||||||
|
|
|
@ -136,7 +136,7 @@ const StyledPanel = styled(Panel, {
|
||||||
bp: {
|
bp: {
|
||||||
mobile: {
|
mobile: {
|
||||||
padding: '$0',
|
padding: '$0',
|
||||||
borderRadius: '$3',
|
borderRadius: '10px',
|
||||||
},
|
},
|
||||||
small: {
|
small: {
|
||||||
padding: '$2',
|
padding: '$2',
|
||||||
|
|
|
@ -83,6 +83,7 @@ export function KeyboardShortcutDialog({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Root onOpenChange={onOpenChange}>
|
<Dialog.Root onOpenChange={onOpenChange}>
|
||||||
|
{/* // todo: hide if no keyboard is attached */}
|
||||||
<Dialog.Trigger asChild>
|
<Dialog.Trigger asChild>
|
||||||
<RowButton id="TD-HelpItem-Keyboard" variant="wide">
|
<RowButton id="TD-HelpItem-Keyboard" variant="wide">
|
||||||
<FormattedMessage id="keyboard.shortcuts" />
|
<FormattedMessage id="keyboard.shortcuts" />
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { SmallIcon } from '~components/Primitives/SmallIcon'
|
||||||
import { useTldrawApp } from '~hooks'
|
import { useTldrawApp } from '~hooks'
|
||||||
import { TDLanguage, TRANSLATIONS } from '~translations'
|
import { TDLanguage, TRANSLATIONS } from '~translations'
|
||||||
import { TDSnapshot } from '~types'
|
import { TDSnapshot } from '~types'
|
||||||
|
import { Divider } from '~components/Primitives/Divider'
|
||||||
|
|
||||||
const languageSelector = (s: TDSnapshot) => s.settings.language
|
const languageSelector = (s: TDSnapshot) => s.settings.language
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ export function LanguageMenu() {
|
||||||
{label}
|
{label}
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
))}
|
))}
|
||||||
<DMDivider />
|
<Divider />
|
||||||
<a
|
<a
|
||||||
href="https://github.com/tldraw/tldraw/blob/main/guides/translation.md"
|
href="https://github.com/tldraw/tldraw/blob/main/guides/translation.md"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { ExitIcon, HamburgerMenuIcon } from '@radix-ui/react-icons'
|
import { HamburgerMenuIcon } from '@radix-ui/react-icons'
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { useTldrawApp } from '~hooks'
|
import { useTldrawApp } from '~hooks'
|
||||||
import { PreferencesMenu } from '../PreferencesMenu'
|
import { PreferencesMenu } from '../PreferencesMenu'
|
||||||
|
@ -10,7 +10,6 @@ import {
|
||||||
DMSubMenu,
|
DMSubMenu,
|
||||||
DMTriggerIcon,
|
DMTriggerIcon,
|
||||||
} from '~components/Primitives/DropdownMenu'
|
} from '~components/Primitives/DropdownMenu'
|
||||||
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
|
||||||
import { useFileSystemHandlers } from '~hooks'
|
import { useFileSystemHandlers } from '~hooks'
|
||||||
import { preventEvent } from '~components/preventEvent'
|
import { preventEvent } from '~components/preventEvent'
|
||||||
import { TDExportType, TDSnapshot } from '~types'
|
import { TDExportType, TDSnapshot } from '~types'
|
||||||
|
@ -18,7 +17,6 @@ import { Divider } from '~components/Primitives/Divider'
|
||||||
import { FormattedMessage, useIntl } from 'react-intl'
|
import { FormattedMessage, useIntl } from 'react-intl'
|
||||||
|
|
||||||
interface MenuProps {
|
interface MenuProps {
|
||||||
sponsor: boolean | undefined
|
|
||||||
readOnly: boolean
|
readOnly: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +28,7 @@ const disableAssetsSelector = (s: TDSnapshot) => {
|
||||||
return s.appState.disableAssets
|
return s.appState.disableAssets
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
export const Menu = React.memo(function Menu({ readOnly }: MenuProps) {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
|
@ -80,14 +78,6 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
app.exportJson()
|
app.exportJson()
|
||||||
}, [app])
|
}, [app])
|
||||||
|
|
||||||
const handleSignIn = React.useCallback(() => {
|
|
||||||
app.callbacks.onSignIn?.(app)
|
|
||||||
}, [app])
|
|
||||||
|
|
||||||
const handleSignOut = React.useCallback(() => {
|
|
||||||
app.callbacks.onSignOut?.(app)
|
|
||||||
}, [app])
|
|
||||||
|
|
||||||
const handleCut = React.useCallback(() => {
|
const handleCut = React.useCallback(() => {
|
||||||
app.cut()
|
app.cut()
|
||||||
}, [app])
|
}, [app])
|
||||||
|
@ -123,8 +113,6 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
app.callbacks.onSaveProjectAs ||
|
app.callbacks.onSaveProjectAs ||
|
||||||
app.callbacks.onExport
|
app.callbacks.onExport
|
||||||
|
|
||||||
const showSignInOutMenu = app.callbacks.onSignIn || app.callbacks.onSignOut
|
|
||||||
|
|
||||||
const hasSelection = numberOfSelectedIds > 0
|
const hasSelection = numberOfSelectedIds > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -186,7 +174,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
>
|
>
|
||||||
<FormattedMessage id="redo" />
|
<FormattedMessage id="redo" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMDivider dir="ltr" />
|
<Divider />
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
disabled={!hasSelection || readOnly}
|
disabled={!hasSelection || readOnly}
|
||||||
|
@ -213,7 +201,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
>
|
>
|
||||||
<FormattedMessage id="paste" />
|
<FormattedMessage id="paste" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMDivider dir="ltr" />
|
<Divider />
|
||||||
<DMSubMenu
|
<DMSubMenu
|
||||||
label={`${intl.formatMessage({ id: 'copy.as' })}...`}
|
label={`${intl.formatMessage({ id: 'copy.as' })}...`}
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -251,7 +239,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
</DMItem>
|
</DMItem>
|
||||||
</DMSubMenu>
|
</DMSubMenu>
|
||||||
|
|
||||||
<DMDivider dir="ltr" />
|
<Divider />
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
onClick={handleSelectAll}
|
onClick={handleSelectAll}
|
||||||
|
@ -268,7 +256,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
>
|
>
|
||||||
<FormattedMessage id="select.none" />
|
<FormattedMessage id="select.none" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMDivider dir="ltr" />
|
<Divider />
|
||||||
<DMItem onSelect={handleDelete} disabled={!hasSelection} kbd="⌫" id="TD-MenuItem-Delete">
|
<DMItem onSelect={handleDelete} disabled={!hasSelection} kbd="⌫" id="TD-MenuItem-Delete">
|
||||||
<FormattedMessage id="delete" />
|
<FormattedMessage id="delete" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
|
@ -315,26 +303,8 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
||||||
<FormattedMessage id="zoom.to.selection" />
|
<FormattedMessage id="zoom.to.selection" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
</DMSubMenu>
|
</DMSubMenu>
|
||||||
<DMDivider dir="ltr" />
|
<Divider />
|
||||||
<PreferencesMenu />
|
<PreferencesMenu />
|
||||||
{showSignInOutMenu && (
|
|
||||||
<>
|
|
||||||
<DMDivider dir="ltr" />{' '}
|
|
||||||
{app.callbacks.onSignIn && (
|
|
||||||
<DMItem onSelect={handleSignIn} id="TD-MenuItem-Sign_in">
|
|
||||||
<FormattedMessage id="menu.sign.in" />
|
|
||||||
</DMItem>
|
|
||||||
)}
|
|
||||||
{app.callbacks.onSignOut && (
|
|
||||||
<DMItem onSelect={handleSignOut} id="TD-MenuItem-Sign_out">
|
|
||||||
<FormattedMessage id="menu.sign.out" />
|
|
||||||
<SmallIcon>
|
|
||||||
<ExitIcon />
|
|
||||||
</SmallIcon>
|
|
||||||
</DMItem>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DMContent>
|
</DMContent>
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { TLDR } from '~state/TLDR'
|
||||||
import { Utils } from '@tldraw/core'
|
import { Utils } from '@tldraw/core'
|
||||||
import { FormattedMessage } from 'react-intl'
|
import { FormattedMessage } from 'react-intl'
|
||||||
import { MultiplayerIcon2 } from '~components/Primitives/icons/MultiplayerIcon2'
|
import { MultiplayerIcon2 } from '~components/Primitives/icons/MultiplayerIcon2'
|
||||||
|
import { Divider } from '~components/Primitives/Divider'
|
||||||
|
|
||||||
const roomSelector = (state: TDSnapshot) => state.room
|
const roomSelector = (state: TDSnapshot) => state.room
|
||||||
|
|
||||||
|
@ -124,7 +125,7 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() {
|
||||||
<FormattedMessage id="copy.readonly.link" />
|
<FormattedMessage id="copy.readonly.link" />
|
||||||
<SmallIcon>{copied ? <CheckIcon /> : <ClipboardIcon />}</SmallIcon>
|
<SmallIcon>{copied ? <CheckIcon /> : <ClipboardIcon />}</SmallIcon>
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMDivider id="TD-Multiplayer-CopyInviteLinkDivider" />
|
<Divider />
|
||||||
<DMItem
|
<DMItem
|
||||||
id="TD-Multiplayer-CreateMultiplayerProject"
|
id="TD-Multiplayer-CreateMultiplayerProject"
|
||||||
onClick={handleCreateMultiplayerProject}
|
onClick={handleCreateMultiplayerProject}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { SmallIcon } from '~components/Primitives/SmallIcon'
|
||||||
import { RowButton } from '~components/Primitives/RowButton'
|
import { RowButton } from '~components/Primitives/RowButton'
|
||||||
import { ToolButton } from '~components/Primitives/ToolButton'
|
import { ToolButton } from '~components/Primitives/ToolButton'
|
||||||
import { FormattedMessage, useIntl } from 'react-intl'
|
import { FormattedMessage, useIntl } from 'react-intl'
|
||||||
|
import { Divider } from '~components/Primitives/Divider'
|
||||||
|
|
||||||
const sortedSelector = (s: TDSnapshot) =>
|
const sortedSelector = (s: TDSnapshot) =>
|
||||||
Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0))
|
Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0))
|
||||||
|
@ -148,7 +149,7 @@ function PageMenuContent({ onClose }: { onClose: () => void }) {
|
||||||
</ButtonWithOptions>
|
</ButtonWithOptions>
|
||||||
))}
|
))}
|
||||||
</DropdownMenu.RadioGroup>
|
</DropdownMenu.RadioGroup>
|
||||||
<DMDivider />
|
<Divider />
|
||||||
<DropdownMenu.Item onSelect={handleCreatePage} asChild>
|
<DropdownMenu.Item onSelect={handleCreatePage} asChild>
|
||||||
<RowButton>
|
<RowButton>
|
||||||
<span>
|
<span>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { DMCheckboxItem, DMDivider, DMSubMenu } from '~components/Primitives/Dro
|
||||||
import { useTldrawApp } from '~hooks'
|
import { useTldrawApp } from '~hooks'
|
||||||
import { TDDockPosition, TDExportBackground, TDSnapshot } from '~types'
|
import { TDDockPosition, TDExportBackground, TDSnapshot } from '~types'
|
||||||
import { styled } from '~styles'
|
import { styled } from '~styles'
|
||||||
|
import { Divider } from '~components/Primitives/Divider'
|
||||||
|
|
||||||
const settingsSelector = (s: TDSnapshot) => s.settings
|
const settingsSelector = (s: TDSnapshot) => s.settings
|
||||||
|
|
||||||
|
@ -94,7 +95,7 @@ export function PreferencesMenu() {
|
||||||
>
|
>
|
||||||
<FormattedMessage id="preferences.debug.mode" />
|
<FormattedMessage id="preferences.debug.mode" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
<DMDivider />
|
<Divider />
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
checked={settings.showGrid}
|
checked={settings.showGrid}
|
||||||
onCheckedChange={toggleGrid}
|
onCheckedChange={toggleGrid}
|
||||||
|
@ -117,34 +118,6 @@ export function PreferencesMenu() {
|
||||||
>
|
>
|
||||||
<FormattedMessage id="preferences.keep.stylemenu.open" />
|
<FormattedMessage id="preferences.keep.stylemenu.open" />
|
||||||
</DMCheckboxItem>
|
</DMCheckboxItem>
|
||||||
<DMCheckboxItem
|
|
||||||
checked={settings.isSnapping}
|
|
||||||
onCheckedChange={toggleisSnapping}
|
|
||||||
id="TD-MenuItem-Preferences-Always_Show_Snaps"
|
|
||||||
>
|
|
||||||
<FormattedMessage id="preferences.always.show.snaps" />
|
|
||||||
</DMCheckboxItem>
|
|
||||||
<DMCheckboxItem
|
|
||||||
checked={settings.showRotateHandles}
|
|
||||||
onCheckedChange={toggleRotateHandle}
|
|
||||||
id="TD-MenuItem-Preferences-Rotate_Handles"
|
|
||||||
>
|
|
||||||
<FormattedMessage id="preferences.rotate.handles" />
|
|
||||||
</DMCheckboxItem>
|
|
||||||
<DMCheckboxItem
|
|
||||||
checked={settings.showBindingHandles}
|
|
||||||
onCheckedChange={toggleBoundShapesHandle}
|
|
||||||
id="TD-MenuItem-Preferences-Binding_Handles"
|
|
||||||
>
|
|
||||||
<FormattedMessage id="preferences.binding.handles" />
|
|
||||||
</DMCheckboxItem>
|
|
||||||
<DMCheckboxItem
|
|
||||||
checked={settings.showCloneHandles}
|
|
||||||
onCheckedChange={toggleCloneControls}
|
|
||||||
id="TD-MenuItem-Preferences-Clone_Handles"
|
|
||||||
>
|
|
||||||
<FormattedMessage id="preferences.clone.handles" />
|
|
||||||
</DMCheckboxItem>
|
|
||||||
<DMSubMenu label={intl.formatMessage({ id: 'dock.position' })} overflow={false}>
|
<DMSubMenu label={intl.formatMessage({ id: 'dock.position' })} overflow={false}>
|
||||||
{DockPosition.map((position) => (
|
{DockPosition.map((position) => (
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
|
|
|
@ -241,7 +241,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
||||||
size={18}
|
size={18}
|
||||||
strokeWidth={2.5}
|
strokeWidth={2.5}
|
||||||
fill={
|
fill={
|
||||||
displayedStyle.isFilled ? fills.light[style as ColorStyle] : 'transparent'
|
displayedStyle.isFilled ? fills[theme][style as ColorStyle] : 'transparent'
|
||||||
}
|
}
|
||||||
stroke={strokes.light[style as ColorStyle]}
|
stroke={strokes.light[style as ColorStyle]}
|
||||||
/>
|
/>
|
||||||
|
@ -337,7 +337,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<DMDivider />
|
<Divider />
|
||||||
<DMCheckboxItem
|
<DMCheckboxItem
|
||||||
variant="styleMenu"
|
variant="styleMenu"
|
||||||
checked={keepOpen}
|
checked={keepOpen}
|
||||||
|
@ -403,6 +403,7 @@ const OverlapIcons = styled('div', {
|
||||||
gridColumn: 1,
|
gridColumn: 1,
|
||||||
gridRow: 1,
|
gridRow: 1,
|
||||||
},
|
},
|
||||||
|
backgroundColor: '$panel',
|
||||||
})
|
})
|
||||||
|
|
||||||
const FontIcon = styled('div', {
|
const FontIcon = styled('div', {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { ZoomMenu } from './ZoomMenu'
|
||||||
import { StyleMenu } from './StyleMenu'
|
import { StyleMenu } from './StyleMenu'
|
||||||
import { Panel } from '~components/Primitives/Panel'
|
import { Panel } from '~components/Primitives/Panel'
|
||||||
import { ToolButton } from '~components/Primitives/ToolButton'
|
import { ToolButton } from '~components/Primitives/ToolButton'
|
||||||
import { RedoIcon, UndoIcon } from '~components/Primitives/icons'
|
import { UndoIcon } from '~components/Primitives/icons'
|
||||||
import { useTldrawApp } from '~hooks'
|
import { useTldrawApp } from '~hooks'
|
||||||
import { MultiplayerMenu } from './MultiplayerMenu'
|
import { MultiplayerMenu } from './MultiplayerMenu'
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ interface TopPanelProps {
|
||||||
showStyles: boolean
|
showStyles: boolean
|
||||||
showZoom: boolean
|
showZoom: boolean
|
||||||
showMultiplayerMenu: boolean
|
showMultiplayerMenu: boolean
|
||||||
sponsor?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TopPanel({
|
export function TopPanel({
|
||||||
|
@ -26,7 +25,6 @@ export function TopPanel({
|
||||||
showMenu,
|
showMenu,
|
||||||
showStyles,
|
showStyles,
|
||||||
showZoom,
|
showZoom,
|
||||||
sponsor,
|
|
||||||
showMultiplayerMenu,
|
showMultiplayerMenu,
|
||||||
}: TopPanelProps) {
|
}: TopPanelProps) {
|
||||||
const app = useTldrawApp()
|
const app = useTldrawApp()
|
||||||
|
@ -35,7 +33,7 @@ export function TopPanel({
|
||||||
<StyledTopPanel>
|
<StyledTopPanel>
|
||||||
{(showMenu || showPages) && (
|
{(showMenu || showPages) && (
|
||||||
<Panel side="left" id="TD-MenuPanel">
|
<Panel side="left" id="TD-MenuPanel">
|
||||||
{showMenu && <Menu sponsor={sponsor} readOnly={readOnly} />}
|
{showMenu && <Menu readOnly={readOnly} />}
|
||||||
{showMultiplayerMenu && <MultiplayerMenu />}
|
{showMultiplayerMenu && <MultiplayerMenu />}
|
||||||
{showPages && <PageMenu />}
|
{showPages && <PageMenu />}
|
||||||
</Panel>
|
</Panel>
|
||||||
|
@ -47,12 +45,11 @@ export function TopPanel({
|
||||||
<ReadOnlyLabel>Read Only</ReadOnlyLabel>
|
<ReadOnlyLabel>Read Only</ReadOnlyLabel>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{' '}
|
|
||||||
<ToolButton>
|
<ToolButton>
|
||||||
<UndoIcon onClick={app.undo} />
|
<UndoIcon onClick={app.undo} />
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
<ToolButton>
|
<ToolButton>
|
||||||
<RedoIcon onClick={app.redo} />
|
<UndoIcon onClick={app.redo} flipHorizontal />
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -30,10 +30,10 @@ export const ZoomMenu = React.memo(function ZoomMenu() {
|
||||||
<FormattedMessage id="zoom.out" />
|
<FormattedMessage id="zoom.out" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem onSelect={preventEvent} onClick={app.resetZoom} kbd="⇧0" id="TD-Zoom-Zoom_To_100%">
|
<DMItem onSelect={preventEvent} onClick={app.resetZoom} kbd="⇧0" id="TD-Zoom-Zoom_To_100%">
|
||||||
<FormattedMessage id="to" /> 100%
|
<FormattedMessage id="zoom.to" /> 100%
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem onSelect={preventEvent} onClick={app.zoomToFit} kbd="⇧1" id="TD-Zoom-To_Fit">
|
<DMItem onSelect={preventEvent} onClick={app.zoomToFit} kbd="⇧1" id="TD-Zoom-To_Fit">
|
||||||
<FormattedMessage id="to.fit" />
|
<FormattedMessage id="zoom.to.fit" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
<DMItem
|
<DMItem
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
|
@ -41,7 +41,7 @@ export const ZoomMenu = React.memo(function ZoomMenu() {
|
||||||
kbd="⇧2"
|
kbd="⇧2"
|
||||||
id="TD-Zoom-To_Selection"
|
id="TD-Zoom-To_Selection"
|
||||||
>
|
>
|
||||||
<FormattedMessage id="to.selection" />
|
<FormattedMessage id="zoom.to.selection" />
|
||||||
</DMItem>
|
</DMItem>
|
||||||
</DMContent>
|
</DMContent>
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
|
|
|
@ -119,14 +119,6 @@ export interface TDCallbacks {
|
||||||
* (optional) A callback to run when the opens a file to upload.
|
* (optional) A callback to run when the opens a file to upload.
|
||||||
*/
|
*/
|
||||||
onOpenMedia?: (app: TldrawApp) => void
|
onOpenMedia?: (app: TldrawApp) => void
|
||||||
/**
|
|
||||||
* (optional) A callback to run when the user signs in via the menu.
|
|
||||||
*/
|
|
||||||
onSignIn?: (app: TldrawApp) => void
|
|
||||||
/**
|
|
||||||
* (optional) A callback to run when the user signs out via the menu.
|
|
||||||
*/
|
|
||||||
onSignOut?: (app: TldrawApp) => void
|
|
||||||
/**
|
/**
|
||||||
* (optional) A callback to run when the state is patched.
|
* (optional) A callback to run when the state is patched.
|
||||||
*/
|
*/
|
||||||
|
@ -608,73 +600,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
/* ----------- Managing Multiplayer State ----------- */
|
/* ----------- Managing Multiplayer State ----------- */
|
||||||
|
|
||||||
private justSent = false
|
private justSent = false
|
||||||
private prevShapes = this.page.shapes
|
|
||||||
private prevBindings = this.page.bindings
|
|
||||||
private prevAssets = this.document.assets
|
|
||||||
|
|
||||||
// private broadcastPageChanges = () => {
|
|
||||||
// const visited = new Set<string>()
|
|
||||||
|
|
||||||
// const changedShapes: Record<string, TDShape | undefined> = {}
|
|
||||||
// const changedBindings: Record<string, TDBinding | undefined> = {}
|
|
||||||
// const changedAssets: Record<string, TDAsset | undefined> = {}
|
|
||||||
|
|
||||||
// this.shapes.forEach((shape) => {
|
|
||||||
// visited.add(shape.id)
|
|
||||||
// if (this.prevShapes[shape.id] !== shape) {
|
|
||||||
// changedShapes[shape.id] = shape
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// Object.keys(this.prevShapes)
|
|
||||||
// .filter((id) => !visited.has(id))
|
|
||||||
// .forEach((id) => {
|
|
||||||
// // After visiting all the current shapes, if we haven't visited a
|
|
||||||
// // previously present shape, then it was deleted
|
|
||||||
// changedShapes[id] = undefined
|
|
||||||
// })
|
|
||||||
|
|
||||||
// this.bindings.forEach((binding) => {
|
|
||||||
// visited.add(binding.id)
|
|
||||||
// if (this.prevBindings[binding.id] !== binding) {
|
|
||||||
// changedBindings[binding.id] = binding
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// Object.keys(this.prevBindings)
|
|
||||||
// .filter((id) => !visited.has(id))
|
|
||||||
// .forEach((id) => {
|
|
||||||
// // After visiting all the current bindings, if we haven't visited a
|
|
||||||
// // previously present shape, then it was deleted
|
|
||||||
// changedBindings[id] = undefined
|
|
||||||
// })
|
|
||||||
|
|
||||||
// this.assets.forEach((asset) => {
|
|
||||||
// visited.add(asset.id)
|
|
||||||
// if (this.prevAssets[asset.id] !== asset) {
|
|
||||||
// changedAssets[asset.id] = asset
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// Object.keys(this.prevAssets)
|
|
||||||
// .filter((id) => !visited.has(id))
|
|
||||||
// .forEach((id) => {
|
|
||||||
// changedAssets[id] = undefined
|
|
||||||
// })
|
|
||||||
|
|
||||||
// // Only trigger update if shapes or bindings have changed
|
|
||||||
// if (
|
|
||||||
// Object.keys(changedBindings).length > 0 ||
|
|
||||||
// Object.keys(changedShapes).length > 0 ||
|
|
||||||
// Object.keys(changedAssets).length > 0
|
|
||||||
// ) {
|
|
||||||
// this.justSent = true
|
|
||||||
// this.callbacks.onChangePage?.(this, changedShapes, changedBindings, changedAssets,)
|
|
||||||
// this.prevShapes = this.page.shapes
|
|
||||||
// this.prevBindings = this.page.bindings
|
|
||||||
// this.prevAssets = this.document.assets
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
getReservedContent = (coreReservedIds: string[], pageId = this.currentPageId) => {
|
getReservedContent = (coreReservedIds: string[], pageId = this.currentPageId) => {
|
||||||
const { bindings } = this.document.pages[pageId]
|
const { bindings } = this.document.pages[pageId]
|
||||||
|
@ -807,9 +732,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
// will have changed. This is important because we want to restore
|
// will have changed. This is important because we want to restore
|
||||||
// related shapes that may not have changed on our side, but which
|
// related shapes that may not have changed on our side, but which
|
||||||
// were deleted on the server.
|
// were deleted on the server.
|
||||||
this.prevShapes = shapes
|
|
||||||
this.prevBindings = bindings
|
|
||||||
this.prevAssets = assets
|
|
||||||
|
|
||||||
const nextShapes = {
|
const nextShapes = {
|
||||||
...shapes,
|
...shapes,
|
||||||
|
@ -914,8 +836,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
})
|
})
|
||||||
|
|
||||||
this.state.document = next.document
|
this.state.document = next.document
|
||||||
// this.prevShapes = nextShapes
|
|
||||||
// this.prevBindings = nextBindings
|
|
||||||
|
|
||||||
return next
|
return next
|
||||||
}, true)
|
}, true)
|
||||||
|
@ -1941,7 +1861,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'text/plain': {
|
case 'text/plain': {
|
||||||
console.log(str)
|
|
||||||
if (str.startsWith('<svg')) {
|
if (str.startsWith('<svg')) {
|
||||||
getSvgFromText(str)
|
getSvgFromText(str)
|
||||||
} else {
|
} else {
|
||||||
|
@ -2153,30 +2072,30 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
svg.setAttribute('height', commonBounds.height.toString())
|
svg.setAttribute('height', commonBounds.height.toString())
|
||||||
|
|
||||||
// Set export background
|
// Set export background
|
||||||
const exportBackground: TDExportBackground = this.settings.exportBackground;
|
const exportBackground: TDExportBackground = this.settings.exportBackground
|
||||||
const darkBackground = '#212529';
|
const darkBackground = '#212529'
|
||||||
const lightBackground = 'rgb(248, 249, 250)'
|
const lightBackground = 'rgb(248, 249, 250)'
|
||||||
|
|
||||||
switch(exportBackground) {
|
switch (exportBackground) {
|
||||||
case TDExportBackground.Auto: {
|
case TDExportBackground.Auto: {
|
||||||
svg.style.setProperty(
|
svg.style.setProperty(
|
||||||
'background-color',
|
'background-color',
|
||||||
this.settings.isDarkMode ? darkBackground : lightBackground
|
this.settings.isDarkMode ? darkBackground : lightBackground
|
||||||
)
|
)
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
case TDExportBackground.Dark: {
|
case TDExportBackground.Dark: {
|
||||||
svg.style.setProperty('background-color', darkBackground)
|
svg.style.setProperty('background-color', darkBackground)
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
case TDExportBackground.Light: {
|
case TDExportBackground.Light: {
|
||||||
svg.style.setProperty('background-color', lightBackground)
|
svg.style.setProperty('background-color', lightBackground)
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
case TDExportBackground.Transparent:
|
case TDExportBackground.Transparent:
|
||||||
default: {
|
default: {
|
||||||
svg.style.setProperty('background-color', 'transparent')
|
svg.style.setProperty('background-color', 'transparent')
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2384,7 +2303,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
opts
|
opts
|
||||||
|
|
||||||
const svg = await this.getSvg(ids, {
|
const svg = await this.getSvg(ids, {
|
||||||
includeFonts: format !== TDExportType.SVG
|
includeFonts: format !== TDExportType.SVG,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!svg) return
|
if (!svg) return
|
||||||
|
@ -4166,7 +4085,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
|
|
||||||
getShapeUtil = TLDR.getShapeUtil
|
getShapeUtil = TLDR.getShapeUtil
|
||||||
|
|
||||||
static version = 15.4
|
static version = 15.5
|
||||||
|
|
||||||
static defaultDocument: TDDocument = {
|
static defaultDocument: TDDocument = {
|
||||||
id: 'doc',
|
id: 'doc',
|
||||||
|
@ -4213,7 +4132,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||||
showGrid: false,
|
showGrid: false,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
dockPosition: 'bottom',
|
dockPosition: 'bottom',
|
||||||
exportBackground: TDExportBackground.Transparent
|
exportBackground: TDExportBackground.Transparent,
|
||||||
},
|
},
|
||||||
appState: {
|
appState: {
|
||||||
status: TDStatus.Idle,
|
status: TDStatus.Idle,
|
||||||
|
|
|
@ -50,7 +50,7 @@ TldrawTestApp {
|
||||||
"shapes": Object {},
|
"shapes": Object {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"version": 15.4,
|
"version": 15.5,
|
||||||
},
|
},
|
||||||
"settings": Object {
|
"settings": Object {
|
||||||
"dockPosition": "bottom",
|
"dockPosition": "bottom",
|
||||||
|
@ -202,7 +202,7 @@ TldrawTestApp {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"version": 15.4,
|
"version": 15.5,
|
||||||
},
|
},
|
||||||
"settings": Object {
|
"settings": Object {
|
||||||
"dockPosition": "bottom",
|
"dockPosition": "bottom",
|
||||||
|
@ -375,7 +375,7 @@ TldrawTestApp {
|
||||||
"shapes": Object {},
|
"shapes": Object {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"version": 15.4,
|
"version": 15.5,
|
||||||
},
|
},
|
||||||
"settings": Object {
|
"settings": Object {
|
||||||
"dockPosition": "bottom",
|
"dockPosition": "bottom",
|
||||||
|
@ -495,10 +495,7 @@ TldrawTestApp {
|
||||||
"pointShape": [Function],
|
"pointShape": [Function],
|
||||||
"pointer": -1,
|
"pointer": -1,
|
||||||
"pressKey": [Function],
|
"pressKey": [Function],
|
||||||
"prevAssets": Object {},
|
|
||||||
"prevBindings": Object {},
|
|
||||||
"prevSelectedIds": Array [],
|
"prevSelectedIds": Array [],
|
||||||
"prevShapes": Object {},
|
|
||||||
"previousPoint": Array [
|
"previousPoint": Array [
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
/* 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,
|
||||||
|
TDExportBackground,
|
||||||
|
TDShapeType,
|
||||||
|
TDSnapshot,
|
||||||
|
TextShape,
|
||||||
|
} from '~types'
|
||||||
|
|
||||||
export function migrate(state: TDSnapshot, newVersion: number): TDSnapshot {
|
export function migrate(state: TDSnapshot, newVersion: number): TDSnapshot {
|
||||||
const { document, settings } = state
|
const { document, settings } = state
|
||||||
|
@ -122,6 +130,10 @@ export function migrate(state: TDSnapshot, newVersion: number): TDSnapshot {
|
||||||
settings.dockPosition = 'bottom'
|
settings.dockPosition = 'bottom'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (version < 15.5) {
|
||||||
|
settings.exportBackground = TDExportBackground.Transparent
|
||||||
|
}
|
||||||
|
|
||||||
// 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) => {
|
||||||
|
|
|
@ -23,7 +23,7 @@ export class ImageUtil extends TDShapeUtil<T, E> {
|
||||||
|
|
||||||
isAspectRatioLocked = true
|
isAspectRatioLocked = true
|
||||||
|
|
||||||
showCloneHandles = true
|
showCloneHandles = false
|
||||||
|
|
||||||
getShape = (props: Partial<T>): T => {
|
getShape = (props: Partial<T>): T => {
|
||||||
return Utils.deepMerge<T>(
|
return Utils.deepMerge<T>(
|
||||||
|
@ -46,6 +46,7 @@ export class ImageUtil extends TDShapeUtil<T, E> {
|
||||||
Component = TDShapeUtil.Component<T, E, TDMeta>(
|
Component = TDShapeUtil.Component<T, E, TDMeta>(
|
||||||
({ shape, asset = { src: '' }, isBinding, isGhost, meta, events, onShapeChange }, ref) => {
|
({ shape, asset = { src: '' }, isBinding, isGhost, meta, events, onShapeChange }, ref) => {
|
||||||
const { size, style } = shape
|
const { size, style } = shape
|
||||||
|
const { bindingDistance } = this
|
||||||
|
|
||||||
const rImage = React.useRef<HTMLImageElement>(null)
|
const rImage = React.useRef<HTMLImageElement>(null)
|
||||||
const rWrapper = React.useRef<HTMLDivElement>(null)
|
const rWrapper = React.useRef<HTMLDivElement>(null)
|
||||||
|
@ -65,10 +66,10 @@ export class ImageUtil extends TDShapeUtil<T, E> {
|
||||||
className="tl-binding-indicator"
|
className="tl-binding-indicator"
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: `calc(${-this.bindingDistance}px * var(--tl-zoom))`,
|
top: `calc(${-bindingDistance}px * var(--tl-zoom))`,
|
||||||
left: `calc(${-this.bindingDistance}px * var(--tl-zoom))`,
|
left: `calc(${-bindingDistance}px * var(--tl-zoom))`,
|
||||||
width: `calc(100% + ${this.bindingDistance * 2}px * var(--tl-zoom))`,
|
width: `calc(100% + ${bindingDistance * 2}px * var(--tl-zoom))`,
|
||||||
height: `calc(100% + ${this.bindingDistance * 2}px * var(--tl-zoom))`,
|
height: `calc(100% + ${bindingDistance * 2}px * var(--tl-zoom))`,
|
||||||
backgroundColor: 'var(--tl-selectFill)',
|
backgroundColor: 'var(--tl-selectFill)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -85,7 +86,6 @@ export class ImageUtil extends TDShapeUtil<T, E> {
|
||||||
src={(asset as TDImageAsset).src}
|
src={(asset as TDImageAsset).src}
|
||||||
alt="tl_image_asset"
|
alt="tl_image_asset"
|
||||||
draggable={false}
|
draggable={false}
|
||||||
// onLoad={onImageLoad}
|
|
||||||
/>
|
/>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
</HTMLContainer>
|
</HTMLContainer>
|
||||||
|
|
|
@ -20,7 +20,7 @@ export class VideoUtil extends TDShapeUtil<T, E> {
|
||||||
canEdit = true
|
canEdit = true
|
||||||
canClone = true
|
canClone = true
|
||||||
isAspectRatioLocked = true
|
isAspectRatioLocked = true
|
||||||
showCloneHandles = true
|
showCloneHandles = false
|
||||||
isStateful = true // don't unmount
|
isStateful = true // don't unmount
|
||||||
|
|
||||||
getShape = (props: Partial<T>): T => {
|
getShape = (props: Partial<T>): T => {
|
||||||
|
|
|
@ -15,8 +15,6 @@ const { styled, createTheme } = createStitches({
|
||||||
panelContrast: '#ffffff',
|
panelContrast: '#ffffff',
|
||||||
selected: 'rgba(66, 133, 244, 1.000)',
|
selected: 'rgba(66, 133, 244, 1.000)',
|
||||||
selectedContrast: '#fefefe',
|
selectedContrast: '#fefefe',
|
||||||
sponsor: '#ec6cb9',
|
|
||||||
sponsorContrast: '#ec6cb944',
|
|
||||||
text: '#333333',
|
text: '#333333',
|
||||||
tooltip: '#1d1d1d',
|
tooltip: '#1d1d1d',
|
||||||
tooltipContrast: '#ffffff',
|
tooltipContrast: '#ffffff',
|
||||||
|
|
|
@ -10,8 +10,6 @@
|
||||||
"zoom.in": "تكبير",
|
"zoom.in": "تكبير",
|
||||||
"zoom.out": "تصغير",
|
"zoom.out": "تصغير",
|
||||||
"to": "إلى",
|
"to": "إلى",
|
||||||
"to.selection": "للاختيار",
|
|
||||||
"to.fit": "تناسب",
|
|
||||||
"menu.tools": "أدوات",
|
"menu.tools": "أدوات",
|
||||||
"menu.transform": "التحويلات",
|
"menu.transform": "التحويلات",
|
||||||
"menu.file": "ملف",
|
"menu.file": "ملف",
|
||||||
|
@ -20,7 +18,6 @@
|
||||||
"menu.preferences": "التفضيلات",
|
"menu.preferences": "التفضيلات",
|
||||||
"menu.sign.in": "تسجيل الدخول",
|
"menu.sign.in": "تسجيل الدخول",
|
||||||
"menu.sign.out": "خروج",
|
"menu.sign.out": "خروج",
|
||||||
"sponsored": "برعاية",
|
|
||||||
"become.a.sponsor": "كن راعياً",
|
"become.a.sponsor": "كن راعياً",
|
||||||
"zoom.to.content": "العودة إلى المحتوى",
|
"zoom.to.content": "العودة إلى المحتوى",
|
||||||
"zoom.to.selection": "تكبير للتحديد",
|
"zoom.to.selection": "تكبير للتحديد",
|
||||||
|
@ -101,4 +98,4 @@
|
||||||
"keyboard.shortcuts": "اختصارات لوحة المفاتيح",
|
"keyboard.shortcuts": "اختصارات لوحة المفاتيح",
|
||||||
"search": "بحث",
|
"search": "بحث",
|
||||||
"loading": "{dots}تحميل "
|
"loading": "{dots}تحميل "
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "Zoom ind",
|
"zoom.in": "Zoom ind",
|
||||||
"zoom.out": "Zoom ud",
|
"zoom.out": "Zoom ud",
|
||||||
"to": "til",
|
"to": "til",
|
||||||
"to.selection": "Til valgte",
|
|
||||||
"to.fit": "Til lærred",
|
|
||||||
"menu.file": "Fil",
|
"menu.file": "Fil",
|
||||||
"menu.edit": "Rediger",
|
"menu.edit": "Rediger",
|
||||||
"menu.view": "Vis",
|
"menu.view": "Vis",
|
||||||
"menu.preferences": "Indstillinger",
|
"menu.preferences": "Indstillinger",
|
||||||
"menu.sign.in": "Log ind",
|
"menu.sign.in": "Log ind",
|
||||||
"menu.sign.out": "Log ud",
|
"menu.sign.out": "Log ud",
|
||||||
"sponsored": "Sponsoreret",
|
|
||||||
"become.a.sponsor": "Bliv sponsor",
|
"become.a.sponsor": "Bliv sponsor",
|
||||||
"zoom.to.selection": "Zoom til valgte",
|
"zoom.to.selection": "Zoom til valgte",
|
||||||
"zoom.to.fit": "Zoom til lærred",
|
"zoom.to.fit": "Zoom til lærred",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "Tilbage",
|
"backward": "Tilbage",
|
||||||
"back": "Bagerst",
|
"back": "Bagerst",
|
||||||
"language": "Sprog"
|
"language": "Sprog"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "Heranzoomen",
|
"zoom.in": "Heranzoomen",
|
||||||
"zoom.out": "Herauszoomen",
|
"zoom.out": "Herauszoomen",
|
||||||
"to": "zu",
|
"to": "zu",
|
||||||
"to.selection": "Zur Auswahl",
|
|
||||||
"to.fit": "Anpassen",
|
|
||||||
"menu.file": "Datei",
|
"menu.file": "Datei",
|
||||||
"menu.edit": "Bearbeiten",
|
"menu.edit": "Bearbeiten",
|
||||||
"menu.view": "Ansicht",
|
"menu.view": "Ansicht",
|
||||||
"menu.preferences": "Präferenzen",
|
"menu.preferences": "Präferenzen",
|
||||||
"menu.sign.in": "Einloggen",
|
"menu.sign.in": "Einloggen",
|
||||||
"menu.sign.out": "Ausloggen",
|
"menu.sign.out": "Ausloggen",
|
||||||
"sponsored": "Gesponsert",
|
|
||||||
"become.a.sponsor": "Sponsor werden",
|
"become.a.sponsor": "Sponsor werden",
|
||||||
"zoom.to.selection": "Zur Auswahl zoomen",
|
"zoom.to.selection": "Zur Auswahl zoomen",
|
||||||
"zoom.to.fit": "Zoom anpassen",
|
"zoom.to.fit": "Zoom anpassen",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "Rückwärts",
|
"backward": "Rückwärts",
|
||||||
"back": "Hinten",
|
"back": "Hinten",
|
||||||
"language": "Sprache"
|
"language": "Sprache"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "Acercar",
|
"zoom.in": "Acercar",
|
||||||
"zoom.out": "Alejar",
|
"zoom.out": "Alejar",
|
||||||
"to": "A",
|
"to": "A",
|
||||||
"to.selection": "A la selección",
|
|
||||||
"to.fit": "Ajustar",
|
|
||||||
"menu.file": "Archivo",
|
"menu.file": "Archivo",
|
||||||
"menu.edit": "Editar",
|
"menu.edit": "Editar",
|
||||||
"menu.view": "Ver",
|
"menu.view": "Ver",
|
||||||
"menu.preferences": "Preferencias",
|
"menu.preferences": "Preferencias",
|
||||||
"menu.sign.in": "Iniciar sesión",
|
"menu.sign.in": "Iniciar sesión",
|
||||||
"menu.sign.out": "Cerrar sesión",
|
"menu.sign.out": "Cerrar sesión",
|
||||||
"sponsored": "Patrocinado",
|
|
||||||
"become.a.sponsor": "Conviértete en patrocinador",
|
"become.a.sponsor": "Conviértete en patrocinador",
|
||||||
"zoom.to.content": "Acercar al contenido",
|
"zoom.to.content": "Acercar al contenido",
|
||||||
"zoom.to.selection": "Acercar a la selección",
|
"zoom.to.selection": "Acercar a la selección",
|
||||||
|
@ -99,4 +96,4 @@
|
||||||
"top": "Arriba",
|
"top": "Arriba",
|
||||||
"search": "Buscar",
|
"search": "Buscar",
|
||||||
"page": "Página"
|
"page": "Página"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "زوم جلو",
|
"zoom.in": "زوم جلو",
|
||||||
"zoom.out": "زوم عقب",
|
"zoom.out": "زوم عقب",
|
||||||
"to": "به",
|
"to": "به",
|
||||||
"to.selection": "به انتخابشدهها",
|
|
||||||
"to.fit": "به کل صفحه",
|
|
||||||
"menu.file": "فایل",
|
"menu.file": "فایل",
|
||||||
"menu.edit": "ویرایش",
|
"menu.edit": "ویرایش",
|
||||||
"menu.view": "نمایش",
|
"menu.view": "نمایش",
|
||||||
"menu.preferences": "تنظیمها",
|
"menu.preferences": "تنظیمها",
|
||||||
"menu.sign.in": "ورود",
|
"menu.sign.in": "ورود",
|
||||||
"menu.sign.out": "خروج",
|
"menu.sign.out": "خروج",
|
||||||
"sponsored": "حامیان",
|
|
||||||
"become.a.sponsor": " حامی شو",
|
"become.a.sponsor": " حامی شو",
|
||||||
"zoom.to.selection": "نمایش انتخابشدهها",
|
"zoom.to.selection": "نمایش انتخابشدهها",
|
||||||
"zoom.to.fit": "نمایش کل صفحه",
|
"zoom.to.fit": "نمایش کل صفحه",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "به عقب",
|
"backward": "به عقب",
|
||||||
"back": "به آخر",
|
"back": "به آخر",
|
||||||
"language": "زبان"
|
"language": "زبان"
|
||||||
}
|
}
|
|
@ -10,8 +10,6 @@
|
||||||
"zoom.in": "Zoom avant",
|
"zoom.in": "Zoom avant",
|
||||||
"zoom.out": "Zoom arrière",
|
"zoom.out": "Zoom arrière",
|
||||||
"to": "À",
|
"to": "À",
|
||||||
"to.selection": "Sélection",
|
|
||||||
"to.fit": "Contenu",
|
|
||||||
"menu.tools": "Outils",
|
"menu.tools": "Outils",
|
||||||
"menu.transform": "Transformation",
|
"menu.transform": "Transformation",
|
||||||
"menu.file": "Fichier",
|
"menu.file": "Fichier",
|
||||||
|
@ -20,7 +18,6 @@
|
||||||
"menu.preferences": "Préférences",
|
"menu.preferences": "Préférences",
|
||||||
"menu.sign.in": "S'authentifier",
|
"menu.sign.in": "S'authentifier",
|
||||||
"menu.sign.out": "Se déconnecter",
|
"menu.sign.out": "Se déconnecter",
|
||||||
"sponsored": "Sponsorisé",
|
|
||||||
"become.a.sponsor": "Devenir un sponsor",
|
"become.a.sponsor": "Devenir un sponsor",
|
||||||
"zoom.to.content": "Retour au contenu",
|
"zoom.to.content": "Retour au contenu",
|
||||||
"zoom.to.selection": "Ajuster le zoom à la sélection",
|
"zoom.to.selection": "Ajuster le zoom à la sélection",
|
||||||
|
@ -99,4 +96,4 @@
|
||||||
"right": "À droite",
|
"right": "À droite",
|
||||||
"top": "En haut",
|
"top": "En haut",
|
||||||
"page": "Page"
|
"page": "Page"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "הגדל תצוגה",
|
"zoom.in": "הגדל תצוגה",
|
||||||
"zoom.out": "הקטן תצוגה",
|
"zoom.out": "הקטן תצוגה",
|
||||||
"to": "ל",
|
"to": "ל",
|
||||||
"to.selection": "לסימון",
|
|
||||||
"to.fit": "להתאמה",
|
|
||||||
"menu.file": "קובץ",
|
"menu.file": "קובץ",
|
||||||
"menu.edit": "עריכה",
|
"menu.edit": "עריכה",
|
||||||
"menu.view": "תצוגה",
|
"menu.view": "תצוגה",
|
||||||
"menu.preferences": "מאפיינים",
|
"menu.preferences": "מאפיינים",
|
||||||
"menu.sign.in": "הירשם",
|
"menu.sign.in": "הירשם",
|
||||||
"menu.sign.out": "התנתק",
|
"menu.sign.out": "התנתק",
|
||||||
"sponsored": "חסות",
|
|
||||||
"become.a.sponsor": "מתן חסות",
|
"become.a.sponsor": "מתן חסות",
|
||||||
"zoom.to.selection": "זום לבחירה",
|
"zoom.to.selection": "זום לבחירה",
|
||||||
"zoom.to.fit": "זום להתאמה",
|
"zoom.to.fit": "זום להתאמה",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "אחורה",
|
"backward": "אחורה",
|
||||||
"back": "בחזרה",
|
"back": "בחזרה",
|
||||||
"language": "שפה"
|
"language": "שפה"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "Ingrandisci",
|
"zoom.in": "Ingrandisci",
|
||||||
"zoom.out": "Rimpicciolisci",
|
"zoom.out": "Rimpicciolisci",
|
||||||
"to": "Imposta",
|
"to": "Imposta",
|
||||||
"to.selection": "Adatta alla selezione",
|
|
||||||
"to.fit": "Adatta",
|
|
||||||
"menu.file": "File",
|
"menu.file": "File",
|
||||||
"menu.edit": "Modifica",
|
"menu.edit": "Modifica",
|
||||||
"menu.view": "Visualizzazione",
|
"menu.view": "Visualizzazione",
|
||||||
"menu.preferences": "Preferenze",
|
"menu.preferences": "Preferenze",
|
||||||
"menu.sign.in": "Accedi",
|
"menu.sign.in": "Accedi",
|
||||||
"menu.sign.out": "Esci",
|
"menu.sign.out": "Esci",
|
||||||
"sponsored": "Sponsorizza",
|
|
||||||
"become.a.sponsor": "Sponsorizza",
|
"become.a.sponsor": "Sponsorizza",
|
||||||
"zoom.to.selection": "Adatta alla selezione",
|
"zoom.to.selection": "Adatta alla selezione",
|
||||||
"zoom.to.fit": "Adatta",
|
"zoom.to.fit": "Adatta",
|
||||||
|
@ -93,4 +90,4 @@
|
||||||
"left": "Sinistra",
|
"left": "Sinistra",
|
||||||
"right": "Destra",
|
"right": "Destra",
|
||||||
"top": "In Alto"
|
"top": "In Alto"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "拡大",
|
"zoom.in": "拡大",
|
||||||
"zoom.out": "縮小",
|
"zoom.out": "縮小",
|
||||||
"to": " ",
|
"to": " ",
|
||||||
"to.selection": "選択したアイテムに合わせる",
|
|
||||||
"to.fit": "すべて表示",
|
|
||||||
"menu.file": "ファイル",
|
"menu.file": "ファイル",
|
||||||
"menu.edit": "編集",
|
"menu.edit": "編集",
|
||||||
"menu.view": "表示",
|
"menu.view": "表示",
|
||||||
"menu.preferences": "設定",
|
"menu.preferences": "設定",
|
||||||
"menu.sign.in": "サインイン",
|
"menu.sign.in": "サインイン",
|
||||||
"menu.sign.out": "サインアウト",
|
"menu.sign.out": "サインアウト",
|
||||||
"sponsored": "支援",
|
|
||||||
"become.a.sponsor": "支援する",
|
"become.a.sponsor": "支援する",
|
||||||
"zoom.to.selection": "選択したアイテムに合わせて拡大",
|
"zoom.to.selection": "選択したアイテムに合わせて拡大",
|
||||||
"zoom.to.fit": "拡大してすべてを表示",
|
"zoom.to.fit": "拡大してすべてを表示",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "ひとつ後ろへ",
|
"backward": "ひとつ後ろへ",
|
||||||
"back": "最背面へ",
|
"back": "最背面へ",
|
||||||
"language": "言語"
|
"language": "言語"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "확대",
|
"zoom.in": "확대",
|
||||||
"zoom.out": "축소",
|
"zoom.out": "축소",
|
||||||
"to": "to",
|
"to": "to",
|
||||||
"to.selection": "선택 요소 맞춤",
|
|
||||||
"to.fit": "전체 맞춤",
|
|
||||||
"menu.file": "파일",
|
"menu.file": "파일",
|
||||||
"menu.edit": "편집",
|
"menu.edit": "편집",
|
||||||
"menu.view": "보기",
|
"menu.view": "보기",
|
||||||
"menu.preferences": "설정",
|
"menu.preferences": "설정",
|
||||||
"menu.sign.in": "로그인",
|
"menu.sign.in": "로그인",
|
||||||
"menu.sign.out": "로그아웃",
|
"menu.sign.out": "로그아웃",
|
||||||
"sponsored": "후원",
|
|
||||||
"become.a.sponsor": "후원자 되기",
|
"become.a.sponsor": "후원자 되기",
|
||||||
"zoom.to.selection": "선택 요소 맞추기",
|
"zoom.to.selection": "선택 요소 맞추기",
|
||||||
"zoom.to.fit": "전체 맞추기",
|
"zoom.to.fit": "전체 맞추기",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "뒤로",
|
"backward": "뒤로",
|
||||||
"back": "맨 뒤로",
|
"back": "맨 뒤로",
|
||||||
"language": "언어"
|
"language": "언어"
|
||||||
}
|
}
|
|
@ -10,8 +10,6 @@
|
||||||
"zoom.in": "Zoom In",
|
"zoom.in": "Zoom In",
|
||||||
"zoom.out": "Zoom Out",
|
"zoom.out": "Zoom Out",
|
||||||
"to": "To",
|
"to": "To",
|
||||||
"to.selection": "To Selection",
|
|
||||||
"to.fit": "To Fit",
|
|
||||||
"menu.tools": "Tools",
|
"menu.tools": "Tools",
|
||||||
"menu.transform": "Transform",
|
"menu.transform": "Transform",
|
||||||
"menu.file": "File",
|
"menu.file": "File",
|
||||||
|
@ -20,7 +18,6 @@
|
||||||
"menu.preferences": "Preferences",
|
"menu.preferences": "Preferences",
|
||||||
"menu.sign.in": "Sign In",
|
"menu.sign.in": "Sign In",
|
||||||
"menu.sign.out": "Sign Out",
|
"menu.sign.out": "Sign Out",
|
||||||
"sponsored": "Sponsored",
|
|
||||||
"become.a.sponsor": "Become a Sponsor",
|
"become.a.sponsor": "Become a Sponsor",
|
||||||
"zoom.to.content": "Back to content",
|
"zoom.to.content": "Back to content",
|
||||||
"zoom.to.selection": "Zoom to Selection",
|
"zoom.to.selection": "Zoom to Selection",
|
||||||
|
@ -105,5 +102,8 @@
|
||||||
"transparent": "Transparent",
|
"transparent": "Transparent",
|
||||||
"auto": "Auto",
|
"auto": "Auto",
|
||||||
"light": "Light",
|
"light": "Light",
|
||||||
"dark": "Dark"
|
"dark": "Dark",
|
||||||
}
|
"github": "Github",
|
||||||
|
"twitter": "Twitter",
|
||||||
|
"discord": "Discord"
|
||||||
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "जुम इन",
|
"zoom.in": "जुम इन",
|
||||||
"zoom.out": "जुम आउट",
|
"zoom.out": "जुम आउट",
|
||||||
"to": "टु",
|
"to": "टु",
|
||||||
"to.selection": "टु सेलेक्सन",
|
|
||||||
"to.fit": "टु फिट",
|
|
||||||
"menu.file": "फाइल",
|
"menu.file": "फाइल",
|
||||||
"menu.edit": "सम्पादन गर्नुहोस्",
|
"menu.edit": "सम्पादन गर्नुहोस्",
|
||||||
"menu.view": "भ्यू",
|
"menu.view": "भ्यू",
|
||||||
"menu.preferences": "प्राथमिकताहरू",
|
"menu.preferences": "प्राथमिकताहरू",
|
||||||
"menu.sign.in": "साइन इन गर्नुहोस्",
|
"menu.sign.in": "साइन इन गर्नुहोस्",
|
||||||
"menu.sign.out": "साइन आउट गर्नुहोस्",
|
"menu.sign.out": "साइन आउट गर्नुहोस्",
|
||||||
"sponsored": "प्रायोजित",
|
|
||||||
"become.a.sponsor": "प्रायोजक बन्नुहोस्",
|
"become.a.sponsor": "प्रायोजक बन्नुहोस्",
|
||||||
"zoom.to.selection": "जुम टु सेलेक्सन",
|
"zoom.to.selection": "जुम टु सेलेक्सन",
|
||||||
"zoom.to.fit": "जुम टु फिट",
|
"zoom.to.fit": "जुम टु फिट",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "पछाडि",
|
"backward": "पछाडि",
|
||||||
"back": "थप पछाडि",
|
"back": "थप पछाडि",
|
||||||
"language": "भाषा"
|
"language": "भाषा"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "Zoom inn",
|
"zoom.in": "Zoom inn",
|
||||||
"zoom.out": "Zoom ut",
|
"zoom.out": "Zoom ut",
|
||||||
"to": "til",
|
"to": "til",
|
||||||
"to.selection": "Til valg",
|
|
||||||
"to.fit": "For å passe",
|
|
||||||
"menu.file": "Fil",
|
"menu.file": "Fil",
|
||||||
"menu.edit": "Rediger",
|
"menu.edit": "Rediger",
|
||||||
"menu.view": "Vis",
|
"menu.view": "Vis",
|
||||||
"menu.preferences": "Preferanser",
|
"menu.preferences": "Preferanser",
|
||||||
"menu.sign.in": "Logg inn",
|
"menu.sign.in": "Logg inn",
|
||||||
"menu.sign.out": "Logg ut",
|
"menu.sign.out": "Logg ut",
|
||||||
"sponsored": "Sponset",
|
|
||||||
"become.a.sponsor": "Bli en sponsor",
|
"become.a.sponsor": "Bli en sponsor",
|
||||||
"zoom.to.selection": "Zoom til valg",
|
"zoom.to.selection": "Zoom til valg",
|
||||||
"zoom.to.fit": "Zoom for å passe",
|
"zoom.to.fit": "Zoom for å passe",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "Bakover",
|
"backward": "Bakover",
|
||||||
"back": "Bakerst",
|
"back": "Bakerst",
|
||||||
"language": "Språk"
|
"language": "Språk"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "Przybliż",
|
"zoom.in": "Przybliż",
|
||||||
"zoom.out": "Oddal",
|
"zoom.out": "Oddal",
|
||||||
"to": "do",
|
"to": "do",
|
||||||
"to.selection": "Do zaznaczenia",
|
|
||||||
"to.fit": "Do wypełnienia",
|
|
||||||
"menu.file": "Plik",
|
"menu.file": "Plik",
|
||||||
"menu.edit": "Edycja",
|
"menu.edit": "Edycja",
|
||||||
"menu.view": "Widok",
|
"menu.view": "Widok",
|
||||||
"menu.preferences": "Preferencje",
|
"menu.preferences": "Preferencje",
|
||||||
"menu.sign.in": "Zaloguj",
|
"menu.sign.in": "Zaloguj",
|
||||||
"menu.sign.out": "Wyloguj",
|
"menu.sign.out": "Wyloguj",
|
||||||
"sponsored": "Sponsorzy",
|
|
||||||
"become.a.sponsor": "Zostań sponsorem",
|
"become.a.sponsor": "Zostań sponsorem",
|
||||||
"zoom.to.selection": "Przybliż do zaznaczenia",
|
"zoom.to.selection": "Przybliż do zaznaczenia",
|
||||||
"zoom.to.fit": "Wypełnij ekran",
|
"zoom.to.fit": "Wypełnij ekran",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "Do tyłu",
|
"backward": "Do tyłu",
|
||||||
"back": "Na spód",
|
"back": "Na spód",
|
||||||
"language": "Język"
|
"language": "Język"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "Aumentar zoom",
|
"zoom.in": "Aumentar zoom",
|
||||||
"zoom.out": "Diminuir zoom",
|
"zoom.out": "Diminuir zoom",
|
||||||
"to": "para",
|
"to": "para",
|
||||||
"to.selection": "Para seleção",
|
|
||||||
"to.fit": "Para encaixar",
|
|
||||||
"menu.file": "Arquivo",
|
"menu.file": "Arquivo",
|
||||||
"menu.edit": "Editar",
|
"menu.edit": "Editar",
|
||||||
"menu.view": "Visualizar",
|
"menu.view": "Visualizar",
|
||||||
"menu.preferences": "Preferências",
|
"menu.preferences": "Preferências",
|
||||||
"menu.sign.in": "Entrar",
|
"menu.sign.in": "Entrar",
|
||||||
"menu.sign.out": "Sair",
|
"menu.sign.out": "Sair",
|
||||||
"sponsored": "Patrocinado",
|
|
||||||
"become.a.sponsor": "Torne-se um patrocinador",
|
"become.a.sponsor": "Torne-se um patrocinador",
|
||||||
"zoom.to.selection": "Zoom para a seleção",
|
"zoom.to.selection": "Zoom para a seleção",
|
||||||
"zoom.to.fit": "Zoom para ajuste",
|
"zoom.to.fit": "Zoom para ajuste",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "Recuar",
|
"backward": "Recuar",
|
||||||
"back": "Voltar",
|
"back": "Voltar",
|
||||||
"language": "Idioma"
|
"language": "Idioma"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "Увеличить",
|
"zoom.in": "Увеличить",
|
||||||
"zoom.out": "Уменьшить",
|
"zoom.out": "Уменьшить",
|
||||||
"to": "к",
|
"to": "к",
|
||||||
"to.selection": "К выделению",
|
|
||||||
"to.fit": "По размеру экрана",
|
|
||||||
"menu.file": "Файл",
|
"menu.file": "Файл",
|
||||||
"menu.edit": "Редактирование",
|
"menu.edit": "Редактирование",
|
||||||
"menu.view": "Вид",
|
"menu.view": "Вид",
|
||||||
"menu.preferences": "Настройки",
|
"menu.preferences": "Настройки",
|
||||||
"menu.sign.in": "Войти",
|
"menu.sign.in": "Войти",
|
||||||
"menu.sign.out": "Выйти",
|
"menu.sign.out": "Выйти",
|
||||||
"sponsored": "Спонсировано",
|
|
||||||
"become.a.sponsor": "Стать спонсором",
|
"become.a.sponsor": "Стать спонсором",
|
||||||
"zoom.to.selection": "Приблизить к выделению",
|
"zoom.to.selection": "Приблизить к выделению",
|
||||||
"zoom.to.fit": "Увеличить по размеру экрана",
|
"zoom.to.fit": "Увеличить по размеру экрана",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "На задний план",
|
"backward": "На задний план",
|
||||||
"back": "Назад",
|
"back": "Назад",
|
||||||
"language": "Язык"
|
"language": "Язык"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "Yakınlaştır",
|
"zoom.in": "Yakınlaştır",
|
||||||
"zoom.out": "Uzaklaştır",
|
"zoom.out": "Uzaklaştır",
|
||||||
"to": "",
|
"to": "",
|
||||||
"to.selection": "Seçime Göre",
|
|
||||||
"to.fit": "Sığdırmaya Göre",
|
|
||||||
"menu.file": "Dosya",
|
"menu.file": "Dosya",
|
||||||
"menu.edit": "Düzenle",
|
"menu.edit": "Düzenle",
|
||||||
"menu.view": "Görüntü",
|
"menu.view": "Görüntü",
|
||||||
"menu.preferences": "Tercihler",
|
"menu.preferences": "Tercihler",
|
||||||
"menu.sign.in": "Giriş Yap",
|
"menu.sign.in": "Giriş Yap",
|
||||||
"menu.sign.out": "Oturumu Kapat",
|
"menu.sign.out": "Oturumu Kapat",
|
||||||
"sponsored": "Sponsorlu",
|
|
||||||
"become.a.sponsor": "Sponsor Ol",
|
"become.a.sponsor": "Sponsor Ol",
|
||||||
"zoom.to.selection": "Seçime Yakınlaştır",
|
"zoom.to.selection": "Seçime Yakınlaştır",
|
||||||
"zoom.to.fit": "Sığdırmak için Yakınlaştır",
|
"zoom.to.fit": "Sığdırmak için Yakınlaştır",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "En Arkaya",
|
"backward": "En Arkaya",
|
||||||
"back": "Arkaya",
|
"back": "Arkaya",
|
||||||
"language": "Dil"
|
"language": "Dil"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "Збільшити",
|
"zoom.in": "Збільшити",
|
||||||
"zoom.out": "Зменшити",
|
"zoom.out": "Зменшити",
|
||||||
"to": "до",
|
"to": "до",
|
||||||
"to.selection": "До виділення",
|
|
||||||
"to.fit": "За розміром екрану",
|
|
||||||
"menu.file": "Файл",
|
"menu.file": "Файл",
|
||||||
"menu.edit": "Редагування",
|
"menu.edit": "Редагування",
|
||||||
"menu.view": "Вигляд",
|
"menu.view": "Вигляд",
|
||||||
"menu.preferences": "Налаштування",
|
"menu.preferences": "Налаштування",
|
||||||
"menu.sign.in": "Увійти",
|
"menu.sign.in": "Увійти",
|
||||||
"menu.sign.out": "Вийти",
|
"menu.sign.out": "Вийти",
|
||||||
"sponsored": "Спонсовано",
|
|
||||||
"become.a.sponsor": "Стати спонсором",
|
"become.a.sponsor": "Стати спонсором",
|
||||||
"zoom.to.selection": "Наблизити до виділення",
|
"zoom.to.selection": "Наблизити до виділення",
|
||||||
"zoom.to.fit": "Збільшити за розміром екрану",
|
"zoom.to.fit": "Збільшити за розміром екрану",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "На задній план",
|
"backward": "На задній план",
|
||||||
"back": "Назад",
|
"back": "Назад",
|
||||||
"language": "Мова"
|
"language": "Мова"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "放大",
|
"zoom.in": "放大",
|
||||||
"zoom.out": "缩小",
|
"zoom.out": "缩小",
|
||||||
"to": "缩放至",
|
"to": "缩放至",
|
||||||
"to.selection": "缩放选中",
|
|
||||||
"to.fit": "自适应缩放",
|
|
||||||
"menu.file": "文件",
|
"menu.file": "文件",
|
||||||
"menu.edit": "编辑",
|
"menu.edit": "编辑",
|
||||||
"menu.view": "视图",
|
"menu.view": "视图",
|
||||||
"menu.preferences": "偏好",
|
"menu.preferences": "偏好",
|
||||||
"menu.sign.in": "登录",
|
"menu.sign.in": "登录",
|
||||||
"menu.sign.out": "登出",
|
"menu.sign.out": "登出",
|
||||||
"sponsored": "已赞助",
|
|
||||||
"become.a.sponsor": "成为赞助者",
|
"become.a.sponsor": "成为赞助者",
|
||||||
"zoom.to.selection": "缩放选中",
|
"zoom.to.selection": "缩放选中",
|
||||||
"zoom.to.fit": "自适应缩放",
|
"zoom.to.fit": "自适应缩放",
|
||||||
|
@ -87,4 +84,4 @@
|
||||||
"backward": "下一层",
|
"backward": "下一层",
|
||||||
"back": "置底",
|
"back": "置底",
|
||||||
"language": "语言"
|
"language": "语言"
|
||||||
}
|
}
|
|
@ -10,15 +10,12 @@
|
||||||
"zoom.in": "放大",
|
"zoom.in": "放大",
|
||||||
"zoom.out": "縮小",
|
"zoom.out": "縮小",
|
||||||
"to": "至",
|
"to": "至",
|
||||||
"to.selection": "至選取範圍",
|
|
||||||
"to.fit": "至適當大小",
|
|
||||||
"menu.file": "檔案",
|
"menu.file": "檔案",
|
||||||
"menu.edit": "編輯",
|
"menu.edit": "編輯",
|
||||||
"menu.view": "檢視",
|
"menu.view": "檢視",
|
||||||
"menu.preferences": "選項",
|
"menu.preferences": "選項",
|
||||||
"menu.sign.in": "登入",
|
"menu.sign.in": "登入",
|
||||||
"menu.sign.out": "登出",
|
"menu.sign.out": "登出",
|
||||||
"sponsored": "贊助",
|
|
||||||
"become.a.sponsor": "成為贊助者",
|
"become.a.sponsor": "成為贊助者",
|
||||||
"zoom.to.selection": "縮放至選取範圍",
|
"zoom.to.selection": "縮放至選取範圍",
|
||||||
"zoom.to.fit": "縮放至適當大小",
|
"zoom.to.fit": "縮放至適當大小",
|
||||||
|
@ -95,4 +92,4 @@
|
||||||
"right": "右側",
|
"right": "右側",
|
||||||
"top": "上方",
|
"top": "上方",
|
||||||
"page": "頁面"
|
"page": "頁面"
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue