Merge pull request #239 from PasteBar/long-press-clip-orginize-update
long press to activate clips organize update
This commit is contained in:
commit
2144265b9c
5
.changeset/afraid-steaks-think.md
Normal file
5
.changeset/afraid-steaks-think.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'pastebar-app-ui': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Added long press on clip to activate clips organize
|
@ -1,27 +0,0 @@
|
|||||||
export function useLongPress() {
|
|
||||||
return function (callback) {
|
|
||||||
let timeout
|
|
||||||
|
|
||||||
function start(e) {
|
|
||||||
if (e.type === 'mousedown' && e.button !== 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
callback()
|
|
||||||
}, 1500)
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear() {
|
|
||||||
timeout && clearTimeout(timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
onMouseDown: start,
|
|
||||||
onTouchStart: start,
|
|
||||||
onMouseUp: clear,
|
|
||||||
onMouseLeave: clear,
|
|
||||||
onTouchMove: clear,
|
|
||||||
onTouchEnd: clear,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
150
packages/pastebar-app-ui/src/hooks/use-long-press.ts
Normal file
150
packages/pastebar-app-ui/src/hooks/use-long-press.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { useCallback, useRef } from 'react'
|
||||||
|
|
||||||
|
interface LongPressOptions {
|
||||||
|
delay?: number
|
||||||
|
threshold?: number
|
||||||
|
cancelOnMove?: boolean
|
||||||
|
preventDefault?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LongPressHandlers {
|
||||||
|
onMouseDown: (e: React.MouseEvent) => void
|
||||||
|
onMouseUp: (e: React.MouseEvent) => void
|
||||||
|
onMouseLeave: (e: React.MouseEvent) => void
|
||||||
|
onMouseMove?: (e: React.MouseEvent) => void
|
||||||
|
onTouchStart: (e: React.TouchEvent) => void
|
||||||
|
onTouchEnd: (e: React.TouchEvent) => void
|
||||||
|
onTouchMove: (e: React.TouchEvent) => void
|
||||||
|
onTouchCancel: (e: React.TouchEvent) => void
|
||||||
|
onContextMenu?: (e: React.MouseEvent) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLongPress(
|
||||||
|
callback: () => void,
|
||||||
|
options: LongPressOptions = {}
|
||||||
|
): LongPressHandlers {
|
||||||
|
const {
|
||||||
|
delay = 600,
|
||||||
|
threshold = 10,
|
||||||
|
cancelOnMove = true,
|
||||||
|
preventDefault = true
|
||||||
|
} = options
|
||||||
|
|
||||||
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
const startPositionRef = useRef<{ x: number; y: number } | null>(null)
|
||||||
|
const isLongPressTriggeredRef = useRef(false)
|
||||||
|
const isTouchRef = useRef(false)
|
||||||
|
|
||||||
|
const clear = useCallback(() => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current)
|
||||||
|
timeoutRef.current = null
|
||||||
|
}
|
||||||
|
startPositionRef.current = null
|
||||||
|
isLongPressTriggeredRef.current = false
|
||||||
|
isTouchRef.current = false
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const start = useCallback(
|
||||||
|
(e: React.MouseEvent | React.TouchEvent) => {
|
||||||
|
// Prevent multiple simultaneous long press handlers
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only handle left mouse button for mouse events
|
||||||
|
if ('button' in e && e.button !== 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if this is a touch event
|
||||||
|
isTouchRef.current = 'touches' in e
|
||||||
|
|
||||||
|
// Get the starting position
|
||||||
|
const touch = 'touches' in e ? e.touches[0] : e
|
||||||
|
startPositionRef.current = {
|
||||||
|
x: touch.clientX,
|
||||||
|
y: touch.clientY
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preventDefault) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the long press timer
|
||||||
|
timeoutRef.current = setTimeout(() => {
|
||||||
|
isLongPressTriggeredRef.current = true
|
||||||
|
callback()
|
||||||
|
clear()
|
||||||
|
}, delay)
|
||||||
|
},
|
||||||
|
[callback, clear, delay, preventDefault]
|
||||||
|
)
|
||||||
|
|
||||||
|
const move = useCallback(
|
||||||
|
(e: React.MouseEvent | React.TouchEvent) => {
|
||||||
|
if (!cancelOnMove || !startPositionRef.current || !timeoutRef.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current position
|
||||||
|
const touch = 'touches' in e ? e.touches[0] : e
|
||||||
|
const currentX = touch.clientX
|
||||||
|
const currentY = touch.clientY
|
||||||
|
|
||||||
|
// Calculate distance moved
|
||||||
|
const deltaX = Math.abs(currentX - startPositionRef.current.x)
|
||||||
|
const deltaY = Math.abs(currentY - startPositionRef.current.y)
|
||||||
|
|
||||||
|
// Cancel if moved beyond threshold
|
||||||
|
if (deltaX > threshold || deltaY > threshold) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[cancelOnMove, clear, threshold]
|
||||||
|
)
|
||||||
|
|
||||||
|
const end = useCallback(
|
||||||
|
(e: React.MouseEvent | React.TouchEvent) => {
|
||||||
|
// Prevent default only if long press was triggered
|
||||||
|
if (isLongPressTriggeredRef.current && preventDefault) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
clear()
|
||||||
|
},
|
||||||
|
[clear, preventDefault]
|
||||||
|
)
|
||||||
|
|
||||||
|
const cancel = useCallback(() => {
|
||||||
|
clear()
|
||||||
|
}, [clear])
|
||||||
|
|
||||||
|
// Prevent context menu on long press
|
||||||
|
const handleContextMenu = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
if (isLongPressTriggeredRef.current || timeoutRef.current) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
onMouseDown: start as (e: React.MouseEvent) => void,
|
||||||
|
onMouseUp: end as (e: React.MouseEvent) => void,
|
||||||
|
onMouseLeave: cancel,
|
||||||
|
onMouseMove: cancelOnMove ? (move as (e: React.MouseEvent) => void) : undefined,
|
||||||
|
onTouchStart: start as (e: React.TouchEvent) => void,
|
||||||
|
onTouchEnd: end as (e: React.TouchEvent) => void,
|
||||||
|
onTouchMove: move as (e: React.TouchEvent) => void,
|
||||||
|
onTouchCancel: cancel,
|
||||||
|
onContextMenu: preventDefault ? handleContextMenu : undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Higher-order function version for backward compatibility
|
||||||
|
export function useLongPressHOF() {
|
||||||
|
return function (callback: () => void, options?: LongPressOptions) {
|
||||||
|
return useLongPress(callback, options)
|
||||||
|
}
|
||||||
|
}
|
@ -209,7 +209,7 @@ export const ClipboardHistoryIconMenu = ({
|
|||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
id="history-menu-button_tour"
|
id="history-menu-button_tour"
|
||||||
className="w-10 text-gray-400 hover:text-gray-500 hover:dark:text-gray-400 dark:text-gray-500 bg-transparent p-1 relative hover:bg-gray-100/70 dark:bg-gray-900 dark:hover:bg-gray-700/70"
|
className="w-10 text-gray-400 hover:text-gray-500 hover:dark:text-gray-400 dark:text-gray-500 bg-transparent p-1 relative hover:bg-gray-100/70 dark:hover:bg-gray-700/70"
|
||||||
>
|
>
|
||||||
<HistoryIcon
|
<HistoryIcon
|
||||||
className="w-5 max-w-[22px] min-w-[16px] stroke-[1.3px]"
|
className="w-5 max-w-[22px] min-w-[16px] stroke-[1.3px]"
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
isKeyAltPressed,
|
isKeyAltPressed,
|
||||||
settingsStoreAtom,
|
settingsStoreAtom,
|
||||||
showClipFindKeyPressed,
|
showClipFindKeyPressed,
|
||||||
|
showClipsMoveOnBoardId,
|
||||||
showLargeViewClipId,
|
showLargeViewClipId,
|
||||||
showLinkedClipId,
|
showLinkedClipId,
|
||||||
} from '~/store'
|
} from '~/store'
|
||||||
@ -363,7 +364,20 @@ export function ClipCard({
|
|||||||
const isSearch = useSignal(false)
|
const isSearch = useSignal(false)
|
||||||
const searchTerm = useSignal('')
|
const searchTerm = useSignal('')
|
||||||
const { updateItemById } = useUpdateItemById()
|
const { updateItemById } = useUpdateItemById()
|
||||||
// const onLongPress = useLongPress()
|
const longPressHandlers = useLongPress(
|
||||||
|
() => {
|
||||||
|
if (isClipEdit || isShowDetails || isPinnedBoard) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
showClipsMoveOnBoardId.value = clip.parentId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
delay: 1000,
|
||||||
|
threshold: 10,
|
||||||
|
cancelOnMove: true,
|
||||||
|
preventDefault: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const [copiedItem, setCopiedItem, _, copyInProgressItemId] = useCopyClipItem({})
|
const [copiedItem, setCopiedItem, _, copyInProgressItemId] = useCopyClipItem({})
|
||||||
const [pastedItem, pastingCountDown, setPastedItem] = usePasteClipItem({})
|
const [pastedItem, pastingCountDown, setPastedItem] = usePasteClipItem({})
|
||||||
@ -665,12 +679,7 @@ export function ClipCard({
|
|||||||
>
|
>
|
||||||
<CardHeaderWithRef
|
<CardHeaderWithRef
|
||||||
title={titleText}
|
title={titleText}
|
||||||
// {...onLongPress(() => {
|
{...longPressHandlers}
|
||||||
// if (isClipEdit || isShowDetails || isPinnedBoard) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// showClipsMoveOnBoardId.value = clip.parentId
|
|
||||||
// })}
|
|
||||||
onClickCapture={e => {
|
onClickCapture={e => {
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -110,7 +110,7 @@ export const MenuIconMenu = ({
|
|||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
id="menu-icon-menu-button_tour"
|
id="menu-icon-menu-button_tour"
|
||||||
className="w-10 text-gray-400 hover:text-gray-500 hover:dark:text-gray-400 dark:text-gray-500 bg-transparent p-1 relative hover:bg-gray-100/70 dark:bg-gray-900 dark:hover:bg-gray-700/70"
|
className="w-10 text-gray-400 hover:text-gray-500 hover:dark:text-gray-400 dark:text-gray-500 bg-transparent p-1 relative hover:bg-gray-100/70 dark:hover:bg-gray-700/70"
|
||||||
>
|
>
|
||||||
<MenuSquare className="stroke-[1.3px]" size={22} />
|
<MenuSquare className="stroke-[1.3px]" size={22} />
|
||||||
{selectedItemIds.length > 1 && (
|
{selectedItemIds.length > 1 && (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user