share: make share/fork/copy actions clearer (#3846)

Right now when you hit Share/Fork on production it can take a good 5
seconds for something to happen.
In the meantime, it can feel like nothing happened when you clicked the
button. Maybe you click it again to see if that'll fix it, which doesn't
do anything. Same thing for the Copy action, sometimes we don't have an
icon to subtly show that it's been copied.

This adds some toasts and disables the Share menu while a project is
being created.

Also, has two drive-by fixes:
- the getShareUrl logic is old and needed to be superseded by the new
stuff
- the icon fix for clipboard-copied.svg from the readonly omnibus PR
(https://github.com/tldraw/tldraw/pull/3192) got overridden in a
different PR (https://github.com/tldraw/tldraw/pull/3627) - this
restores the fix

<img width="304" alt="Screenshot 2024-05-30 at 11 38 39"
src="https://github.com/tldraw/tldraw/assets/469604/f9a3b7c7-f9ea-41f0-ad00-7fc5d71da93f">
<img width="257" alt="Screenshot 2024-05-30 at 11 38 14"
src="https://github.com/tldraw/tldraw/assets/469604/c0a2d762-64c3-44da-b61e-c237133dd8cd">


### Change Type

<!--  Please select a 'Scope' label ️ -->

- [x] `sdk` — Changes the tldraw SDK
- [ ] `dotcom` — Changes the tldraw.com web app
- [ ] `docs` — Changes to the documentation, examples, or templates.
- [ ] `vs code` — Changes to the vscode plugin
- [ ] `internal` — Does not affect user-facing stuff

<!--  Please select a 'Type' label ️ -->

- [ ] `bugfix` — Bug fix
- [ ] `feature` — New feature
- [x] `improvement` — Improving existing features
- [ ] `chore` — Updating dependencies, other boring stuff
- [ ] `galaxy brain` — Architectural changes
- [ ] `tests` — Changes to any test code
- [ ] `tools` — Changes to infrastructure, CI, internal scripts,
debugging tools, etc.
- [ ] `dunno` — I don't know

### Release Notes

- Share menu: make sharing/fork/copy actions clearer
This commit is contained in:
Mime Čuvalo 2024-06-03 09:42:08 +01:00 committed by GitHub
parent 633a4e700d
commit e29137f467
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 60 additions and 33 deletions

View file

@ -26,6 +26,7 @@ import {
useActions,
useBreakpoint,
useEditor,
useToasts,
useTranslation,
} from 'tldraw'
import { FORK_PROJECT_ACTION } from '../../utils/sharing'
@ -66,6 +67,7 @@ export const DocumentNameInner = track(function DocumentNameInner() {
const saveFileAction = actions[SAVE_FILE_COPY_ACTION]
const editor = useEditor()
const msg = useTranslation()
const toasts = useToasts()
return (
<div className="tlui-document-name__inner">
@ -93,12 +95,16 @@ export const DocumentNameInner = track(function DocumentNameInner() {
<TldrawUiDropdownMenuItem>
<TldrawUiButton
type="menu"
onClick={() => {
const shareLink = getShareUrl(
onClick={async () => {
const shareLink = await getShareUrl(
window.location.href,
editor.getInstanceState().isReadonly
)
navigator.clipboard.writeText(shareLink)
shareLink && navigator.clipboard.writeText(shareLink)
toasts.addToast({
title: msg('share-menu.copied'),
severity: 'success',
})
}}
>
<span className={'tlui-button__label' as any}>Copy link</span>

View file

@ -10,10 +10,10 @@ import {
TldrawUiMenuContextProvider,
TldrawUiMenuGroup,
TldrawUiMenuItem,
lns,
unwrapLabel,
useActions,
useContainer,
useToasts,
useTranslation,
} from 'tldraw'
import { useShareMenuIsOpen } from '../hooks/useShareMenuOpen'
@ -21,6 +21,8 @@ import { createQRCodeImageDataString } from '../utils/qrcode'
import { SHARE_PROJECT_ACTION, SHARE_SNAPSHOT_ACTION } from '../utils/sharing'
import { ShareButton } from './ShareButton'
const COPY_LINK_TIMEOUT = 1000
const SHARE_CURRENT_STATE = {
OFFLINE: 'offline',
SHARED_READ_WRITE: 'shared-read-write',
@ -77,6 +79,14 @@ function getFreshShareState(previousReadonlyUrl?: string): ShareState {
}
}
export async function getShareUrl(url: string, readonly: boolean) {
if (!readonly) {
return url
}
return await getReadonlyUrl()
}
async function getReadonlyUrl() {
const pathname = window.location.pathname
const isReadOnly = isSharedReadonlyUrl(pathname)
@ -119,6 +129,7 @@ export const ShareMenu = React.memo(function ShareMenu() {
const [didCopy, setDidCopy] = useState(false)
const [didCopyReadonlyLink, setDidCopyReadonlyLink] = useState(false)
const [didCopySnapshotLink, setDidCopySnapshotLink] = useState(false)
const toasts = useToasts()
useEffect(() => {
if (shareState.state === SHARE_CURRENT_STATE.OFFLINE) {
@ -127,7 +138,7 @@ export const ShareMenu = React.memo(function ShareMenu() {
let cancelled = false
const shareUrl = getShareUrl(window.location.href, false)
const shareUrl = window.location.href
if (!shareState.qrCodeDataUrl && shareState.state === SHARE_CURRENT_STATE.SHARED_READ_WRITE) {
// Fetch the QR code data URL
createQRCodeImageDataString(shareUrl).then((dataUrl) => {
@ -197,8 +208,12 @@ export const ShareMenu = React.memo(function ShareMenu() {
onClick={() => {
if (!currentShareLinkUrl) return
setDidCopy(true)
setTimeout(() => setDidCopy(false), 1000)
setTimeout(() => setDidCopy(false), COPY_LINK_TIMEOUT)
navigator.clipboard.writeText(currentShareLinkUrl)
toasts.addToast({
title: msg('share-menu.copied'),
severity: 'success',
})
}}
/>
@ -212,8 +227,12 @@ export const ShareMenu = React.memo(function ShareMenu() {
onSelect={() => {
if (!shareState.url) return
setDidCopy(true)
setTimeout(() => setDidCopy(false), 750)
setTimeout(() => setDidCopy(false), COPY_LINK_TIMEOUT)
navigator.clipboard.writeText(shareState.url)
toasts.addToast({
title: msg('share-menu.copied'),
severity: 'success',
})
}}
/>
)}
@ -225,8 +244,12 @@ export const ShareMenu = React.memo(function ShareMenu() {
onSelect={() => {
if (!shareState.readonlyUrl) return
setDidCopyReadonlyLink(true)
setTimeout(() => setDidCopyReadonlyLink(false), 750)
setTimeout(() => setDidCopyReadonlyLink(false), COPY_LINK_TIMEOUT)
navigator.clipboard.writeText(shareState.readonlyUrl)
toasts.addToast({
title: msg('share-menu.copied'),
severity: 'success',
})
}}
/>
<p className="tlui-menu__group tlui-share-zone__details">
@ -243,7 +266,7 @@ export const ShareMenu = React.memo(function ShareMenu() {
await shareSnapshot.onSelect('share-menu')
setIsUploadingSnapshot(false)
setDidCopySnapshotLink(true)
setTimeout(() => setDidCopySnapshotLink(false), 1000)
setTimeout(() => setDidCopySnapshotLink(false), COPY_LINK_TIMEOUT)
}}
spinner={isUploadingSnapshot}
/>
@ -260,6 +283,7 @@ export const ShareMenu = React.memo(function ShareMenu() {
readonlyOk
label="share-menu.share-project"
icon="share-1"
disabled={isUploading}
onSelect={async () => {
if (isUploading) return
setIsUploading(true)
@ -289,7 +313,7 @@ export const ShareMenu = React.memo(function ShareMenu() {
await shareSnapshot.onSelect('share-menu')
setIsUploadingSnapshot(false)
setDidCopySnapshotLink(true)
setTimeout(() => setDidCopySnapshotLink(false), 1000)
setTimeout(() => setDidCopySnapshotLink(false), COPY_LINK_TIMEOUT)
}}
spinner={isUploadingSnapshot}
/>
@ -305,23 +329,3 @@ export const ShareMenu = React.memo(function ShareMenu() {
</Popover.Root>
)
})
export function getShareUrl(url: string, readonly: boolean) {
if (!readonly) {
return url
}
const segs = url.split('/')
// Change the r for a v
segs[segs.length - 2] = 'v'
// A url might be something like https://www.tldraw.com/r/123?pageId=myPageId
// we want it instead to be https://www.tldraw.com/v/312?pageId=myPageId, ie
// the scrambled room id but not scrambled query params
const [roomId, params] = segs[segs.length - 1].split('?')
segs[segs.length - 1] = lns(roomId)
if (params) segs[segs.length - 1] += '?' + params
return segs.join('/')
}

View file

@ -104,7 +104,7 @@ export function useSharing(): TLUiOverrides {
return useMemo(
(): TLUiOverrides => ({
actions(editor, actions, { addToast, msg, addDialog }) {
actions(editor, actions, { addToast, clearToasts, msg, addDialog }) {
actions[LEAVE_SHARED_PROJECT_ACTION] = {
id: LEAVE_SHARED_PROJECT_ACTION,
label: 'action.leave-shared-project',
@ -124,6 +124,12 @@ export function useSharing(): TLUiOverrides {
readonlyOk: true,
onSelect: async (source) => {
try {
addToast({
title: msg('share-menu.creating-project'),
severity: 'info',
keepOpen: true,
})
handleUiEvent('share-project', { source })
const data = await getRoomData(editor, addToast, msg, uploadFileToAsset)
if (!data) return
@ -147,6 +153,7 @@ export function useSharing(): TLUiOverrides {
const pathname = decodeURIComponent(
`/${ROOM_PREFIX}/${response.slug}?${new URLSearchParams(query ?? {}).toString()}`
)
clearToasts()
if (runningInIFrame) {
window.open(`${origin}${pathname}`)
} else {
@ -187,6 +194,10 @@ export function useSharing(): TLUiOverrides {
if (link === '') return
navigator.clipboard.writeText(await link.text())
}
addToast({
title: msg('share-menu.copied'),
severity: 'success',
})
},
}
actions[FORK_PROJECT_ACTION] = {

View file

@ -1,4 +1,4 @@
<svg width="30" height="31" viewBox="0 0 30 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 2V4H18V2H8ZM6 1.5C6 0.671573 6.67157 0 7.5 0H18.5C19.3284 0 20 0.671572 20 1.5V2H21C22.6569 2 24 3.34315 24 5V14H22V5C22 4.44772 21.5523 4 21 4H20V4.5C20 5.32843 19.3284 6 18.5 6H7.5C6.67157 6 6 5.32843 6 4.5V4H5C4.44771 4 4 4.44772 4 5V25C4 25.5523 4.44772 26 5 26H12V28H5C3.34315 28 2 26.6569 2 25V5C2 3.34314 3.34315 2 5 2H6V1.5Z" fill="black"/>
<path d="M27.5197 17.173C28.0099 17.4936 28.1475 18.1509 27.827 18.6411L20.6149 29.6713C20.445 29.9313 20.1696 30.1037 19.8615 30.143C19.5534 30.1823 19.2436 30.0846 19.0138 29.8757L14.3472 25.6333C13.9137 25.2393 13.8818 24.5685 14.2758 24.1351C14.6698 23.7017 15.3406 23.6697 15.774 24.0638L19.5203 27.4694L26.0516 17.4803C26.3721 16.9901 27.0294 16.8525 27.5197 17.173Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 893 B

After

Width:  |  Height:  |  Size: 893 B

View file

@ -266,6 +266,8 @@
"share-menu.copy-readonly-link-note": "Anyone with the link will be able to access this project.",
"share-menu.project-too-large": "Sorry, this project can't be shared because it's too large. We're working on it!",
"share-menu.upload-failed": "Sorry, we couldn't upload your project at the moment. Please try again or let us know if the problem persists.",
"share-menu.creating-project": "Creating the new project…",
"share-menu.copied": "Copied link!",
"status.offline": "Offline",
"status.online": "Online",
"people-menu.title": "People",

File diff suppressed because one or more lines are too long

View file

@ -270,6 +270,8 @@ export type TLUiTranslationKey =
| 'share-menu.copy-readonly-link-note'
| 'share-menu.project-too-large'
| 'share-menu.upload-failed'
| 'share-menu.creating-project'
| 'share-menu.copied'
| 'status.offline'
| 'status.online'
| 'people-menu.title'

View file

@ -272,6 +272,8 @@ export const DEFAULT_TRANSLATION = {
"Sorry, this project can't be shared because it's too large. We're working on it!",
'share-menu.upload-failed':
"Sorry, we couldn't upload your project at the moment. Please try again or let us know if the problem persists.",
'share-menu.creating-project': 'Creating the new project…',
'share-menu.copied': 'Copied link!',
'status.offline': 'Offline',
'status.online': 'Online',
'people-menu.title': 'People',