141 lines
6.1 KiB
Vue
141 lines
6.1 KiB
Vue
|
|
<template>
|
||
|
|
<div class="sticky top-0 z-10 flex items-center gap-3 border-b border-white/10 px-4 py-3 bg-black/60 backdrop-blur-md md:bg-transparent md:backdrop-blur-none">
|
||
|
|
<!-- Back / Forward navigation -->
|
||
|
|
<div class="flex items-center gap-0.5">
|
||
|
|
<button class="app-session-btn" aria-label="Back" title="Go back" @click="$emit('goBack')">
|
||
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||
|
|
</svg>
|
||
|
|
</button>
|
||
|
|
<button class="app-session-btn" aria-label="Forward" title="Go forward" @click="$emit('goForward')">
|
||
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||
|
|
</svg>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<span class="flex-1 truncate text-sm font-medium text-white/90">{{ appTitle }}</span>
|
||
|
|
|
||
|
|
<button class="app-session-btn" aria-label="Refresh" :disabled="isRefreshing" @click="$emit('refresh')">
|
||
|
|
<svg class="w-5 h-5 transition-transform duration-300" :class="{ 'animate-spin': isRefreshing }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||
|
|
</svg>
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<!-- Display mode selector -->
|
||
|
|
<div class="relative" ref="modeMenuRef">
|
||
|
|
<button
|
||
|
|
class="app-session-btn"
|
||
|
|
aria-label="Display mode"
|
||
|
|
title="Display mode"
|
||
|
|
@click="showModeMenu = !showModeMenu"
|
||
|
|
>
|
||
|
|
<!-- Panel icon -->
|
||
|
|
<svg v-if="displayMode === 'panel'" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v18m12-18H3a1 1 0 00-1 1v16a1 1 0 001 1h18a1 1 0 001-1V4a1 1 0 00-1-1z" />
|
||
|
|
</svg>
|
||
|
|
<!-- Overlay icon -->
|
||
|
|
<svg v-else-if="displayMode === 'overlay'" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v14a1 1 0 01-1 1H5a1 1 0 01-1-1V5z" />
|
||
|
|
</svg>
|
||
|
|
<!-- Fullscreen icon -->
|
||
|
|
<svg v-else class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5v-4m0 4h-4m4 0l-5-5" />
|
||
|
|
</svg>
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<!-- Dropdown -->
|
||
|
|
<Transition name="menu-fade">
|
||
|
|
<div v-if="showModeMenu" class="absolute right-0 top-full mt-1 w-48 bg-black/90 border border-white/10 rounded-lg backdrop-blur-xl shadow-2xl overflow-hidden z-50">
|
||
|
|
<button
|
||
|
|
class="mode-option"
|
||
|
|
:class="{ 'mode-option-active': displayMode === 'panel' }"
|
||
|
|
@click="selectMode('panel')"
|
||
|
|
>
|
||
|
|
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v18m12-18H3a1 1 0 00-1 1v16a1 1 0 001 1h18a1 1 0 001-1V4a1 1 0 00-1-1z" />
|
||
|
|
</svg>
|
||
|
|
<span>Right panel</span>
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
class="mode-option"
|
||
|
|
:class="{ 'mode-option-active': displayMode === 'overlay' }"
|
||
|
|
@click="selectMode('overlay')"
|
||
|
|
>
|
||
|
|
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v14a1 1 0 01-1 1H5a1 1 0 01-1-1V5z" />
|
||
|
|
</svg>
|
||
|
|
<span>Over whole app</span>
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
class="mode-option"
|
||
|
|
:class="{ 'mode-option-active': displayMode === 'fullscreen' }"
|
||
|
|
@click="selectMode('fullscreen')"
|
||
|
|
>
|
||
|
|
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5v-4m0 4h-4m4 0l-5-5" />
|
||
|
|
</svg>
|
||
|
|
<span>Open fullscreen</span>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</Transition>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<button class="app-session-btn" aria-label="Open in new tab" title="Open in new tab" @click="$emit('openNewTab')">
|
||
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||
|
|
</svg>
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<button class="app-session-btn" aria-label="Close" @click="$emit('close')">
|
||
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||
|
|
</svg>
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<kbd class="hidden sm:inline-flex px-2 py-1 text-xs text-white/50 bg-white/10 rounded">Esc</kbd>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||
|
|
import type { DisplayMode } from './appSessionConfig'
|
||
|
|
|
||
|
|
defineProps<{
|
||
|
|
appTitle: string
|
||
|
|
isRefreshing: boolean
|
||
|
|
displayMode: DisplayMode
|
||
|
|
}>()
|
||
|
|
|
||
|
|
const emit = defineEmits<{
|
||
|
|
goBack: []
|
||
|
|
goForward: []
|
||
|
|
refresh: []
|
||
|
|
openNewTab: []
|
||
|
|
close: []
|
||
|
|
setMode: [mode: DisplayMode]
|
||
|
|
}>()
|
||
|
|
|
||
|
|
const showModeMenu = ref(false)
|
||
|
|
const modeMenuRef = ref<HTMLElement | null>(null)
|
||
|
|
|
||
|
|
function selectMode(mode: DisplayMode) {
|
||
|
|
showModeMenu.value = false
|
||
|
|
emit('setMode', mode)
|
||
|
|
}
|
||
|
|
|
||
|
|
function onClickOutside(e: MouseEvent) {
|
||
|
|
if (showModeMenu.value && modeMenuRef.value && !modeMenuRef.value.contains(e.target as Node)) {
|
||
|
|
showModeMenu.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
onMounted(() => {
|
||
|
|
document.addEventListener('click', onClickOutside)
|
||
|
|
})
|
||
|
|
|
||
|
|
onBeforeUnmount(() => {
|
||
|
|
document.removeEventListener('click', onClickOutside)
|
||
|
|
})
|
||
|
|
</script>
|