[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 {
|
||||
id?: string
|
||||
isUser?: boolean
|
||||
isSponsor?: boolean
|
||||
}
|
||||
|
||||
const Editor = ({
|
||||
id = 'home',
|
||||
isUser = false,
|
||||
isSponsor = false,
|
||||
...rest
|
||||
}: EditorProps & Partial<TldrawProps>) => {
|
||||
const Editor = ({ id = 'home', ...rest }: EditorProps & Partial<TldrawProps>) => {
|
||||
const handleMount = React.useCallback((app: TldrawApp) => {
|
||||
window.app = app
|
||||
}, [])
|
||||
|
@ -30,7 +23,6 @@ const Editor = ({
|
|||
id={id}
|
||||
autofocus
|
||||
onMount={handleMount}
|
||||
showSponsorLink={!isSponsor}
|
||||
onAssetUpload={onAssetUpload}
|
||||
{...fileSystemEvents}
|
||||
{...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 { useAccountHandlers } from 'hooks/useAccountHandlers'
|
||||
import { useUploadAssets } from 'hooks/useUploadAssets'
|
||||
import * as React from 'react'
|
||||
import * as gtag from 'utils/gtag'
|
||||
|
@ -8,16 +7,9 @@ declare const window: Window & { app: TldrawApp }
|
|||
|
||||
interface EditorProps {
|
||||
id?: string
|
||||
isUser?: boolean
|
||||
isSponsor?: boolean
|
||||
}
|
||||
|
||||
const Editor = ({
|
||||
id = 'home',
|
||||
isUser = false,
|
||||
isSponsor = false,
|
||||
...rest
|
||||
}: EditorProps & Partial<TldrawProps>) => {
|
||||
const Editor = ({ id = 'home', ...rest }: EditorProps & Partial<TldrawProps>) => {
|
||||
const handleMount = React.useCallback((app: TldrawApp) => {
|
||||
window.app = app
|
||||
}, [])
|
||||
|
@ -34,8 +26,6 @@ const Editor = ({
|
|||
|
||||
const fileSystemEvents = useFileSystem()
|
||||
|
||||
const { onSignIn, onSignOut } = useAccountHandlers()
|
||||
|
||||
const { onAssetUpload } = useUploadAssets()
|
||||
|
||||
return (
|
||||
|
@ -45,9 +35,6 @@ const Editor = ({
|
|||
autofocus
|
||||
onMount={handleMount}
|
||||
onPersist={handlePersist}
|
||||
showSponsorLink={!isSponsor}
|
||||
onSignIn={isSponsor ? undefined : onSignIn}
|
||||
onSignOut={isUser ? onSignOut : undefined}
|
||||
onAssetUpload={onAssetUpload}
|
||||
{...fileSystemEvents}
|
||||
{...rest}
|
||||
|
|
|
@ -1,39 +1,27 @@
|
|||
import * as React from 'react'
|
||||
import { RoomProvider } from '../utils/liveblocks'
|
||||
import { Tldraw, useFileSystem } from '@tldraw/tldraw'
|
||||
import { useAccountHandlers } from 'hooks/useAccountHandlers'
|
||||
import { useMultiplayerAssets } from 'hooks/useMultiplayerAssets'
|
||||
import { useMultiplayerState } from 'hooks/useMultiplayerState'
|
||||
import { useUploadAssets } from 'hooks/useUploadAssets'
|
||||
import React, { FC } from 'react'
|
||||
import { styled } from 'styles'
|
||||
|
||||
interface Props {
|
||||
roomId: string
|
||||
isUser: boolean
|
||||
isSponsor: boolean
|
||||
}
|
||||
|
||||
const MultiplayerEditor: FC<Props> = ({
|
||||
roomId,
|
||||
isUser = false,
|
||||
isSponsor = false,
|
||||
}: {
|
||||
roomId: string
|
||||
isUser: boolean
|
||||
isSponsor: boolean
|
||||
}) => {
|
||||
const MultiplayerEditor = ({ roomId }: Props) => {
|
||||
return (
|
||||
<RoomProvider id={roomId}>
|
||||
<Editor roomId={roomId} isSponsor={isSponsor} isUser={isUser} />
|
||||
<Editor roomId={roomId} />
|
||||
</RoomProvider>
|
||||
)
|
||||
}
|
||||
|
||||
// Inner Editor
|
||||
|
||||
function Editor({ roomId, isUser, isSponsor }: Props) {
|
||||
function Editor({ roomId }: Props) {
|
||||
const fileSystemEvents = useFileSystem()
|
||||
const { onSignIn, onSignOut } = useAccountHandlers()
|
||||
const { error, ...events } = useMultiplayerState(roomId)
|
||||
const { onAssetCreate, onAssetDelete } = useMultiplayerAssets()
|
||||
const { onAssetUpload } = useUploadAssets()
|
||||
|
@ -46,9 +34,6 @@ function Editor({ roomId, isUser, isSponsor }: Props) {
|
|||
autofocus
|
||||
disableAssets={false}
|
||||
showPages={false}
|
||||
showSponsorLink={!isSponsor}
|
||||
onSignIn={isSponsor ? undefined : onSignIn}
|
||||
onSignOut={isUser ? onSignOut : undefined}
|
||||
onAssetCreate={onAssetCreate}
|
||||
onAssetDelete={onAssetDelete}
|
||||
onAssetUpload={onAssetUpload}
|
||||
|
|
|
@ -1,37 +1,25 @@
|
|||
import * as React from 'react'
|
||||
import { RoomProvider } from '../utils/liveblocks'
|
||||
import { Tldraw, useFileSystem } from '@tldraw/tldraw'
|
||||
import { useAccountHandlers } from 'hooks/useAccountHandlers'
|
||||
import React, { FC } from 'react'
|
||||
import { styled } from 'styles'
|
||||
import { useReadOnlyMultiplayerState } from 'hooks/useReadOnlyMultiplayerState'
|
||||
|
||||
interface Props {
|
||||
roomId: string
|
||||
isUser: boolean
|
||||
isSponsor: boolean
|
||||
}
|
||||
|
||||
const ReadOnlyMultiplayerEditor: FC<Props> = ({
|
||||
roomId,
|
||||
isUser = false,
|
||||
isSponsor = false,
|
||||
}: {
|
||||
roomId: string
|
||||
isUser: boolean
|
||||
isSponsor: boolean
|
||||
}) => {
|
||||
const ReadOnlyMultiplayerEditor = ({ roomId }: Props) => {
|
||||
return (
|
||||
<RoomProvider id={roomId}>
|
||||
<ReadOnlyEditor roomId={roomId} isSponsor={isSponsor} isUser={isUser} />
|
||||
<ReadOnlyEditor roomId={roomId} />
|
||||
</RoomProvider>
|
||||
)
|
||||
}
|
||||
|
||||
// Inner Editor
|
||||
|
||||
function ReadOnlyEditor({ roomId, isUser, isSponsor }: Props) {
|
||||
function ReadOnlyEditor({ roomId }: Props) {
|
||||
const { onSaveProjectAs, onSaveProject } = useFileSystem()
|
||||
const { onSignIn, onSignOut } = useAccountHandlers()
|
||||
const { error, ...events } = useReadOnlyMultiplayerState(roomId)
|
||||
|
||||
if (error) return <LoadingScreen>Error: {error.message}</LoadingScreen>
|
||||
|
@ -42,9 +30,6 @@ function ReadOnlyEditor({ roomId, isUser, isSponsor }: Props) {
|
|||
autofocus
|
||||
disableAssets={false}
|
||||
showPages={false}
|
||||
showSponsorLink={!isSponsor}
|
||||
onSignIn={isSponsor ? undefined : onSignIn}
|
||||
onSignOut={isUser ? onSignOut : undefined}
|
||||
onSaveProjectAs={onSaveProjectAs}
|
||||
onSaveProject={onSaveProject}
|
||||
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",
|
||||
"@tldraw/core": "*",
|
||||
"@tldraw/tldraw": "*",
|
||||
"@types/next-auth": "^3.15.0",
|
||||
"@types/react": "^18.0.12",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"aws-sdk": "^2.1053.0",
|
||||
|
@ -37,7 +36,6 @@
|
|||
"lz-string": "^1.4.4",
|
||||
"nanoid": "^3.3.4",
|
||||
"next": "^12.1.6",
|
||||
"next-auth": "^4.0.5",
|
||||
"next-pwa": "^5.5.4",
|
||||
"next-themes": "^0.0.15",
|
||||
"react": "^18.1.0",
|
||||
|
@ -49,4 +47,4 @@
|
|||
"devDependencies": {
|
||||
"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 Head from 'next/head'
|
||||
import { useRouter } from 'next/router'
|
||||
import { FC, useMemo } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
const Editor = dynamic(() => import('components/Editor'), { ssr: false }) as any
|
||||
|
||||
interface PageProps {
|
||||
isUser: boolean
|
||||
isSponsor: boolean
|
||||
}
|
||||
|
||||
const Home: FC<PageProps> = ({ isUser, isSponsor }) => {
|
||||
const Home = () => {
|
||||
const { query } = useRouter()
|
||||
const isExportMode = useMemo(() => 'exportMode' in query, [query])
|
||||
|
||||
|
@ -21,20 +14,9 @@ const Home: FC<PageProps> = ({ isUser, isSponsor }) => {
|
|||
<Head>
|
||||
<title>tldraw</title>
|
||||
</Head>
|
||||
<Editor id="home" isUser={isUser} isSponsor={isSponsor} showUI={!isExportMode} />
|
||||
<Editor id="home" showUI={!isExportMode} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
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 type { GetServerSideProps } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const IFrameWarning = dynamic(() => import('components/IFrameWarning'), {
|
||||
|
@ -13,27 +12,22 @@ const MultiplayerEditor = dynamic(() => import('components/MultiplayerEditor'),
|
|||
|
||||
interface RoomProps {
|
||||
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) {
|
||||
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) => {
|
||||
const session = await getSession(context)
|
||||
const id = context.query.id?.toString()
|
||||
|
||||
return {
|
||||
props: {
|
||||
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 type { GetServerSideProps } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { Utils } from '@tldraw/core'
|
||||
|
||||
|
@ -14,27 +13,22 @@ const ReadOnlyMultiplayerEditor = dynamic(() => import('components/ReadOnlyMulti
|
|||
|
||||
interface RoomProps {
|
||||
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) {
|
||||
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) => {
|
||||
const session = await getSession(context)
|
||||
const id = context.query.id?.toString()
|
||||
|
||||
return {
|
||||
props: {
|
||||
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 (
|
||||
<div className="tldraw">
|
||||
<Tldraw
|
||||
id="develop"
|
||||
{...fileSystemEvents}
|
||||
onMount={handleMount}
|
||||
onSignIn={handleSignIn}
|
||||
onSignOut={handleSignOut}
|
||||
onPersist={handlePersist}
|
||||
showSponsorLink={true}
|
||||
/>
|
||||
<Tldraw id="develop" {...fileSystemEvents} onMount={handleMount} onPersist={handlePersist} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ export default function Scroll() {
|
|||
return (
|
||||
<div style={{ height: 1600, width: 1600, padding: 200 }}>
|
||||
<div style={{ width: '100%', height: '100%', position: 'relative' }}>
|
||||
<Tldraw id="develop" onMount={handleMount} showSponsorLink={true} />
|
||||
<Tldraw id="develop" onMount={handleMount} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
/* eslint-disable no-inner-declarations */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import Vec from '@tldraw/vec'
|
||||
import * as React from 'react'
|
||||
import { useCursorAnimation } from '~hooks'
|
||||
import type { TLShape, TLUser } from '~types'
|
||||
|
||||
interface UserProps {
|
||||
|
@ -11,12 +9,17 @@ interface UserProps {
|
|||
|
||||
export function User({ user }: UserProps) {
|
||||
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 (
|
||||
<svg
|
||||
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"
|
||||
viewBox="0 0 35 35"
|
||||
fill="none"
|
||||
|
|
|
@ -15,5 +15,4 @@ export * from './usePreventNavigationCss'
|
|||
export * from './useBoundsEvents'
|
||||
export * from './usePosition'
|
||||
export * from './useKeyEvents'
|
||||
export * from './useCursorAnimation'
|
||||
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;
|
||||
will-change: transform;
|
||||
}
|
||||
.tl-animated {
|
||||
transition: transform 200ms linear;
|
||||
}
|
||||
.tl-indicator {
|
||||
fill: transparent;
|
||||
stroke-width: calc(1.5px * var(--tl-scale));
|
||||
|
|
|
@ -36,12 +36,12 @@ export function useZoomEvents<T extends HTMLElement>(
|
|||
e.preventDefault()
|
||||
if (inputs.isPinching) return
|
||||
|
||||
const { offset } = normalizeWheel(e)
|
||||
const [x, y, z] = normalizeWheel(e)
|
||||
|
||||
// alt+scroll or ctrl+scroll = zoom
|
||||
if ((e.altKey || e.ctrlKey || e.metaKey) && e.buttons === 0) {
|
||||
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)
|
||||
|
||||
callbacks.onZoom?.({ ...info, delta }, e)
|
||||
|
@ -52,9 +52,9 @@ export function useZoomEvents<T extends HTMLElement>(
|
|||
const delta = Vec.mul(
|
||||
e.shiftKey && !Utils.isDarwin
|
||||
? // shift+scroll = pan horizontally
|
||||
[offset[1], 0]
|
||||
[y, 0]
|
||||
: // scroll = pan vertically (or in any direction on a trackpad)
|
||||
[...offset],
|
||||
[x, y],
|
||||
0.5
|
||||
)
|
||||
|
||||
|
@ -146,45 +146,26 @@ export function useZoomEvents<T extends HTMLElement>(
|
|||
}
|
||||
|
||||
// Reasonable defaults
|
||||
const PIXEL_STEP = 10
|
||||
const LINE_HEIGHT = 40
|
||||
const PAGE_HEIGHT = 800
|
||||
const MAX_ZOOM_STEP = 10
|
||||
|
||||
function normalizeWheel(event: any) {
|
||||
let sX = 0,
|
||||
sY = 0, // spinX, spinY
|
||||
pX = 0,
|
||||
pY = 0 // pixelX, pixelY
|
||||
// Adapted from https://stackoverflow.com/a/13650579
|
||||
function normalizeWheel(event: WheelEvent) {
|
||||
const { deltaY, deltaX } = event
|
||||
|
||||
// Legacy
|
||||
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
|
||||
let deltaZ = 0
|
||||
|
||||
// side scrolling on FF with DOMMouseScroll
|
||||
if ('axis' in event && event.axis === event.HORIZONTAL_AXIS) {
|
||||
sX = sY
|
||||
sY = 0
|
||||
}
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
const signY = Math.sign(event.deltaY)
|
||||
const absDeltaY = Math.abs(event.deltaY)
|
||||
|
||||
pX = 'deltaX' in event ? event.deltaX : sX * PIXEL_STEP
|
||||
pY = 'deltaY' in event ? event.deltaY : sY * PIXEL_STEP
|
||||
let dy = deltaY
|
||||
|
||||
if ((pX || pY) && event.deltaMode) {
|
||||
if (event.deltaMode == 1) {
|
||||
// delta in LINE units
|
||||
pX *= LINE_HEIGHT
|
||||
pY *= LINE_HEIGHT
|
||||
} else {
|
||||
// delta in PAGE units
|
||||
pX *= PAGE_HEIGHT
|
||||
pY *= PAGE_HEIGHT
|
||||
if (absDeltaY > MAX_ZOOM_STEP) {
|
||||
dy = MAX_ZOOM_STEP * signY
|
||||
}
|
||||
|
||||
deltaZ = dy
|
||||
}
|
||||
|
||||
// Fall-back if spin cannot be determined
|
||||
if (pX && !sX) sX = pX < 1 ? -1 : 1
|
||||
if (pY && !sY) sY = pY < 1 ? -1 : 1
|
||||
return { spin: [sX, sY], offset: [pX, pY] }
|
||||
return [deltaX, deltaY, deltaZ]
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
if (typeof window === 'undefined') return false
|
||||
const ua = window.navigator.userAgent
|
||||
|
|
|
@ -74,11 +74,6 @@ export interface TldrawProps extends TDCallbacks {
|
|||
*/
|
||||
showTools?: boolean
|
||||
|
||||
/**
|
||||
* (optional) Whether to show a sponsor link for Tldraw.
|
||||
*/
|
||||
showSponsorLink?: boolean
|
||||
|
||||
/**
|
||||
* (optional) Whether to show the UI.
|
||||
*/
|
||||
|
@ -118,7 +113,6 @@ export function Tldraw({
|
|||
readOnly = false,
|
||||
disableAssets = false,
|
||||
darkMode = false,
|
||||
showSponsorLink,
|
||||
onMount,
|
||||
onChange,
|
||||
onChangePresence,
|
||||
|
@ -127,8 +121,6 @@ export function Tldraw({
|
|||
onSaveProjectAs,
|
||||
onOpenProject,
|
||||
onOpenMedia,
|
||||
onSignOut,
|
||||
onSignIn,
|
||||
onUndo,
|
||||
onRedo,
|
||||
onPersist,
|
||||
|
@ -155,8 +147,6 @@ export function Tldraw({
|
|||
onSaveProjectAs,
|
||||
onOpenProject,
|
||||
onOpenMedia,
|
||||
onSignOut,
|
||||
onSignIn,
|
||||
onUndo,
|
||||
onRedo,
|
||||
onPersist,
|
||||
|
@ -184,8 +174,6 @@ export function Tldraw({
|
|||
onSaveProjectAs,
|
||||
onOpenProject,
|
||||
onOpenMedia,
|
||||
onSignOut,
|
||||
onSignIn,
|
||||
onUndo,
|
||||
onRedo,
|
||||
onPersist,
|
||||
|
@ -256,8 +244,6 @@ export function Tldraw({
|
|||
onSaveProjectAs,
|
||||
onOpenProject,
|
||||
onOpenMedia,
|
||||
onSignOut,
|
||||
onSignIn,
|
||||
onUndo,
|
||||
onRedo,
|
||||
onPersist,
|
||||
|
@ -280,8 +266,6 @@ export function Tldraw({
|
|||
onSaveProjectAs,
|
||||
onOpenProject,
|
||||
onOpenMedia,
|
||||
onSignOut,
|
||||
onSignIn,
|
||||
onUndo,
|
||||
onRedo,
|
||||
onPersist,
|
||||
|
@ -323,7 +307,6 @@ export function Tldraw({
|
|||
showZoom={showZoom}
|
||||
showTools={showTools}
|
||||
showUI={showUI}
|
||||
showSponsorLink={showSponsorLink}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</TldrawContext.Provider>
|
||||
|
@ -341,7 +324,6 @@ interface InnerTldrawProps {
|
|||
showStyles: boolean
|
||||
showUI: boolean
|
||||
showTools: boolean
|
||||
showSponsorLink?: boolean
|
||||
}
|
||||
|
||||
const InnerTldraw = React.memo(function InnerTldraw({
|
||||
|
@ -353,7 +335,6 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
|||
showZoom,
|
||||
showStyles,
|
||||
showTools,
|
||||
showSponsorLink,
|
||||
readOnly,
|
||||
showUI,
|
||||
}: InnerTldrawProps) {
|
||||
|
@ -438,8 +419,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
|||
const hideIndicators =
|
||||
(isInSession && state.appState.status !== TDStatus.Brushing) || !isSelecting
|
||||
|
||||
const hideCloneHandles =
|
||||
isInSession || !isSelecting || !settings.showCloneHandles || pageState.camera.zoom < 0.2
|
||||
const hideCloneHandles = isInSession || !isSelecting || pageState.camera.zoom < 0.2
|
||||
|
||||
const translation = useTranslation(settings.language)
|
||||
|
||||
|
@ -551,7 +531,6 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
|||
showMultiplayerMenu={showMultiplayerMenu}
|
||||
showStyles={showStyles}
|
||||
showZoom={showZoom}
|
||||
sponsor={showSponsorLink}
|
||||
/>
|
||||
<StyledSpacer />
|
||||
{showTools && !readOnly && <ToolsPanel />}
|
||||
|
|
|
@ -2,11 +2,10 @@ import * as React from 'react'
|
|||
import { styled } from '~styles'
|
||||
|
||||
export const Divider = styled('hr', {
|
||||
height: 1,
|
||||
marginTop: '$1',
|
||||
marginRight: '-$0',
|
||||
marginBottom: '$1',
|
||||
marginLeft: '-$0',
|
||||
height: 0,
|
||||
paddingTop: 1,
|
||||
width: 'calc(100%+8px)',
|
||||
backgroundColor: '$hover',
|
||||
border: 'none',
|
||||
borderBottom: '1px solid $hover',
|
||||
margin: '$2 -4px',
|
||||
})
|
||||
|
|
|
@ -20,7 +20,7 @@ export const DMRadioItem = styled(RadioItem, {
|
|||
isActive: {
|
||||
true: {
|
||||
backgroundColor: '$selected',
|
||||
color: '$panel',
|
||||
color: 'white',
|
||||
},
|
||||
false: {},
|
||||
},
|
||||
|
|
|
@ -10,6 +10,7 @@ export const MenuContent = styled('div', {
|
|||
minWidth: 180,
|
||||
pointerEvents: 'all',
|
||||
backgroundColor: '$panel',
|
||||
border: '1px solid $panelContrast',
|
||||
boxShadow: '$panel',
|
||||
padding: '$2 $2',
|
||||
borderRadius: '$3',
|
||||
|
|
|
@ -8,27 +8,34 @@ export const Panel = styled('div', {
|
|||
padding: '$2',
|
||||
border: '1px solid $panelContrast',
|
||||
gap: 0,
|
||||
overflow: 'hidden',
|
||||
variants: {
|
||||
side: {
|
||||
center: {
|
||||
borderRadius: '$4',
|
||||
borderRadius: 9,
|
||||
},
|
||||
left: {
|
||||
padding: 0,
|
||||
borderTop: 0,
|
||||
borderLeft: 0,
|
||||
borderTopRightRadius: '$1',
|
||||
borderBottomRightRadius: '$3',
|
||||
borderBottomLeftRadius: '$1',
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 9,
|
||||
borderBottomLeftRadius: 0,
|
||||
},
|
||||
right: {
|
||||
padding: 0,
|
||||
borderTop: 0,
|
||||
borderRight: 0,
|
||||
borderTopLeftRadius: '$1',
|
||||
borderBottomLeftRadius: '$3',
|
||||
borderBottomRightRadius: '$1',
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 9,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'& hr': {
|
||||
height: 10,
|
||||
width: '100%',
|
||||
backgroundColor: 'red',
|
||||
border: 'none',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -12,7 +12,6 @@ export interface RowButtonProps {
|
|||
disabled?: boolean
|
||||
kbd?: string
|
||||
variant?: 'wide' | 'styleMenu'
|
||||
isSponsor?: boolean
|
||||
isActive?: boolean
|
||||
isWarning?: boolean
|
||||
hasIndicator?: boolean
|
||||
|
@ -147,12 +146,6 @@ export const StyledRowButton = styled('button', {
|
|||
width: 'auto',
|
||||
},
|
||||
},
|
||||
isSponsor: {
|
||||
true: {
|
||||
color: '#eb30a2',
|
||||
},
|
||||
false: {},
|
||||
},
|
||||
isWarning: {
|
||||
true: {
|
||||
color: '$warn',
|
||||
|
@ -165,34 +158,4 @@ export const StyledRowButton = styled('button', {
|
|||
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
|
||||
disabled?: boolean
|
||||
isActive?: boolean
|
||||
isSponsor?: boolean
|
||||
isToolLocked?: boolean
|
||||
variant?: 'icon' | 'text' | 'circle' | 'primary'
|
||||
children: React.ReactNode
|
||||
|
@ -29,7 +28,6 @@ export const ToolButton = React.forwardRef<HTMLButtonElement, ToolButtonProps>(
|
|||
isToolLocked = false,
|
||||
disabled = false,
|
||||
isActive = false,
|
||||
isSponsor = false,
|
||||
onKeyDown,
|
||||
id,
|
||||
...rest
|
||||
|
@ -40,7 +38,6 @@ export const ToolButton = React.forwardRef<HTMLButtonElement, ToolButtonProps>(
|
|||
<StyledToolButton
|
||||
ref={ref}
|
||||
isActive={isActive}
|
||||
isSponsor={isSponsor}
|
||||
variant={variant}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
|
@ -129,9 +126,9 @@ export const StyledToolButton = styled('button', {
|
|||
outline: 'none',
|
||||
cursor: 'pointer',
|
||||
pointerEvents: 'all',
|
||||
border: 'none',
|
||||
height: '40px',
|
||||
width: '40px',
|
||||
border: '1px solid $panel',
|
||||
'-webkit-tap-highlight-color': 'transparent',
|
||||
'tap-highlight-color': 'transparent',
|
||||
|
||||
|
@ -165,6 +162,7 @@ export const StyledToolButton = styled('button', {
|
|||
padding: 0,
|
||||
height: 32,
|
||||
width: 32,
|
||||
border: 'none',
|
||||
[`& ${StyledToolButtonInner}`]: {
|
||||
border: '1px solid $panelContrast',
|
||||
borderRadius: '100%',
|
||||
|
@ -176,13 +174,6 @@ export const StyledToolButton = styled('button', {
|
|||
},
|
||||
},
|
||||
},
|
||||
isSponsor: {
|
||||
true: {
|
||||
[`${StyledToolButtonInner}`]: {
|
||||
backgroundColor: '$sponsorContrast',
|
||||
},
|
||||
},
|
||||
},
|
||||
isActive: {
|
||||
true: {},
|
||||
false: {},
|
||||
|
@ -234,7 +225,6 @@ export const StyledToolButton = styled('button', {
|
|||
css: {
|
||||
[`&:hover:not(:disabled) ${StyledToolButtonInner}`]: {
|
||||
backgroundColor: '$hover',
|
||||
border: '1px solid $panel',
|
||||
},
|
||||
[`&:focus:not(:disabled) ${StyledToolButtonInner}`]: {
|
||||
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'
|
||||
|
||||
export function UndoIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
export function UndoIcon({
|
||||
flipHorizontal,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & { flipHorizontal?: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
width={32}
|
||||
height={32}
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 15 15"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...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" />
|
||||
</svg>
|
||||
|
|
|
@ -4,15 +4,11 @@ export * from './DashDashedIcon'
|
|||
export * from './DashDottedIcon'
|
||||
export * from './DashDrawIcon'
|
||||
export * from './DashSolidIcon'
|
||||
export * from './IsFilledIcon'
|
||||
export * from './RedoIcon'
|
||||
export * from './TrashIcon'
|
||||
export * from './UndoIcon'
|
||||
export * from './SizeSmallIcon'
|
||||
export * from './SizeMediumIcon'
|
||||
export * from './SizeLargeIcon'
|
||||
export * from './EraserIcon'
|
||||
export * from './MultiplayerIcon'
|
||||
export * from './DiscordIcon'
|
||||
export * from './LineIcon'
|
||||
export * from './QuestionMarkIcon'
|
||||
|
|
|
@ -6,14 +6,19 @@ import { styled } from '~styles'
|
|||
import { useTldrawApp } from '~hooks'
|
||||
import { TDSnapshot } from '~types'
|
||||
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 { MenuContent } from '~components/Primitives/MenuContent'
|
||||
import { DMContent, DMDivider } from '~components/Primitives/DropdownMenu'
|
||||
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
||||
import { DiscordIcon } from '~components/Primitives/icons'
|
||||
import { LanguageMenu } from '~components/TopPanel/LanguageMenu/LanguageMenu'
|
||||
import { KeyboardShortcutDialog } from './keyboardShortcutDialog'
|
||||
import { Divider } from '~components/Primitives/Divider'
|
||||
|
||||
const isDebugModeSelector = (s: TDSnapshot) => s.settings.isDebugMode
|
||||
const dockPositionState = (s: TDSnapshot) => s.settings.dockPosition
|
||||
|
@ -28,17 +33,17 @@ export function HelpPanel() {
|
|||
return (
|
||||
<Popover.Root>
|
||||
<PopoverAnchor dir="ltr">
|
||||
<Popover.Trigger asChild dir="ltr">
|
||||
<Popover.Trigger dir="ltr" asChild>
|
||||
<HelpButton side={side} debug={isDebugMode} bp={breakpoints}>
|
||||
<QuestionMarkIcon />
|
||||
</HelpButton>
|
||||
</Popover.Trigger>
|
||||
</PopoverAnchor>
|
||||
<Popover.Content dir="ltr">
|
||||
<Popover.Content dir="ltr" asChild>
|
||||
<StyledContent style={{ visibility: isKeyboardShortcutsOpen ? 'hidden' : 'visible' }}>
|
||||
<LanguageMenuDropdown />
|
||||
<KeyboardShortcutDialog onOpenChange={setIsKeyboardShortcutsOpen} />
|
||||
<DMDivider />
|
||||
<Divider />
|
||||
<Links />
|
||||
</StyledContent>
|
||||
</Popover.Content>
|
||||
|
@ -60,9 +65,14 @@ const LanguageMenuDropdown = () => {
|
|||
}
|
||||
|
||||
const linksData = [
|
||||
{ id: 'github', title: 'Github', icon: GitHubLogoIcon, url: 'https://github.com/tldraw/tldraw' },
|
||||
{ id: 'twitter', title: 'Twitter', icon: TwitterLogoIcon, url: 'https://twitter.com/tldraw' },
|
||||
{ id: 'discord', title: 'Discord', icon: DiscordIcon, url: 'https://discord.gg/SBBEVCA4PG' },
|
||||
{ id: 'github', icon: GitHubLogoIcon, url: 'https://github.com/tldraw/tldraw' },
|
||||
{ id: 'twitter', icon: TwitterLogoIcon, url: 'https://twitter.com/tldraw' },
|
||||
{ id: 'discord', icon: DiscordIcon, url: 'https://discord.gg/SBBEVCA4PG' },
|
||||
{
|
||||
id: 'become.a.sponsor',
|
||||
icon: HeartFilledIcon,
|
||||
url: 'https://github.com/sponsors/steveruizok',
|
||||
},
|
||||
]
|
||||
|
||||
const Links = () => {
|
||||
|
@ -71,7 +81,7 @@ const Links = () => {
|
|||
{linksData.map((item) => (
|
||||
<a key={item.id} href={item.url} target="_blank" rel="nofollow">
|
||||
<RowButton id={`TD-Link-${item.id}`} variant="wide">
|
||||
{item.title}
|
||||
<FormattedMessage id={item.id} />
|
||||
<SmallIcon>
|
||||
<item.icon />
|
||||
</SmallIcon>
|
||||
|
@ -92,10 +102,10 @@ const HelpButton = styled('button', {
|
|||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: 'none',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: '$panel',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '$panel',
|
||||
border: '1px solid $panelContrast',
|
||||
bottom: 10,
|
||||
color: '$text',
|
||||
variants: {
|
||||
|
|
|
@ -136,7 +136,7 @@ const StyledPanel = styled(Panel, {
|
|||
bp: {
|
||||
mobile: {
|
||||
padding: '$0',
|
||||
borderRadius: '$3',
|
||||
borderRadius: '10px',
|
||||
},
|
||||
small: {
|
||||
padding: '$2',
|
||||
|
|
|
@ -83,6 +83,7 @@ export function KeyboardShortcutDialog({
|
|||
|
||||
return (
|
||||
<Dialog.Root onOpenChange={onOpenChange}>
|
||||
{/* // todo: hide if no keyboard is attached */}
|
||||
<Dialog.Trigger asChild>
|
||||
<RowButton id="TD-HelpItem-Keyboard" variant="wide">
|
||||
<FormattedMessage id="keyboard.shortcuts" />
|
||||
|
|
|
@ -6,6 +6,7 @@ import { SmallIcon } from '~components/Primitives/SmallIcon'
|
|||
import { useTldrawApp } from '~hooks'
|
||||
import { TDLanguage, TRANSLATIONS } from '~translations'
|
||||
import { TDSnapshot } from '~types'
|
||||
import { Divider } from '~components/Primitives/Divider'
|
||||
|
||||
const languageSelector = (s: TDSnapshot) => s.settings.language
|
||||
|
||||
|
@ -32,7 +33,7 @@ export function LanguageMenu() {
|
|||
{label}
|
||||
</DMCheckboxItem>
|
||||
))}
|
||||
<DMDivider />
|
||||
<Divider />
|
||||
<a
|
||||
href="https://github.com/tldraw/tldraw/blob/main/guides/translation.md"
|
||||
target="_blank"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 { useTldrawApp } from '~hooks'
|
||||
import { PreferencesMenu } from '../PreferencesMenu'
|
||||
|
@ -10,7 +10,6 @@ import {
|
|||
DMSubMenu,
|
||||
DMTriggerIcon,
|
||||
} from '~components/Primitives/DropdownMenu'
|
||||
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
||||
import { useFileSystemHandlers } from '~hooks'
|
||||
import { preventEvent } from '~components/preventEvent'
|
||||
import { TDExportType, TDSnapshot } from '~types'
|
||||
|
@ -18,7 +17,6 @@ import { Divider } from '~components/Primitives/Divider'
|
|||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
|
||||
interface MenuProps {
|
||||
sponsor: boolean | undefined
|
||||
readOnly: boolean
|
||||
}
|
||||
|
||||
|
@ -30,7 +28,7 @@ const disableAssetsSelector = (s: TDSnapshot) => {
|
|||
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 intl = useIntl()
|
||||
|
||||
|
@ -80,14 +78,6 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
app.exportJson()
|
||||
}, [app])
|
||||
|
||||
const handleSignIn = React.useCallback(() => {
|
||||
app.callbacks.onSignIn?.(app)
|
||||
}, [app])
|
||||
|
||||
const handleSignOut = React.useCallback(() => {
|
||||
app.callbacks.onSignOut?.(app)
|
||||
}, [app])
|
||||
|
||||
const handleCut = React.useCallback(() => {
|
||||
app.cut()
|
||||
}, [app])
|
||||
|
@ -123,8 +113,6 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
app.callbacks.onSaveProjectAs ||
|
||||
app.callbacks.onExport
|
||||
|
||||
const showSignInOutMenu = app.callbacks.onSignIn || app.callbacks.onSignOut
|
||||
|
||||
const hasSelection = numberOfSelectedIds > 0
|
||||
|
||||
return (
|
||||
|
@ -186,7 +174,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
>
|
||||
<FormattedMessage id="redo" />
|
||||
</DMItem>
|
||||
<DMDivider dir="ltr" />
|
||||
<Divider />
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
disabled={!hasSelection || readOnly}
|
||||
|
@ -213,7 +201,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
>
|
||||
<FormattedMessage id="paste" />
|
||||
</DMItem>
|
||||
<DMDivider dir="ltr" />
|
||||
<Divider />
|
||||
<DMSubMenu
|
||||
label={`${intl.formatMessage({ id: 'copy.as' })}...`}
|
||||
size="small"
|
||||
|
@ -251,7 +239,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
</DMItem>
|
||||
</DMSubMenu>
|
||||
|
||||
<DMDivider dir="ltr" />
|
||||
<Divider />
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
onClick={handleSelectAll}
|
||||
|
@ -268,7 +256,7 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
>
|
||||
<FormattedMessage id="select.none" />
|
||||
</DMItem>
|
||||
<DMDivider dir="ltr" />
|
||||
<Divider />
|
||||
<DMItem onSelect={handleDelete} disabled={!hasSelection} kbd="⌫" id="TD-MenuItem-Delete">
|
||||
<FormattedMessage id="delete" />
|
||||
</DMItem>
|
||||
|
@ -315,26 +303,8 @@ export const Menu = React.memo(function Menu({ sponsor, readOnly }: MenuProps) {
|
|||
<FormattedMessage id="zoom.to.selection" />
|
||||
</DMItem>
|
||||
</DMSubMenu>
|
||||
<DMDivider dir="ltr" />
|
||||
<Divider />
|
||||
<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>
|
||||
</DropdownMenu.Root>
|
||||
)
|
||||
|
|
|
@ -9,6 +9,7 @@ import { TLDR } from '~state/TLDR'
|
|||
import { Utils } from '@tldraw/core'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
import { MultiplayerIcon2 } from '~components/Primitives/icons/MultiplayerIcon2'
|
||||
import { Divider } from '~components/Primitives/Divider'
|
||||
|
||||
const roomSelector = (state: TDSnapshot) => state.room
|
||||
|
||||
|
@ -124,7 +125,7 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() {
|
|||
<FormattedMessage id="copy.readonly.link" />
|
||||
<SmallIcon>{copied ? <CheckIcon /> : <ClipboardIcon />}</SmallIcon>
|
||||
</DMItem>
|
||||
<DMDivider id="TD-Multiplayer-CopyInviteLinkDivider" />
|
||||
<Divider />
|
||||
<DMItem
|
||||
id="TD-Multiplayer-CreateMultiplayerProject"
|
||||
onClick={handleCreateMultiplayerProject}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { SmallIcon } from '~components/Primitives/SmallIcon'
|
|||
import { RowButton } from '~components/Primitives/RowButton'
|
||||
import { ToolButton } from '~components/Primitives/ToolButton'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
import { Divider } from '~components/Primitives/Divider'
|
||||
|
||||
const sortedSelector = (s: TDSnapshot) =>
|
||||
Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0))
|
||||
|
@ -148,7 +149,7 @@ function PageMenuContent({ onClose }: { onClose: () => void }) {
|
|||
</ButtonWithOptions>
|
||||
))}
|
||||
</DropdownMenu.RadioGroup>
|
||||
<DMDivider />
|
||||
<Divider />
|
||||
<DropdownMenu.Item onSelect={handleCreatePage} asChild>
|
||||
<RowButton>
|
||||
<span>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { DMCheckboxItem, DMDivider, DMSubMenu } from '~components/Primitives/Dro
|
|||
import { useTldrawApp } from '~hooks'
|
||||
import { TDDockPosition, TDExportBackground, TDSnapshot } from '~types'
|
||||
import { styled } from '~styles'
|
||||
import { Divider } from '~components/Primitives/Divider'
|
||||
|
||||
const settingsSelector = (s: TDSnapshot) => s.settings
|
||||
|
||||
|
@ -94,7 +95,7 @@ export function PreferencesMenu() {
|
|||
>
|
||||
<FormattedMessage id="preferences.debug.mode" />
|
||||
</DMCheckboxItem>
|
||||
<DMDivider />
|
||||
<Divider />
|
||||
<DMCheckboxItem
|
||||
checked={settings.showGrid}
|
||||
onCheckedChange={toggleGrid}
|
||||
|
@ -117,34 +118,6 @@ export function PreferencesMenu() {
|
|||
>
|
||||
<FormattedMessage id="preferences.keep.stylemenu.open" />
|
||||
</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}>
|
||||
{DockPosition.map((position) => (
|
||||
<DMCheckboxItem
|
||||
|
|
|
@ -241,7 +241,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
|||
size={18}
|
||||
strokeWidth={2.5}
|
||||
fill={
|
||||
displayedStyle.isFilled ? fills.light[style as ColorStyle] : 'transparent'
|
||||
displayedStyle.isFilled ? fills[theme][style as ColorStyle] : 'transparent'
|
||||
}
|
||||
stroke={strokes.light[style as ColorStyle]}
|
||||
/>
|
||||
|
@ -337,7 +337,7 @@ export const StyleMenu = React.memo(function ColorMenu() {
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
<DMDivider />
|
||||
<Divider />
|
||||
<DMCheckboxItem
|
||||
variant="styleMenu"
|
||||
checked={keepOpen}
|
||||
|
@ -403,6 +403,7 @@ const OverlapIcons = styled('div', {
|
|||
gridColumn: 1,
|
||||
gridRow: 1,
|
||||
},
|
||||
backgroundColor: '$panel',
|
||||
})
|
||||
|
||||
const FontIcon = styled('div', {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { ZoomMenu } from './ZoomMenu'
|
|||
import { StyleMenu } from './StyleMenu'
|
||||
import { Panel } from '~components/Primitives/Panel'
|
||||
import { ToolButton } from '~components/Primitives/ToolButton'
|
||||
import { RedoIcon, UndoIcon } from '~components/Primitives/icons'
|
||||
import { UndoIcon } from '~components/Primitives/icons'
|
||||
import { useTldrawApp } from '~hooks'
|
||||
import { MultiplayerMenu } from './MultiplayerMenu'
|
||||
|
||||
|
@ -17,7 +17,6 @@ interface TopPanelProps {
|
|||
showStyles: boolean
|
||||
showZoom: boolean
|
||||
showMultiplayerMenu: boolean
|
||||
sponsor?: boolean
|
||||
}
|
||||
|
||||
export function TopPanel({
|
||||
|
@ -26,7 +25,6 @@ export function TopPanel({
|
|||
showMenu,
|
||||
showStyles,
|
||||
showZoom,
|
||||
sponsor,
|
||||
showMultiplayerMenu,
|
||||
}: TopPanelProps) {
|
||||
const app = useTldrawApp()
|
||||
|
@ -35,7 +33,7 @@ export function TopPanel({
|
|||
<StyledTopPanel>
|
||||
{(showMenu || showPages) && (
|
||||
<Panel side="left" id="TD-MenuPanel">
|
||||
{showMenu && <Menu sponsor={sponsor} readOnly={readOnly} />}
|
||||
{showMenu && <Menu readOnly={readOnly} />}
|
||||
{showMultiplayerMenu && <MultiplayerMenu />}
|
||||
{showPages && <PageMenu />}
|
||||
</Panel>
|
||||
|
@ -47,12 +45,11 @@ export function TopPanel({
|
|||
<ReadOnlyLabel>Read Only</ReadOnlyLabel>
|
||||
) : (
|
||||
<>
|
||||
{' '}
|
||||
<ToolButton>
|
||||
<UndoIcon onClick={app.undo} />
|
||||
</ToolButton>
|
||||
<ToolButton>
|
||||
<RedoIcon onClick={app.redo} />
|
||||
<UndoIcon onClick={app.redo} flipHorizontal />
|
||||
</ToolButton>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -30,10 +30,10 @@ export const ZoomMenu = React.memo(function ZoomMenu() {
|
|||
<FormattedMessage id="zoom.out" />
|
||||
</DMItem>
|
||||
<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 onSelect={preventEvent} onClick={app.zoomToFit} kbd="⇧1" id="TD-Zoom-To_Fit">
|
||||
<FormattedMessage id="to.fit" />
|
||||
<FormattedMessage id="zoom.to.fit" />
|
||||
</DMItem>
|
||||
<DMItem
|
||||
onSelect={preventEvent}
|
||||
|
@ -41,7 +41,7 @@ export const ZoomMenu = React.memo(function ZoomMenu() {
|
|||
kbd="⇧2"
|
||||
id="TD-Zoom-To_Selection"
|
||||
>
|
||||
<FormattedMessage id="to.selection" />
|
||||
<FormattedMessage id="zoom.to.selection" />
|
||||
</DMItem>
|
||||
</DMContent>
|
||||
</DropdownMenu.Root>
|
||||
|
|
|
@ -119,14 +119,6 @@ export interface TDCallbacks {
|
|||
* (optional) A callback to run when the opens a file to upload.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -608,73 +600,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
/* ----------- Managing Multiplayer State ----------- */
|
||||
|
||||
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) => {
|
||||
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
|
||||
// related shapes that may not have changed on our side, but which
|
||||
// were deleted on the server.
|
||||
this.prevShapes = shapes
|
||||
this.prevBindings = bindings
|
||||
this.prevAssets = assets
|
||||
|
||||
const nextShapes = {
|
||||
...shapes,
|
||||
|
@ -914,8 +836,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
})
|
||||
|
||||
this.state.document = next.document
|
||||
// this.prevShapes = nextShapes
|
||||
// this.prevBindings = nextBindings
|
||||
|
||||
return next
|
||||
}, true)
|
||||
|
@ -1941,7 +1861,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
break
|
||||
}
|
||||
case 'text/plain': {
|
||||
console.log(str)
|
||||
if (str.startsWith('<svg')) {
|
||||
getSvgFromText(str)
|
||||
} else {
|
||||
|
@ -2153,30 +2072,30 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
svg.setAttribute('height', commonBounds.height.toString())
|
||||
|
||||
// Set export background
|
||||
const exportBackground: TDExportBackground = this.settings.exportBackground;
|
||||
const darkBackground = '#212529';
|
||||
const exportBackground: TDExportBackground = this.settings.exportBackground
|
||||
const darkBackground = '#212529'
|
||||
const lightBackground = 'rgb(248, 249, 250)'
|
||||
|
||||
switch(exportBackground) {
|
||||
switch (exportBackground) {
|
||||
case TDExportBackground.Auto: {
|
||||
svg.style.setProperty(
|
||||
'background-color',
|
||||
this.settings.isDarkMode ? darkBackground : lightBackground
|
||||
)
|
||||
break;
|
||||
break
|
||||
}
|
||||
case TDExportBackground.Dark: {
|
||||
svg.style.setProperty('background-color', darkBackground)
|
||||
break;
|
||||
break
|
||||
}
|
||||
case TDExportBackground.Light: {
|
||||
svg.style.setProperty('background-color', lightBackground)
|
||||
break;
|
||||
break
|
||||
}
|
||||
case TDExportBackground.Transparent:
|
||||
case TDExportBackground.Transparent:
|
||||
default: {
|
||||
svg.style.setProperty('background-color', 'transparent')
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2384,7 +2303,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
opts
|
||||
|
||||
const svg = await this.getSvg(ids, {
|
||||
includeFonts: format !== TDExportType.SVG
|
||||
includeFonts: format !== TDExportType.SVG,
|
||||
})
|
||||
|
||||
if (!svg) return
|
||||
|
@ -4166,7 +4085,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
|
||||
getShapeUtil = TLDR.getShapeUtil
|
||||
|
||||
static version = 15.4
|
||||
static version = 15.5
|
||||
|
||||
static defaultDocument: TDDocument = {
|
||||
id: 'doc',
|
||||
|
@ -4213,7 +4132,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
showGrid: false,
|
||||
language: 'en',
|
||||
dockPosition: 'bottom',
|
||||
exportBackground: TDExportBackground.Transparent
|
||||
exportBackground: TDExportBackground.Transparent,
|
||||
},
|
||||
appState: {
|
||||
status: TDStatus.Idle,
|
||||
|
|
|
@ -50,7 +50,7 @@ TldrawTestApp {
|
|||
"shapes": Object {},
|
||||
},
|
||||
},
|
||||
"version": 15.4,
|
||||
"version": 15.5,
|
||||
},
|
||||
"settings": Object {
|
||||
"dockPosition": "bottom",
|
||||
|
@ -202,7 +202,7 @@ TldrawTestApp {
|
|||
},
|
||||
},
|
||||
},
|
||||
"version": 15.4,
|
||||
"version": 15.5,
|
||||
},
|
||||
"settings": Object {
|
||||
"dockPosition": "bottom",
|
||||
|
@ -375,7 +375,7 @@ TldrawTestApp {
|
|||
"shapes": Object {},
|
||||
},
|
||||
},
|
||||
"version": 15.4,
|
||||
"version": 15.5,
|
||||
},
|
||||
"settings": Object {
|
||||
"dockPosition": "bottom",
|
||||
|
@ -495,10 +495,7 @@ TldrawTestApp {
|
|||
"pointShape": [Function],
|
||||
"pointer": -1,
|
||||
"pressKey": [Function],
|
||||
"prevAssets": Object {},
|
||||
"prevBindings": Object {},
|
||||
"prevSelectedIds": Array [],
|
||||
"prevShapes": Object {},
|
||||
"previousPoint": Array [
|
||||
0,
|
||||
0,
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
/* 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 {
|
||||
const { document, settings } = state
|
||||
|
@ -122,6 +130,10 @@ export function migrate(state: TDSnapshot, newVersion: number): TDSnapshot {
|
|||
settings.dockPosition = 'bottom'
|
||||
}
|
||||
|
||||
if (version < 15.5) {
|
||||
settings.exportBackground = TDExportBackground.Transparent
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
Object.values(document.pageStates).forEach((pageState) => {
|
||||
pageState.selectedIds = pageState.selectedIds.filter((id) => {
|
||||
|
|
|
@ -23,7 +23,7 @@ export class ImageUtil extends TDShapeUtil<T, E> {
|
|||
|
||||
isAspectRatioLocked = true
|
||||
|
||||
showCloneHandles = true
|
||||
showCloneHandles = false
|
||||
|
||||
getShape = (props: Partial<T>): T => {
|
||||
return Utils.deepMerge<T>(
|
||||
|
@ -46,6 +46,7 @@ export class ImageUtil extends TDShapeUtil<T, E> {
|
|||
Component = TDShapeUtil.Component<T, E, TDMeta>(
|
||||
({ shape, asset = { src: '' }, isBinding, isGhost, meta, events, onShapeChange }, ref) => {
|
||||
const { size, style } = shape
|
||||
const { bindingDistance } = this
|
||||
|
||||
const rImage = React.useRef<HTMLImageElement>(null)
|
||||
const rWrapper = React.useRef<HTMLDivElement>(null)
|
||||
|
@ -65,10 +66,10 @@ export class ImageUtil extends TDShapeUtil<T, E> {
|
|||
className="tl-binding-indicator"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: `calc(${-this.bindingDistance}px * var(--tl-zoom))`,
|
||||
left: `calc(${-this.bindingDistance}px * var(--tl-zoom))`,
|
||||
width: `calc(100% + ${this.bindingDistance * 2}px * var(--tl-zoom))`,
|
||||
height: `calc(100% + ${this.bindingDistance * 2}px * var(--tl-zoom))`,
|
||||
top: `calc(${-bindingDistance}px * var(--tl-zoom))`,
|
||||
left: `calc(${-bindingDistance}px * var(--tl-zoom))`,
|
||||
width: `calc(100% + ${bindingDistance * 2}px * var(--tl-zoom))`,
|
||||
height: `calc(100% + ${bindingDistance * 2}px * var(--tl-zoom))`,
|
||||
backgroundColor: 'var(--tl-selectFill)',
|
||||
}}
|
||||
/>
|
||||
|
@ -85,7 +86,6 @@ export class ImageUtil extends TDShapeUtil<T, E> {
|
|||
src={(asset as TDImageAsset).src}
|
||||
alt="tl_image_asset"
|
||||
draggable={false}
|
||||
// onLoad={onImageLoad}
|
||||
/>
|
||||
</Wrapper>
|
||||
</HTMLContainer>
|
||||
|
|
|
@ -20,7 +20,7 @@ export class VideoUtil extends TDShapeUtil<T, E> {
|
|||
canEdit = true
|
||||
canClone = true
|
||||
isAspectRatioLocked = true
|
||||
showCloneHandles = true
|
||||
showCloneHandles = false
|
||||
isStateful = true // don't unmount
|
||||
|
||||
getShape = (props: Partial<T>): T => {
|
||||
|
|
|
@ -15,8 +15,6 @@ const { styled, createTheme } = createStitches({
|
|||
panelContrast: '#ffffff',
|
||||
selected: 'rgba(66, 133, 244, 1.000)',
|
||||
selectedContrast: '#fefefe',
|
||||
sponsor: '#ec6cb9',
|
||||
sponsorContrast: '#ec6cb944',
|
||||
text: '#333333',
|
||||
tooltip: '#1d1d1d',
|
||||
tooltipContrast: '#ffffff',
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
"zoom.in": "تكبير",
|
||||
"zoom.out": "تصغير",
|
||||
"to": "إلى",
|
||||
"to.selection": "للاختيار",
|
||||
"to.fit": "تناسب",
|
||||
"menu.tools": "أدوات",
|
||||
"menu.transform": "التحويلات",
|
||||
"menu.file": "ملف",
|
||||
|
@ -20,7 +18,6 @@
|
|||
"menu.preferences": "التفضيلات",
|
||||
"menu.sign.in": "تسجيل الدخول",
|
||||
"menu.sign.out": "خروج",
|
||||
"sponsored": "برعاية",
|
||||
"become.a.sponsor": "كن راعياً",
|
||||
"zoom.to.content": "العودة إلى المحتوى",
|
||||
"zoom.to.selection": "تكبير للتحديد",
|
||||
|
@ -101,4 +98,4 @@
|
|||
"keyboard.shortcuts": "اختصارات لوحة المفاتيح",
|
||||
"search": "بحث",
|
||||
"loading": "{dots}تحميل "
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "Zoom ind",
|
||||
"zoom.out": "Zoom ud",
|
||||
"to": "til",
|
||||
"to.selection": "Til valgte",
|
||||
"to.fit": "Til lærred",
|
||||
"menu.file": "Fil",
|
||||
"menu.edit": "Rediger",
|
||||
"menu.view": "Vis",
|
||||
"menu.preferences": "Indstillinger",
|
||||
"menu.sign.in": "Log ind",
|
||||
"menu.sign.out": "Log ud",
|
||||
"sponsored": "Sponsoreret",
|
||||
"become.a.sponsor": "Bliv sponsor",
|
||||
"zoom.to.selection": "Zoom til valgte",
|
||||
"zoom.to.fit": "Zoom til lærred",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "Tilbage",
|
||||
"back": "Bagerst",
|
||||
"language": "Sprog"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "Heranzoomen",
|
||||
"zoom.out": "Herauszoomen",
|
||||
"to": "zu",
|
||||
"to.selection": "Zur Auswahl",
|
||||
"to.fit": "Anpassen",
|
||||
"menu.file": "Datei",
|
||||
"menu.edit": "Bearbeiten",
|
||||
"menu.view": "Ansicht",
|
||||
"menu.preferences": "Präferenzen",
|
||||
"menu.sign.in": "Einloggen",
|
||||
"menu.sign.out": "Ausloggen",
|
||||
"sponsored": "Gesponsert",
|
||||
"become.a.sponsor": "Sponsor werden",
|
||||
"zoom.to.selection": "Zur Auswahl zoomen",
|
||||
"zoom.to.fit": "Zoom anpassen",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "Rückwärts",
|
||||
"back": "Hinten",
|
||||
"language": "Sprache"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "Acercar",
|
||||
"zoom.out": "Alejar",
|
||||
"to": "A",
|
||||
"to.selection": "A la selección",
|
||||
"to.fit": "Ajustar",
|
||||
"menu.file": "Archivo",
|
||||
"menu.edit": "Editar",
|
||||
"menu.view": "Ver",
|
||||
"menu.preferences": "Preferencias",
|
||||
"menu.sign.in": "Iniciar sesión",
|
||||
"menu.sign.out": "Cerrar sesión",
|
||||
"sponsored": "Patrocinado",
|
||||
"become.a.sponsor": "Conviértete en patrocinador",
|
||||
"zoom.to.content": "Acercar al contenido",
|
||||
"zoom.to.selection": "Acercar a la selección",
|
||||
|
@ -99,4 +96,4 @@
|
|||
"top": "Arriba",
|
||||
"search": "Buscar",
|
||||
"page": "Página"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "زوم جلو",
|
||||
"zoom.out": "زوم عقب",
|
||||
"to": "به",
|
||||
"to.selection": "به انتخابشدهها",
|
||||
"to.fit": "به کل صفحه",
|
||||
"menu.file": "فایل",
|
||||
"menu.edit": "ویرایش",
|
||||
"menu.view": "نمایش",
|
||||
"menu.preferences": "تنظیمها",
|
||||
"menu.sign.in": "ورود",
|
||||
"menu.sign.out": "خروج",
|
||||
"sponsored": "حامیان",
|
||||
"become.a.sponsor": " حامی شو",
|
||||
"zoom.to.selection": "نمایش انتخابشدهها",
|
||||
"zoom.to.fit": "نمایش کل صفحه",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "به عقب",
|
||||
"back": "به آخر",
|
||||
"language": "زبان"
|
||||
}
|
||||
}
|
|
@ -10,8 +10,6 @@
|
|||
"zoom.in": "Zoom avant",
|
||||
"zoom.out": "Zoom arrière",
|
||||
"to": "À",
|
||||
"to.selection": "Sélection",
|
||||
"to.fit": "Contenu",
|
||||
"menu.tools": "Outils",
|
||||
"menu.transform": "Transformation",
|
||||
"menu.file": "Fichier",
|
||||
|
@ -20,7 +18,6 @@
|
|||
"menu.preferences": "Préférences",
|
||||
"menu.sign.in": "S'authentifier",
|
||||
"menu.sign.out": "Se déconnecter",
|
||||
"sponsored": "Sponsorisé",
|
||||
"become.a.sponsor": "Devenir un sponsor",
|
||||
"zoom.to.content": "Retour au contenu",
|
||||
"zoom.to.selection": "Ajuster le zoom à la sélection",
|
||||
|
@ -99,4 +96,4 @@
|
|||
"right": "À droite",
|
||||
"top": "En haut",
|
||||
"page": "Page"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "הגדל תצוגה",
|
||||
"zoom.out": "הקטן תצוגה",
|
||||
"to": "ל",
|
||||
"to.selection": "לסימון",
|
||||
"to.fit": "להתאמה",
|
||||
"menu.file": "קובץ",
|
||||
"menu.edit": "עריכה",
|
||||
"menu.view": "תצוגה",
|
||||
"menu.preferences": "מאפיינים",
|
||||
"menu.sign.in": "הירשם",
|
||||
"menu.sign.out": "התנתק",
|
||||
"sponsored": "חסות",
|
||||
"become.a.sponsor": "מתן חסות",
|
||||
"zoom.to.selection": "זום לבחירה",
|
||||
"zoom.to.fit": "זום להתאמה",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "אחורה",
|
||||
"back": "בחזרה",
|
||||
"language": "שפה"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "Ingrandisci",
|
||||
"zoom.out": "Rimpicciolisci",
|
||||
"to": "Imposta",
|
||||
"to.selection": "Adatta alla selezione",
|
||||
"to.fit": "Adatta",
|
||||
"menu.file": "File",
|
||||
"menu.edit": "Modifica",
|
||||
"menu.view": "Visualizzazione",
|
||||
"menu.preferences": "Preferenze",
|
||||
"menu.sign.in": "Accedi",
|
||||
"menu.sign.out": "Esci",
|
||||
"sponsored": "Sponsorizza",
|
||||
"become.a.sponsor": "Sponsorizza",
|
||||
"zoom.to.selection": "Adatta alla selezione",
|
||||
"zoom.to.fit": "Adatta",
|
||||
|
@ -93,4 +90,4 @@
|
|||
"left": "Sinistra",
|
||||
"right": "Destra",
|
||||
"top": "In Alto"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "拡大",
|
||||
"zoom.out": "縮小",
|
||||
"to": " ",
|
||||
"to.selection": "選択したアイテムに合わせる",
|
||||
"to.fit": "すべて表示",
|
||||
"menu.file": "ファイル",
|
||||
"menu.edit": "編集",
|
||||
"menu.view": "表示",
|
||||
"menu.preferences": "設定",
|
||||
"menu.sign.in": "サインイン",
|
||||
"menu.sign.out": "サインアウト",
|
||||
"sponsored": "支援",
|
||||
"become.a.sponsor": "支援する",
|
||||
"zoom.to.selection": "選択したアイテムに合わせて拡大",
|
||||
"zoom.to.fit": "拡大してすべてを表示",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "ひとつ後ろへ",
|
||||
"back": "最背面へ",
|
||||
"language": "言語"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "확대",
|
||||
"zoom.out": "축소",
|
||||
"to": "to",
|
||||
"to.selection": "선택 요소 맞춤",
|
||||
"to.fit": "전체 맞춤",
|
||||
"menu.file": "파일",
|
||||
"menu.edit": "편집",
|
||||
"menu.view": "보기",
|
||||
"menu.preferences": "설정",
|
||||
"menu.sign.in": "로그인",
|
||||
"menu.sign.out": "로그아웃",
|
||||
"sponsored": "후원",
|
||||
"become.a.sponsor": "후원자 되기",
|
||||
"zoom.to.selection": "선택 요소 맞추기",
|
||||
"zoom.to.fit": "전체 맞추기",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "뒤로",
|
||||
"back": "맨 뒤로",
|
||||
"language": "언어"
|
||||
}
|
||||
}
|
|
@ -10,8 +10,6 @@
|
|||
"zoom.in": "Zoom In",
|
||||
"zoom.out": "Zoom Out",
|
||||
"to": "To",
|
||||
"to.selection": "To Selection",
|
||||
"to.fit": "To Fit",
|
||||
"menu.tools": "Tools",
|
||||
"menu.transform": "Transform",
|
||||
"menu.file": "File",
|
||||
|
@ -20,7 +18,6 @@
|
|||
"menu.preferences": "Preferences",
|
||||
"menu.sign.in": "Sign In",
|
||||
"menu.sign.out": "Sign Out",
|
||||
"sponsored": "Sponsored",
|
||||
"become.a.sponsor": "Become a Sponsor",
|
||||
"zoom.to.content": "Back to content",
|
||||
"zoom.to.selection": "Zoom to Selection",
|
||||
|
@ -105,5 +102,8 @@
|
|||
"transparent": "Transparent",
|
||||
"auto": "Auto",
|
||||
"light": "Light",
|
||||
"dark": "Dark"
|
||||
}
|
||||
"dark": "Dark",
|
||||
"github": "Github",
|
||||
"twitter": "Twitter",
|
||||
"discord": "Discord"
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "जुम इन",
|
||||
"zoom.out": "जुम आउट",
|
||||
"to": "टु",
|
||||
"to.selection": "टु सेलेक्सन",
|
||||
"to.fit": "टु फिट",
|
||||
"menu.file": "फाइल",
|
||||
"menu.edit": "सम्पादन गर्नुहोस्",
|
||||
"menu.view": "भ्यू",
|
||||
"menu.preferences": "प्राथमिकताहरू",
|
||||
"menu.sign.in": "साइन इन गर्नुहोस्",
|
||||
"menu.sign.out": "साइन आउट गर्नुहोस्",
|
||||
"sponsored": "प्रायोजित",
|
||||
"become.a.sponsor": "प्रायोजक बन्नुहोस्",
|
||||
"zoom.to.selection": "जुम टु सेलेक्सन",
|
||||
"zoom.to.fit": "जुम टु फिट",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "पछाडि",
|
||||
"back": "थप पछाडि",
|
||||
"language": "भाषा"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "Zoom inn",
|
||||
"zoom.out": "Zoom ut",
|
||||
"to": "til",
|
||||
"to.selection": "Til valg",
|
||||
"to.fit": "For å passe",
|
||||
"menu.file": "Fil",
|
||||
"menu.edit": "Rediger",
|
||||
"menu.view": "Vis",
|
||||
"menu.preferences": "Preferanser",
|
||||
"menu.sign.in": "Logg inn",
|
||||
"menu.sign.out": "Logg ut",
|
||||
"sponsored": "Sponset",
|
||||
"become.a.sponsor": "Bli en sponsor",
|
||||
"zoom.to.selection": "Zoom til valg",
|
||||
"zoom.to.fit": "Zoom for å passe",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "Bakover",
|
||||
"back": "Bakerst",
|
||||
"language": "Språk"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "Przybliż",
|
||||
"zoom.out": "Oddal",
|
||||
"to": "do",
|
||||
"to.selection": "Do zaznaczenia",
|
||||
"to.fit": "Do wypełnienia",
|
||||
"menu.file": "Plik",
|
||||
"menu.edit": "Edycja",
|
||||
"menu.view": "Widok",
|
||||
"menu.preferences": "Preferencje",
|
||||
"menu.sign.in": "Zaloguj",
|
||||
"menu.sign.out": "Wyloguj",
|
||||
"sponsored": "Sponsorzy",
|
||||
"become.a.sponsor": "Zostań sponsorem",
|
||||
"zoom.to.selection": "Przybliż do zaznaczenia",
|
||||
"zoom.to.fit": "Wypełnij ekran",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "Do tyłu",
|
||||
"back": "Na spód",
|
||||
"language": "Język"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "Aumentar zoom",
|
||||
"zoom.out": "Diminuir zoom",
|
||||
"to": "para",
|
||||
"to.selection": "Para seleção",
|
||||
"to.fit": "Para encaixar",
|
||||
"menu.file": "Arquivo",
|
||||
"menu.edit": "Editar",
|
||||
"menu.view": "Visualizar",
|
||||
"menu.preferences": "Preferências",
|
||||
"menu.sign.in": "Entrar",
|
||||
"menu.sign.out": "Sair",
|
||||
"sponsored": "Patrocinado",
|
||||
"become.a.sponsor": "Torne-se um patrocinador",
|
||||
"zoom.to.selection": "Zoom para a seleção",
|
||||
"zoom.to.fit": "Zoom para ajuste",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "Recuar",
|
||||
"back": "Voltar",
|
||||
"language": "Idioma"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "Увеличить",
|
||||
"zoom.out": "Уменьшить",
|
||||
"to": "к",
|
||||
"to.selection": "К выделению",
|
||||
"to.fit": "По размеру экрана",
|
||||
"menu.file": "Файл",
|
||||
"menu.edit": "Редактирование",
|
||||
"menu.view": "Вид",
|
||||
"menu.preferences": "Настройки",
|
||||
"menu.sign.in": "Войти",
|
||||
"menu.sign.out": "Выйти",
|
||||
"sponsored": "Спонсировано",
|
||||
"become.a.sponsor": "Стать спонсором",
|
||||
"zoom.to.selection": "Приблизить к выделению",
|
||||
"zoom.to.fit": "Увеличить по размеру экрана",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "На задний план",
|
||||
"back": "Назад",
|
||||
"language": "Язык"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "Yakınlaştır",
|
||||
"zoom.out": "Uzaklaştır",
|
||||
"to": "",
|
||||
"to.selection": "Seçime Göre",
|
||||
"to.fit": "Sığdırmaya Göre",
|
||||
"menu.file": "Dosya",
|
||||
"menu.edit": "Düzenle",
|
||||
"menu.view": "Görüntü",
|
||||
"menu.preferences": "Tercihler",
|
||||
"menu.sign.in": "Giriş Yap",
|
||||
"menu.sign.out": "Oturumu Kapat",
|
||||
"sponsored": "Sponsorlu",
|
||||
"become.a.sponsor": "Sponsor Ol",
|
||||
"zoom.to.selection": "Seçime Yakınlaştır",
|
||||
"zoom.to.fit": "Sığdırmak için Yakınlaştır",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "En Arkaya",
|
||||
"back": "Arkaya",
|
||||
"language": "Dil"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "Збільшити",
|
||||
"zoom.out": "Зменшити",
|
||||
"to": "до",
|
||||
"to.selection": "До виділення",
|
||||
"to.fit": "За розміром екрану",
|
||||
"menu.file": "Файл",
|
||||
"menu.edit": "Редагування",
|
||||
"menu.view": "Вигляд",
|
||||
"menu.preferences": "Налаштування",
|
||||
"menu.sign.in": "Увійти",
|
||||
"menu.sign.out": "Вийти",
|
||||
"sponsored": "Спонсовано",
|
||||
"become.a.sponsor": "Стати спонсором",
|
||||
"zoom.to.selection": "Наблизити до виділення",
|
||||
"zoom.to.fit": "Збільшити за розміром екрану",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "На задній план",
|
||||
"back": "Назад",
|
||||
"language": "Мова"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "放大",
|
||||
"zoom.out": "缩小",
|
||||
"to": "缩放至",
|
||||
"to.selection": "缩放选中",
|
||||
"to.fit": "自适应缩放",
|
||||
"menu.file": "文件",
|
||||
"menu.edit": "编辑",
|
||||
"menu.view": "视图",
|
||||
"menu.preferences": "偏好",
|
||||
"menu.sign.in": "登录",
|
||||
"menu.sign.out": "登出",
|
||||
"sponsored": "已赞助",
|
||||
"become.a.sponsor": "成为赞助者",
|
||||
"zoom.to.selection": "缩放选中",
|
||||
"zoom.to.fit": "自适应缩放",
|
||||
|
@ -87,4 +84,4 @@
|
|||
"backward": "下一层",
|
||||
"back": "置底",
|
||||
"language": "语言"
|
||||
}
|
||||
}
|
|
@ -10,15 +10,12 @@
|
|||
"zoom.in": "放大",
|
||||
"zoom.out": "縮小",
|
||||
"to": "至",
|
||||
"to.selection": "至選取範圍",
|
||||
"to.fit": "至適當大小",
|
||||
"menu.file": "檔案",
|
||||
"menu.edit": "編輯",
|
||||
"menu.view": "檢視",
|
||||
"menu.preferences": "選項",
|
||||
"menu.sign.in": "登入",
|
||||
"menu.sign.out": "登出",
|
||||
"sponsored": "贊助",
|
||||
"become.a.sponsor": "成為贊助者",
|
||||
"zoom.to.selection": "縮放至選取範圍",
|
||||
"zoom.to.fit": "縮放至適當大小",
|
||||
|
@ -95,4 +92,4 @@
|
|||
"right": "右側",
|
||||
"top": "上方",
|
||||
"page": "頁面"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue