mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-17 06:30:50 +01:00
Fix Android safe-area handling and window maximization
- Fix extension signature verification on Android by canonicalizing paths (symlink compatibility) - Implement proper safe-area-inset handling for mobile devices - Add reactive header height measurement to UI store - Fix maximized window positioning to respect safe-areas and header - Create reusable HaexDebugOverlay component for mobile debugging - Fix Swiper navigation by using absolute positioning instead of flex-1 - Remove debug logging after Android compatibility confirmed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
61
src/components/haex/debug/overlay.vue
Normal file
61
src/components/haex/debug/overlay.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="data"
|
||||
class="fixed top-2 right-2 bg-black/90 text-white text-xs p-3 rounded-lg shadow-2xl max-w-sm z-[9999] backdrop-blur-sm"
|
||||
>
|
||||
<div class="flex justify-between items-start gap-3 mb-2">
|
||||
<span class="font-bold text-sm">{{ title }}</span>
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
class="bg-white/20 hover:bg-white/30 px-2 py-1 rounded text-xs transition-colors"
|
||||
@click="copyToClipboardAsync"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
v-if="dismissible"
|
||||
class="bg-white/20 hover:bg-white/30 px-2 py-1 rounded text-xs transition-colors"
|
||||
@click="handleDismiss"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre class="text-xs whitespace-pre-wrap font-mono overflow-auto max-h-96">{{ formattedData }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: Record<string, any> | null
|
||||
title?: string
|
||||
dismissible?: boolean
|
||||
}>(),
|
||||
{
|
||||
title: 'Debug Info',
|
||||
dismissible: false,
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
dismiss: []
|
||||
}>()
|
||||
|
||||
const formattedData = computed(() => {
|
||||
if (!props.data) return ''
|
||||
return JSON.stringify(props.data, null, 2)
|
||||
})
|
||||
|
||||
const copyToClipboardAsync = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(formattedData.value)
|
||||
} catch (err) {
|
||||
console.error('Failed to copy debug info:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDismiss = () => {
|
||||
emit('dismiss')
|
||||
}
|
||||
</script>
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
ref="desktopEl"
|
||||
class="w-full h-full relative overflow-hidden"
|
||||
class="absolute inset-0 overflow-hidden"
|
||||
>
|
||||
<Swiper
|
||||
:modules="[SwiperNavigation]"
|
||||
@ -13,7 +13,7 @@
|
||||
:no-swiping="true"
|
||||
no-swiping-class="no-swipe"
|
||||
:allow-touch-move="allowSwipe"
|
||||
class="w-full h-full"
|
||||
class="h-full w-full"
|
||||
direction="vertical"
|
||||
@swiper="onSwiperInit"
|
||||
@slide-change="onSlideChange"
|
||||
@ -115,7 +115,14 @@
|
||||
:source-height="window.sourceHeight"
|
||||
:is-opening="window.isOpening"
|
||||
:is-closing="window.isClosing"
|
||||
:warning-level="window.type === 'extension' && availableExtensions.find(ext => ext.id === window.sourceId)?.devServerUrl ? 'warning' : undefined"
|
||||
:warning-level="
|
||||
window.type === 'extension' &&
|
||||
availableExtensions.find(
|
||||
(ext) => ext.id === window.sourceId,
|
||||
)?.devServerUrl
|
||||
? 'warning'
|
||||
: undefined
|
||||
"
|
||||
class="no-swipe"
|
||||
@close="windowManager.closeWindow(window.id)"
|
||||
@minimize="windowManager.minimizeWindow(window.id)"
|
||||
@ -165,7 +172,13 @@
|
||||
:source-height="window.sourceHeight"
|
||||
:is-opening="window.isOpening"
|
||||
:is-closing="window.isClosing"
|
||||
:warning-level="window.type === 'extension' && availableExtensions.find(ext => ext.id === window.sourceId)?.devServerUrl ? 'warning' : undefined"
|
||||
:warning-level="
|
||||
window.type === 'extension' &&
|
||||
availableExtensions.find((ext) => ext.id === window.sourceId)
|
||||
?.devServerUrl
|
||||
? 'warning'
|
||||
: undefined
|
||||
"
|
||||
class="no-swipe"
|
||||
@close="windowManager.closeWindow(window.id)"
|
||||
@minimize="windowManager.minimizeWindow(window.id)"
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<div class="flex items-start gap-4">
|
||||
<div
|
||||
v-if="preview?.manifest.icon"
|
||||
class="w-16 h-16 flex-shrink-0"
|
||||
class="w-16 h-16 shrink-0"
|
||||
>
|
||||
<UIcon
|
||||
:name="preview.manifest.icon"
|
||||
@ -184,7 +184,6 @@ const shellPermissions = computed({
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
const permissionAccordionItems = computed(() => {
|
||||
const items = []
|
||||
|
||||
|
||||
@ -3,15 +3,17 @@
|
||||
ref="windowEl"
|
||||
:style="windowStyle"
|
||||
:class="[
|
||||
'absolute bg-default/80 backdrop-blur-xl rounded-lg shadow-xl overflow-hidden isolate',
|
||||
'absolute bg-default/80 backdrop-blur-xl rounded-lg shadow-xl overflow-hidden',
|
||||
'transition-all ease-out duration-600',
|
||||
'flex flex-col @container',
|
||||
{ 'select-none': isResizingOrDragging },
|
||||
isActive ? 'z-20' : 'z-10',
|
||||
// Border colors based on warning level
|
||||
warningLevel === 'warning' ? 'border-2 border-warning-500' :
|
||||
warningLevel === 'danger' ? 'border-2 border-danger-500' :
|
||||
'border border-gray-200 dark:border-gray-700',
|
||||
warningLevel === 'warning'
|
||||
? 'border-2 border-warning-500'
|
||||
: warningLevel === 'danger'
|
||||
? 'border-2 border-danger-500'
|
||||
: 'border border-gray-200 dark:border-gray-700',
|
||||
]"
|
||||
@mousedown="handleActivate"
|
||||
>
|
||||
@ -320,13 +322,33 @@ const handleMaximize = () => {
|
||||
const bounds = getViewportBounds()
|
||||
|
||||
if (bounds && bounds.width > 0 && bounds.height > 0) {
|
||||
// Get safe-area-insets from CSS variables for debug
|
||||
const safeAreaTop = parseFloat(
|
||||
getComputedStyle(document.documentElement).getPropertyValue(
|
||||
'--safe-area-inset-top',
|
||||
) || '0',
|
||||
)
|
||||
const safeAreaBottom = parseFloat(
|
||||
getComputedStyle(document.documentElement).getPropertyValue(
|
||||
'--safe-area-inset-bottom',
|
||||
) || '0',
|
||||
)
|
||||
|
||||
// Desktop container uses 'absolute inset-0' which stretches over full viewport
|
||||
// bounds.height = full viewport height (includes header area + safe-areas)
|
||||
// We need to calculate available space properly
|
||||
|
||||
// Get header height from UI store (measured reactively in layout)
|
||||
const uiStore = useUiStore()
|
||||
const headerHeight = uiStore.headerHeight
|
||||
|
||||
x.value = 0
|
||||
y.value = 0
|
||||
y.value = 0 // Start below header and status bar
|
||||
width.value = bounds.width
|
||||
height.value = bounds.height
|
||||
// Height: viewport - header - both safe-areas
|
||||
height.value = bounds.height - headerHeight - safeAreaTop - safeAreaBottom
|
||||
isMaximized.value = true
|
||||
}
|
||||
console.log('handleMaximize', preMaximizeState, bounds)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user