wip bsky client for the web & android bbell.vt3e.cat
at main 144 lines 3.0 kB view raw
1<script setup lang="ts"> 2import { computed, onMounted, onUnmounted } from 'vue' 3import { App } from '@capacitor/app' 4import { useNavigationStore } from '@/stores/navigation' 5import { useModalStore } from '@/stores/modal' 6import { stackRoots, type StackRootNames } from '@/router' 7 8import TabStack from '@/components/Navigation/TabStack.vue' 9import ToastStack from '@/components/UI/Toast/ToastStack.vue' 10import NavigationBar from '@/components/Navigation/NavigationBar.vue' 11 12import NewPostModal from '../Modals/NewPostModal.vue' 13 14const nav = useNavigationStore() 15const modals = useModalStore() 16 17const activeTab = computed(() => nav.activeTab) 18const tabs: StackRootNames[] = stackRoots.map((p) => p.name) 19 20const handleBackNavigation = () => { 21 if (!nav.canGoBack) { 22 if (activeTab.value !== 'home') { 23 nav.switchTab('home') 24 return 25 } 26 27 // exit if we are at root of home 28 App.exitApp().catch(() => {}) 29 } 30 nav.pop() 31} 32 33function isTypingInInput(): boolean { 34 const active = document.activeElement as HTMLElement | null 35 if (!active) return false 36 return ( 37 active.tagName === 'INPUT' || 38 active.tagName === 'TEXTAREA' || 39 active.tagName === 'SELECT' || 40 active.isContentEditable 41 ) 42} 43 44function handleKeybings(e: KeyboardEvent) { 45 if (isTypingInInput()) return 46 if (!e.ctrlKey) { 47 switch (e.key) { 48 case 'c': 49 modals.open(NewPostModal) 50 break 51 } 52 } 53} 54 55onMounted(() => { 56 App.addListener('backButton', handleBackNavigation) 57 document.addEventListener('keyup', (e) => { 58 if (e.key === 'e' && e.altKey) handleBackNavigation() 59 }) 60 window.addEventListener('keyup', handleKeybings) 61}) 62 63onUnmounted(() => { 64 App.removeAllListeners() 65 window.removeEventListener('keyup', handleKeybings) 66}) 67</script> 68 69<template> 70 <div class="app-shell"> 71 <div class="skip-links"> 72 <a href="#main-content" id="skip-to-content" class="skip-link"> skip to main content </a> 73 <a href="#navigation-bar" class="skip-link"> skip to navigation </a> 74 </div> 75 76 <div class="viewport" id="main-content"> 77 <ToastStack /> 78 <TabStack 79 v-for="t in tabs" 80 :key="t" 81 :tab="t" 82 v-show="activeTab === t" 83 :class="{ active: activeTab === t }" 84 /> 85 </div> 86 <NavigationBar ref="navBar" /> 87 </div> 88</template> 89 90<style scoped> 91.app-shell { 92 position: relative; 93 height: 100vh; 94 width: 100vw; 95 overflow: hidden; 96 background-color: hsla(var(--mantle) / 1); 97 display: flex; 98 flex-direction: column; 99} 100 101.viewport { 102 position: relative; 103 flex: 1; 104 overflow: hidden; 105 z-index: 0; 106} 107 108@media (min-width: 512px) { 109 .app-shell { 110 flex-direction: row; 111 justify-content: center; 112 } 113 114 .viewport { 115 flex: 1; 116 order: 2; 117 max-width: 668px; 118 } 119} 120 121.skip-links { 122 position: absolute; 123 top: -100px; 124 left: 0; 125 z-index: 1000; 126} 127 128.skip-link { 129 position: absolute; 130 top: -100px; 131 left: 8px; 132 background: hsl(var(--overlay0)); 133 color: hsl(var(--blue)); 134 padding: 8px 16px; 135 text-decoration: none; 136 border-radius: 4px; 137 font-weight: 600; 138 z-index: 11000; 139 &:focus { 140 top: 128px; 141 width: fit-content; 142 } 143} 144</style>