wip bsky client for the web & android
bbell.vt3e.cat
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>