Fix browser text selection during icon drag

- Add e.preventDefault() in handlePointerDown to prevent text selection
- Add @dragstart.prevent to prevent native browser drag
- Add select-none and @selectstart.prevent to workspace
- Add mouseleave event listener to reset drag state when leaving window
- Refactor grid positioning to use consistent iconPadding constant
This commit is contained in:
2025-11-04 22:36:17 +01:00
parent 731ae7cc47
commit b465c117b0
4 changed files with 74 additions and 37 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "haex-hub", "name": "haex-hub",
"private": true, "private": true,
"version": "0.1.9", "version": "0.1.10",
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "nuxt build", "build": "nuxt build",

View File

@ -18,6 +18,7 @@
@pointerdown.left="handlePointerDown" @pointerdown.left="handlePointerDown"
@pointermove="handlePointerMove" @pointermove="handlePointerMove"
@pointerup="handlePointerUp" @pointerup="handlePointerUp"
@dragstart.prevent
@click.left="handleClick" @click.left="handleClick"
@dblclick="handleDoubleClick" @dblclick="handleDoubleClick"
> >
@ -176,6 +177,9 @@ const style = computed(() => ({
const handlePointerDown = (e: PointerEvent) => { const handlePointerDown = (e: PointerEvent) => {
if (!draggableEl.value || !draggableEl.value.parentElement) return if (!draggableEl.value || !draggableEl.value.parentElement) return
// Prevent any text selection during drag
e.preventDefault()
isDragging.value = true isDragging.value = true
emit('dragStart', props.id, props.itemType, props.referenceId, iconWidth.value, iconHeight.value, x.value, y.value) emit('dragStart', props.id, props.itemType, props.referenceId, iconWidth.value, iconHeight.value, x.value, y.value)

View File

@ -25,12 +25,13 @@
> >
<UContextMenu :items="getWorkspaceContextMenuItems(workspace.id)"> <UContextMenu :items="getWorkspaceContextMenuItems(workspace.id)">
<div <div
class="w-full h-full relative" class="w-full h-full relative select-none"
:style="getWorkspaceBackgroundStyle(workspace)" :style="getWorkspaceBackgroundStyle(workspace)"
@click.self.stop="handleDesktopClick" @click.self.stop="handleDesktopClick"
@mousedown.left.self="handleAreaSelectStart" @mousedown.left.self="handleAreaSelectStart"
@dragover.prevent="handleDragOver" @dragover.prevent="handleDragOver"
@drop.prevent="handleDrop($event, workspace.id)" @drop.prevent="handleDrop($event, workspace.id)"
@selectstart.prevent
> >
<!-- Drop Target Zone (visible during drag) --> <!-- Drop Target Zone (visible during drag) -->
<div <div
@ -735,6 +736,21 @@ watch(currentWorkspace, async () => {
} }
}) })
// Reset drag state when mouse leaves the document (fixes stuck dropzone)
useEventListener(document, 'mouseleave', () => {
if (isDragging.value) {
isDragging.value = false
currentDraggedItem.id = ''
currentDraggedItem.itemType = ''
currentDraggedItem.referenceId = ''
currentDraggedItem.width = 0
currentDraggedItem.height = 0
currentDraggedItem.x = 0
currentDraggedItem.y = 0
allowSwipe.value = true
}
})
onMounted(async () => { onMounted(async () => {
// Load workspaces first // Load workspaces first
await workspaceStore.loadWorkspacesAsync() await workspaceStore.loadWorkspacesAsync()

View File

@ -27,24 +27,26 @@ export const useDesktopStore = defineStore('desktopStore', () => {
const deviceStore = useDeviceStore() const deviceStore = useDeviceStore()
const settingsStore = useVaultSettingsStore() const settingsStore = useVaultSettingsStore()
$i18n.setLocaleMessage('de', { $i18n.setLocaleMessage('de', { desktop: de })
desktop: de,
})
$i18n.setLocaleMessage('en', { desktop: en }) $i18n.setLocaleMessage('en', { desktop: en })
const desktopItems = ref<IDesktopItem[]>([]) const desktopItems = ref<IDesktopItem[]>([])
const selectedItemIds = ref<Set<string>>(new Set()) const selectedItemIds = ref<Set<string>>(new Set())
// Desktop Grid Settings (stored in DB per device) // Desktop Grid Settings (stored in DB per device)
const iconSizePreset = ref<DesktopIconSizePreset>(DesktopIconSizePreset.medium) const iconSizePreset = ref<DesktopIconSizePreset>(
DesktopIconSizePreset.medium,
)
// Get device internal ID from DB // Get device internal ID from DB
const getDeviceInternalIdAsync = async () => { const getDeviceInternalIdAsync = async () => {
if (!deviceStore.deviceId || !currentVault.value?.drizzle) return undefined if (!deviceStore.deviceId || !currentVault.value?.drizzle) return undefined
const device = await currentVault.value.drizzle.query.haexDevices.findFirst({ const device = await currentVault.value.drizzle.query.haexDevices.findFirst(
{
where: eq(haexDevices.deviceId, deviceStore.deviceId), where: eq(haexDevices.deviceId, deviceStore.deviceId),
}) },
)
return device?.id ? device.id : undefined return device?.id ? device.id : undefined
} }
@ -54,7 +56,8 @@ export const useDesktopStore = defineStore('desktopStore', () => {
const deviceInternalId = await getDeviceInternalIdAsync() const deviceInternalId = await getDeviceInternalIdAsync()
if (!deviceInternalId) return if (!deviceInternalId) return
const preset = await settingsStore.syncDesktopIconSizeAsync(deviceInternalId) const preset =
await settingsStore.syncDesktopIconSizeAsync(deviceInternalId)
iconSizePreset.value = preset iconSizePreset.value = preset
} }
@ -71,22 +74,26 @@ export const useDesktopStore = defineStore('desktopStore', () => {
return iconSizePresetValues[iconSizePreset.value] return iconSizePresetValues[iconSizePreset.value]
}) })
const iconPadding = 30
// Calculate grid cell size based on icon size // Calculate grid cell size based on icon size
const gridCellSize = computed(() => { const gridCellSize = computed(() => {
// Add padding around icon (30px extra for spacing) // Add padding around icon (30px extra for spacing)
return effectiveIconSize.value + 30 return effectiveIconSize.value + iconPadding
}) })
// Grid offsets for start position (negative = move grid up)
const gridOffsetY = -30 // Start grid 30px higher
// Snap position to grid (centers icon in cell) // Snap position to grid (centers icon in cell)
// iconWidth and iconHeight are optional - if provided, they're used for centering // iconWidth and iconHeight are optional - if provided, they're used for centering
const snapToGrid = (x: number, y: number, iconWidth?: number, iconHeight?: number) => { const snapToGrid = (
x: number,
y: number,
iconWidth?: number,
iconHeight?: number,
) => {
const cellSize = gridCellSize.value const cellSize = gridCellSize.value
// Adjust y for grid offset // Adjust y for grid offset
const adjustedY = Math.max(0, y - gridOffsetY) const adjustedY = Math.max(0, y + iconPadding)
// Calculate which grid cell the position falls into // Calculate which grid cell the position falls into
const col = Math.floor(x / cellSize) const col = Math.floor(x / cellSize)
@ -107,8 +114,8 @@ export const useDesktopStore = defineStore('desktopStore', () => {
const paddingY = (totalHeight - actualIconHeight) / 2 const paddingY = (totalHeight - actualIconHeight) / 2
return { return {
x: col * cellSize + paddingX, x: col * cellSize + paddingX - iconPadding,
y: row * cellSize + paddingY + gridOffsetY, y: row * cellSize + paddingY - iconPadding,
} }
} }
@ -129,9 +136,12 @@ export const useDesktopStore = defineStore('desktopStore', () => {
.from(haexDesktopItems) .from(haexDesktopItems)
.where(eq(haexDesktopItems.workspaceId, currentWorkspace.value.id)) .where(eq(haexDesktopItems.workspaceId, currentWorkspace.value.id))
desktopItems.value = items.map(item => ({ desktopItems.value = items.map((item) => ({
...item, ...item,
referenceId: item.itemType === 'extension' ? item.extensionId! : item.systemWindowId!, referenceId:
item.itemType === 'extension'
? item.extensionId!
: item.systemWindowId!,
})) }))
} catch (error) { } catch (error) {
console.error('Fehler beim Laden der Desktop-Items:', error) console.error('Fehler beim Laden der Desktop-Items:', error)
@ -160,7 +170,10 @@ export const useDesktopStore = defineStore('desktopStore', () => {
workspaceId: targetWorkspaceId, workspaceId: targetWorkspaceId,
itemType: itemType, itemType: itemType,
extensionId: itemType === 'extension' ? referenceId : null, extensionId: itemType === 'extension' ? referenceId : null,
systemWindowId: itemType === 'system' || itemType === 'file' || itemType === 'folder' ? referenceId : null, systemWindowId:
itemType === 'system' || itemType === 'file' || itemType === 'folder'
? referenceId
: null,
positionX: positionX, positionX: positionX,
positionY: positionY, positionY: positionY,
} }
@ -173,7 +186,10 @@ export const useDesktopStore = defineStore('desktopStore', () => {
if (result.length > 0 && result[0]) { if (result.length > 0 && result[0]) {
const itemWithRef = { const itemWithRef = {
...result[0], ...result[0],
referenceId: itemType === 'extension' ? result[0].extensionId! : result[0].systemWindowId!, referenceId:
itemType === 'extension'
? result[0].extensionId!
: result[0].systemWindowId!,
} }
desktopItems.value.push(itemWithRef) desktopItems.value.push(itemWithRef)
return itemWithRef return itemWithRef
@ -184,7 +200,7 @@ export const useDesktopStore = defineStore('desktopStore', () => {
itemType, itemType,
referenceId, referenceId,
workspaceId: targetWorkspaceId, workspaceId: targetWorkspaceId,
position: { x: positionX, y: positionY } position: { x: positionX, y: positionY },
}) })
// Log full error details // Log full error details
@ -221,7 +237,10 @@ export const useDesktopStore = defineStore('desktopStore', () => {
const item = result[0] const item = result[0]
desktopItems.value[index] = { desktopItems.value[index] = {
...item, ...item,
referenceId: item.itemType === 'extension' ? item.extensionId! : item.systemWindowId!, referenceId:
item.itemType === 'extension'
? item.extensionId!
: item.systemWindowId!,
} }
} }
} }
@ -254,16 +273,14 @@ export const useDesktopStore = defineStore('desktopStore', () => {
itemType: DesktopItemType, itemType: DesktopItemType,
referenceId: string, referenceId: string,
) => { ) => {
return desktopItems.value.find( return desktopItems.value.find((item) => {
(item) => {
if (item.itemType !== itemType) return false if (item.itemType !== itemType) return false
if (itemType === 'extension') { if (itemType === 'extension') {
return item.extensionId === referenceId return item.extensionId === referenceId
} else { } else {
return item.systemWindowId === referenceId return item.systemWindowId === referenceId
} }
}, })
)
} }
const openDesktopItem = ( const openDesktopItem = (
@ -274,9 +291,9 @@ export const useDesktopStore = defineStore('desktopStore', () => {
const windowManager = useWindowManagerStore() const windowManager = useWindowManagerStore()
if (itemType === 'system') { if (itemType === 'system') {
const systemWindow = windowManager.getAllSystemWindows().find( const systemWindow = windowManager
(win) => win.id === referenceId, .getAllSystemWindows()
) .find((win) => win.id === referenceId)
if (systemWindow) { if (systemWindow) {
windowManager.openWindowAsync({ windowManager.openWindowAsync({