remove safari special-casing for paste (#1470)

Previously, we had a bunch of special-casing around paste to support
safari quirks on desktop and ios.

Since upgrading radix-ui and useGesture, these are no longer needed and
were actually causing issues. This diff removes the special casing for
paste and makes it a normal action that get triggered the same way as
any other.

### Change Type
- [x] `patch` — Bug Fix

### Test Plan

1. Copy text outside of tldraw, paste in tldraw with the context menu,
edit menu, and keyboard shortcut
2. Repeat for images outside of tldraw
3. Repeat for shapes inside of tldraw

### Release Notes

[fixes a regression introduced during this release]
This commit is contained in:
alex 2023-05-26 09:24:21 +01:00 committed by GitHub
parent 042edeb4b4
commit f8b6ee6ab9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 30 additions and 101 deletions

View file

@ -1025,10 +1025,10 @@ export function useLanguages(): {
export function useLocalStorageState<T = any>(key: string, defaultValue: T): readonly [T, (setter: ((value: T) => T) | T) => void]; export function useLocalStorageState<T = any>(key: string, defaultValue: T): readonly [T, (setter: ((value: T) => T) | T) => void];
// @public (undocumented) // @public (undocumented)
export function useMenuClipboardEvents(source: TLUiEventSource): { export function useMenuClipboardEvents(): {
copy: () => void; copy: (source: TLUiEventSource) => void;
cut: () => void; cut: (source: TLUiEventSource) => void;
paste: (data: ClipboardItem[] | DataTransfer, point?: VecLike) => Promise<void>; paste: (data: ClipboardItem[] | DataTransfer, source: TLUiEventSource, point?: VecLike) => Promise<void>;
}; };
// @public (undocumented) // @public (undocumented)

View file

@ -5,7 +5,6 @@ import * as React from 'react'
import { useValue } from 'signia-react' import { useValue } from 'signia-react'
import { MenuChild } from '../hooks/menuHelpers' import { MenuChild } from '../hooks/menuHelpers'
import { useBreakpoint } from '../hooks/useBreakpoint' import { useBreakpoint } from '../hooks/useBreakpoint'
import { useMenuClipboardEvents } from '../hooks/useClipboardEvents'
import { useContextMenuSchema } from '../hooks/useContextMenuSchema' import { useContextMenuSchema } from '../hooks/useContextMenuSchema'
import { useMenuIsOpen } from '../hooks/useMenuIsOpen' import { useMenuIsOpen } from '../hooks/useMenuIsOpen'
import { useReadonly } from '../hooks/useReadonly' import { useReadonly } from '../hooks/useReadonly'
@ -61,7 +60,6 @@ function ContextMenuContent() {
const [_, handleSubOpenChange] = useMenuIsOpen('context menu sub') const [_, handleSubOpenChange] = useMenuIsOpen('context menu sub')
const isReadonly = useReadonly() const isReadonly = useReadonly()
const { paste } = useMenuClipboardEvents('context-menu')
const breakpoint = useBreakpoint() const breakpoint = useBreakpoint()
const container = useContainer() const container = useContainer()
@ -73,42 +71,6 @@ function ContextMenuContent() {
switch (item.type) { switch (item.type) {
case 'custom': { case 'custom': {
switch (item.id) { switch (item.id) {
case 'MENU_PASTE': {
return (
<_ContextMenu.Item key={item.id}>
<Button
className="tlui-menu__button"
data-wd={`menu-item.${item.id}`}
kbd="$v"
label="action.paste"
disabled={item.disabled}
onClick={() => {
if (!app.isSafari || (app.isSafari && app.isIos)) {
navigator.clipboard.read().then((clipboardItems) => {
paste(clipboardItems, app.inputs.currentPagePoint)
})
}
}}
onMouseDown={() => {
if (app.isSafari && !app.isIos) {
// NOTE: This must be a onMouseDown for Safari/desktop, onClick doesn't work at the time of writing... 😒
navigator.clipboard.read().then((clipboardItems) => {
paste(clipboardItems, app.inputs.currentPagePoint)
})
}
}}
// onPointerUp={() => {
// if (app.isSafari && app.isIos) {
// // NOTE: This must be a onPointerUp for Safari/mobile, onClick doesn't work at the time of writing... 😒
// navigator.clipboard.read().then((clipboardItems) => {
// paste(clipboardItems, app.inputs.currentPagePoint)
// })
// }
// }}
/>
</_ContextMenu.Item>
)
}
case 'MOVE_TO_PAGE_MENU': { case 'MOVE_TO_PAGE_MENU': {
return <MoveToPageMenu key={item.id} /> return <MoveToPageMenu key={item.id} />
} }

View file

@ -1,8 +1,7 @@
import { App, preventDefault, useApp } from '@tldraw/editor' import { App, useApp } from '@tldraw/editor'
import * as React from 'react' import * as React from 'react'
import { MenuChild } from '../hooks/menuHelpers' import { MenuChild } from '../hooks/menuHelpers'
import { useBreakpoint } from '../hooks/useBreakpoint' import { useBreakpoint } from '../hooks/useBreakpoint'
import { useMenuClipboardEvents } from '../hooks/useClipboardEvents'
import { useMenuSchema } from '../hooks/useMenuSchema' import { useMenuSchema } from '../hooks/useMenuSchema'
import { useReadonly } from '../hooks/useReadonly' import { useReadonly } from '../hooks/useReadonly'
import { useTranslation } from '../hooks/useTranslation/useTranslation' import { useTranslation } from '../hooks/useTranslation/useTranslation'
@ -37,7 +36,6 @@ function MenuContent() {
const menuSchema = useMenuSchema() const menuSchema = useMenuSchema()
const breakpoint = useBreakpoint() const breakpoint = useBreakpoint()
const isReadonly = useReadonly() const isReadonly = useReadonly()
const { paste } = useMenuClipboardEvents('menu')
function getMenuItem(app: App, item: MenuChild, parent: MenuChild | null, depth: number) { function getMenuItem(app: App, item: MenuChild, parent: MenuChild | null, depth: number) {
switch (item.type) { switch (item.type) {
@ -48,35 +46,6 @@ function MenuContent() {
return <LanguageMenu key="item" /> return <LanguageMenu key="item" />
} }
if (item.id === 'MENU_PASTE') {
return (
<M.Item
key={item.id}
data-wd={`menu-item.${item.id}`}
kbd="$v"
label="action.paste"
disabled={item.disabled}
onMouseDown={() => {
if (app.isSafari && navigator.clipboard?.read) {
// NOTE: This must be a onMouseDown for Safari/desktop, onClick doesn't work at the time of writing...
navigator.clipboard.read().then((clipboardItems) => {
paste(clipboardItems)
})
}
}}
onClick={() => {
if (app.isSafari) {
// noop
} else if (navigator.clipboard?.read) {
navigator.clipboard.read().then((clipboardItems) => {
paste(clipboardItems)
})
}
}}
onPointerUp={preventDefault}
/>
)
}
return null return null
} }
case 'group': { case 'group': {

View file

@ -64,17 +64,12 @@ function makeActions(actions: ActionItem[]) {
export function ActionsProvider({ overrides, children }: ActionsProviderProps) { export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
const app = useApp() const app = useApp()
// const saveFile = useSaveFile()
// const saveFileAs = useSaveFileAs()
// const newFile = useNewFile()
// const openFile = useOpenFile()
const { addDialog, clearDialogs } = useDialogs() const { addDialog, clearDialogs } = useDialogs()
const { clearToasts } = useToasts() const { clearToasts } = useToasts()
const insertMedia = useInsertMedia() const insertMedia = useInsertMedia()
const printSelectionOrPages = usePrint() const printSelectionOrPages = usePrint()
const { cut, copy } = useMenuClipboardEvents('unknown') const { cut, copy, paste } = useMenuClipboardEvents()
const copyAs = useCopyAs() const copyAs = useCopyAs()
const exportAs = useExportAs() const exportAs = useExportAs()
@ -631,9 +626,8 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
kbd: '$x', kbd: '$x',
readonlyOk: false, readonlyOk: false,
onSelect(source) { onSelect(source) {
trackEvent('cut', { source })
app.mark('cut') app.mark('cut')
cut() cut(source)
}, },
}, },
{ {
@ -642,8 +636,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
kbd: '$c', kbd: '$c',
readonlyOk: true, readonlyOk: true,
onSelect(source) { onSelect(source) {
trackEvent('copy', { source }) copy(source)
copy()
}, },
}, },
{ {
@ -651,9 +644,14 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
label: 'action.paste', label: 'action.paste',
kbd: '$v', kbd: '$v',
readonlyOk: false, readonlyOk: false,
onSelect() { onSelect(source) {
// must be inlined with a custom menu item navigator.clipboard?.read().then((clipboardItems) => {
// the kbd listed here should have no effect paste(
clipboardItems,
source,
source === 'context-menu' ? app.inputs.currentPagePoint : undefined
)
})
}, },
}, },
{ {
@ -931,6 +929,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
copyAs, copyAs,
cut, cut,
copy, copy,
paste,
clearDialogs, clearDialogs,
clearToasts, clearToasts,
printSelectionOrPages, printSelectionOrPages,

View file

@ -539,33 +539,37 @@ const handleNativeOrMenuCopy = (app: App) => {
} }
/** @public */ /** @public */
export function useMenuClipboardEvents(source: TLUiEventSource) { export function useMenuClipboardEvents() {
const app = useApp() const app = useApp()
const trackEvent = useEvents() const trackEvent = useEvents()
const copy = useCallback( const copy = useCallback(
function onCopy() { function onCopy(source: TLUiEventSource) {
if (app.selectedIds.length === 0) return if (app.selectedIds.length === 0) return
handleNativeOrMenuCopy(app) handleNativeOrMenuCopy(app)
trackEvent('copy', { source }) trackEvent('copy', { source })
}, },
[app, trackEvent, source] [app, trackEvent]
) )
const cut = useCallback( const cut = useCallback(
function onCut() { function onCut(source: TLUiEventSource) {
if (app.selectedIds.length === 0) return if (app.selectedIds.length === 0) return
handleNativeOrMenuCopy(app) handleNativeOrMenuCopy(app)
app.deleteShapes() app.deleteShapes()
trackEvent('cut', { source }) trackEvent('cut', { source })
}, },
[app, trackEvent, source] [app, trackEvent]
) )
const paste = useCallback( const paste = useCallback(
async function onPaste(data: DataTransfer | ClipboardItem[], point?: VecLike) { async function onPaste(
data: DataTransfer | ClipboardItem[],
source: TLUiEventSource,
point?: VecLike
) {
// If we're editing a shape, or we are focusing an editable input, then // If we're editing a shape, or we are focusing an editable input, then
// we would want the user's paste interaction to go to that element or // we would want the user's paste interaction to go to that element or
// input instead; e.g. when pasting text into a text shape's content // input instead; e.g. when pasting text into a text shape's content
@ -577,7 +581,7 @@ export function useMenuClipboardEvents(source: TLUiEventSource) {
} else { } else {
// Read it first and then recurse, kind of weird // Read it first and then recurse, kind of weird
navigator.clipboard.read().then((clipboardItems) => { navigator.clipboard.read().then((clipboardItems) => {
paste(clipboardItems, app.inputs.currentPagePoint) paste(clipboardItems, source, point)
}) })
} }
}, },

View file

@ -163,7 +163,7 @@ export const ContextMenuSchemaProvider = track(function ContextMenuSchemaProvide
'clipboard-group', 'clipboard-group',
oneSelected && menuItem(actions['cut']), oneSelected && menuItem(actions['cut']),
oneSelected && menuItem(actions['copy']), oneSelected && menuItem(actions['copy']),
showMenuPaste && menuCustom('MENU_PASTE', { readonlyOk: false }) showMenuPaste && menuItem(actions['paste'])
), ),
atLeastOneShapeOnPage && atLeastOneShapeOnPage &&
menuGroup( menuGroup(

View file

@ -97,12 +97,7 @@ export function MenuSchemaProvider({ overrides, children }: MenuSchemaProviderPr
'clipboard-actions', 'clipboard-actions',
menuItem(actions['cut'], { disabled: noneSelected }), menuItem(actions['cut'], { disabled: noneSelected }),
menuItem(actions['copy'], { disabled: noneSelected }), menuItem(actions['copy'], { disabled: noneSelected }),
{ menuItem(actions['paste'], { disabled: !showMenuPaste })
id: 'MENU_PASTE',
type: 'custom',
disabled: !showMenuPaste,
readonlyOk: false,
}
), ),
menuGroup( menuGroup(
'conversions', 'conversions',