Add "paste at cursor" option, which toggles how cmd + v
and cmd + shift + v
work (#4088)
Add an option to make paste at cursor the default. Not sure if we also want to expose this on tldraw.com? For now I did, but happy to remove if we'd want to keep the preferences simple. We could also add this to the `TldrawOptions`, but it felt like some apps might actually allow this customization on a per user level. Solves https://github.com/tldraw/tldraw/issues/4066 ### Change type - [ ] `bugfix` - [ ] `improvement` - [x] `feature` - [ ] `api` - [ ] `other` ### Test plan 1. Copy / pasting should still work as it works now: `⌘ + v` pastes on top of the shape, `⌘ + ⇧ + v` pastes at cursor. 2. There's now a new option under Preferences to paste at cursor. This just swaps the logic between the two shortcuts: `⌘ + v` then pastes at cursor and `⌘ + ⇧ + v` pastes on top of the shape. ### Release notes - Allow users and sdk users to make pasting at the cursor a default instead of only being available with `⌘ + ⇧ + v`.
This commit is contained in:
parent
7d5a6cbe3b
commit
a85c215ffc
12 changed files with 61 additions and 3 deletions
|
@ -83,6 +83,8 @@
|
|||
"action.toggle-auto-size": "Toggle auto size",
|
||||
"action.toggle-dark-mode.menu": "Dark mode",
|
||||
"action.toggle-dark-mode": "Toggle dark mode",
|
||||
"action.toggle-paste-at-cursor.menu": "Paste at cursor",
|
||||
"action.toggle-paste-at-cursor": "Toggle paste at cursor",
|
||||
"action.toggle-wrap-mode.menu": "Select on wrap",
|
||||
"action.toggle-wrap-mode": "Toggle Select on wrap",
|
||||
"action.toggle-reduce-motion.menu": "Reduce motion",
|
||||
|
|
|
@ -724,6 +724,7 @@ export const defaultUserPreferences: Readonly<{
|
|||
isWrapMode: false;
|
||||
locale: "ar" | "ca" | "cs" | "da" | "de" | "en" | "es" | "fa" | "fi" | "fr" | "gl" | "he" | "hi-in" | "hr" | "hu" | "id" | "it" | "ja" | "ko-kr" | "ku" | "my" | "ne" | "no" | "pl" | "pt-br" | "pt-pt" | "ro" | "ru" | "sl" | "sv" | "te" | "th" | "tr" | "uk" | "vi" | "zh-cn" | "zh-tw";
|
||||
name: "New User";
|
||||
pasteAtCursor: false;
|
||||
}>;
|
||||
|
||||
// @public
|
||||
|
@ -3371,6 +3372,8 @@ export interface TLUserPreferences {
|
|||
locale?: null | string;
|
||||
// (undocumented)
|
||||
name?: null | string;
|
||||
// (undocumented)
|
||||
pasteAtCursor?: boolean | null;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -3480,6 +3483,8 @@ export class UserPreferencesManager {
|
|||
// (undocumented)
|
||||
getName(): string;
|
||||
// (undocumented)
|
||||
getPasteAtCursor(): boolean;
|
||||
// (undocumented)
|
||||
getUserPreferences(): {
|
||||
animationSpeed: number;
|
||||
color: string;
|
||||
|
|
|
@ -22,6 +22,7 @@ export interface TLUserPreferences {
|
|||
isSnapMode?: boolean | null
|
||||
isWrapMode?: boolean | null
|
||||
isDynamicSizeMode?: boolean | null
|
||||
pasteAtCursor?: boolean | null
|
||||
}
|
||||
|
||||
interface UserDataSnapshot {
|
||||
|
@ -46,6 +47,7 @@ const userTypeValidator: T.Validator<TLUserPreferences> = T.object<TLUserPrefere
|
|||
isSnapMode: T.boolean.nullable().optional(),
|
||||
isWrapMode: T.boolean.nullable().optional(),
|
||||
isDynamicSizeMode: T.boolean.nullable().optional(),
|
||||
pasteAtCursor: T.boolean.nullable().optional(),
|
||||
})
|
||||
|
||||
const Versions = {
|
||||
|
@ -56,6 +58,7 @@ const Versions = {
|
|||
AddExcalidrawSelectMode: 5,
|
||||
AddDynamicSizeMode: 6,
|
||||
AllowSystemColorScheme: 7,
|
||||
AddPasteAtCursor: 8,
|
||||
} as const
|
||||
|
||||
const CURRENT_VERSION = Math.max(...Object.values(Versions))
|
||||
|
@ -88,6 +91,9 @@ function migrateSnapshot(data: { version: number; user: any }) {
|
|||
if (data.version < Versions.AddDynamicSizeMode) {
|
||||
data.user.isDynamicSizeMode = false
|
||||
}
|
||||
if (data.version < Versions.AddPasteAtCursor) {
|
||||
data.user.pasteAtCursor = false
|
||||
}
|
||||
|
||||
// finally
|
||||
data.version = CURRENT_VERSION
|
||||
|
@ -131,6 +137,7 @@ export const defaultUserPreferences = Object.freeze({
|
|||
isSnapMode: false,
|
||||
isWrapMode: false,
|
||||
isDynamicSizeMode: false,
|
||||
pasteAtCursor: false,
|
||||
}) satisfies Readonly<Omit<TLUserPreferences, 'id'>>
|
||||
|
||||
/** @public */
|
||||
|
|
|
@ -97,4 +97,8 @@ export class UserPreferencesManager {
|
|||
this.user.userPreferences.get().isDynamicSizeMode ?? defaultUserPreferences.isDynamicSizeMode
|
||||
)
|
||||
}
|
||||
|
||||
@computed getPasteAtCursor() {
|
||||
return this.user.userPreferences.get().pasteAtCursor ?? defaultUserPreferences.pasteAtCursor
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -21,6 +21,7 @@ import {
|
|||
ToggleFocusModeItem,
|
||||
ToggleGridItem,
|
||||
ToggleLockMenuItem,
|
||||
TogglePasteAtCursorItem,
|
||||
ToggleReduceMotionItem,
|
||||
ToggleSnapModeItem,
|
||||
ToggleToolLockItem,
|
||||
|
@ -174,6 +175,7 @@ export function PreferencesGroup() {
|
|||
<ToggleEdgeScrollingItem />
|
||||
<ToggleReduceMotionItem />
|
||||
<ToggleDynamicSizeModeItem />
|
||||
<TogglePasteAtCursorItem />
|
||||
<ToggleDebugModeItem />
|
||||
</TldrawUiMenuGroup>
|
||||
<TldrawUiMenuGroup id="color-scheme">
|
||||
|
|
|
@ -626,6 +626,14 @@ export function ToggleDynamicSizeModeItem() {
|
|||
)
|
||||
}
|
||||
|
||||
/** @public @react */
|
||||
export function TogglePasteAtCursorItem() {
|
||||
const actions = useActions()
|
||||
const editor = useEditor()
|
||||
const pasteAtCursor = useValue('paste at cursor', () => editor.user.getPasteAtCursor(), [editor])
|
||||
return <TldrawUiMenuCheckboxItem {...actions['toggle-paste-at-cursor']} checked={pasteAtCursor} />
|
||||
}
|
||||
|
||||
/* ---------------------- Print --------------------- */
|
||||
/** @public @react */
|
||||
export function PrintItem() {
|
||||
|
|
|
@ -1149,6 +1149,21 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
},
|
||||
checkbox: true,
|
||||
},
|
||||
{
|
||||
id: 'toggle-paste-at-cursor',
|
||||
label: {
|
||||
default: 'action.toggle-paste-at-cursor',
|
||||
menu: 'action.toggle-paste-at-cursor.menu',
|
||||
},
|
||||
readonlyOk: false,
|
||||
onSelect(source) {
|
||||
trackEvent('toggle-paste-at-cursor', { source })
|
||||
editor.user.updateUserPreferences({
|
||||
pasteAtCursor: !editor.user.getPasteAtCursor(),
|
||||
})
|
||||
},
|
||||
checkbox: true,
|
||||
},
|
||||
{
|
||||
id: 'toggle-reduce-motion',
|
||||
label: {
|
||||
|
|
|
@ -88,6 +88,7 @@ export interface TLUiEventMap {
|
|||
'toggle-focus-mode': null
|
||||
'toggle-debug-mode': null
|
||||
'toggle-dynamic-size-mode': null
|
||||
'toggle-paste-at-cursor': null
|
||||
'toggle-lock': null
|
||||
'toggle-reduce-motion': null
|
||||
'toggle-edge-scrolling': null
|
||||
|
|
|
@ -671,12 +671,20 @@ export function useNativeClipboardEvents() {
|
|||
|
||||
// First try to use the clipboard data on the event
|
||||
if (e.clipboardData && !editor.inputs.shiftKey) {
|
||||
handlePasteFromEventClipboardData(editor, e.clipboardData)
|
||||
if (editor.user.getPasteAtCursor()) {
|
||||
handlePasteFromEventClipboardData(editor, e.clipboardData, editor.inputs.currentPagePoint)
|
||||
} else {
|
||||
handlePasteFromEventClipboardData(editor, e.clipboardData)
|
||||
}
|
||||
} else {
|
||||
// Or else use the clipboard API
|
||||
navigator.clipboard.read().then((clipboardItems) => {
|
||||
if (Array.isArray(clipboardItems) && clipboardItems[0] instanceof ClipboardItem) {
|
||||
handlePasteFromClipboardApi(editor, clipboardItems, editor.inputs.currentPagePoint)
|
||||
if (e.clipboardData && editor.user.getPasteAtCursor()) {
|
||||
handlePasteFromClipboardApi(editor, clipboardItems)
|
||||
} else {
|
||||
handlePasteFromClipboardApi(editor, clipboardItems, editor.inputs.currentPagePoint)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -87,6 +87,8 @@ export type TLUiTranslationKey =
|
|||
| 'action.toggle-auto-size'
|
||||
| 'action.toggle-dark-mode.menu'
|
||||
| 'action.toggle-dark-mode'
|
||||
| 'action.toggle-paste-at-cursor.menu'
|
||||
| 'action.toggle-paste-at-cursor'
|
||||
| 'action.toggle-wrap-mode.menu'
|
||||
| 'action.toggle-wrap-mode'
|
||||
| 'action.toggle-reduce-motion.menu'
|
||||
|
|
|
@ -87,6 +87,8 @@ export const DEFAULT_TRANSLATION = {
|
|||
'action.toggle-auto-size': 'Toggle auto size',
|
||||
'action.toggle-dark-mode.menu': 'Dark mode',
|
||||
'action.toggle-dark-mode': 'Toggle dark mode',
|
||||
'action.toggle-paste-at-cursor.menu': 'Paste at cursor',
|
||||
'action.toggle-paste-at-cursor': 'Toggle paste at cursor',
|
||||
'action.toggle-wrap-mode.menu': 'Select on wrap',
|
||||
'action.toggle-wrap-mode': 'Toggle Select on wrap',
|
||||
'action.toggle-reduce-motion.menu': 'Reduce motion',
|
||||
|
|
Loading…
Reference in a new issue