Skip to content

Commit 8ff591e

Browse files
committed
Extract route mechanism to composable
1 parent 8d14aba commit 8ff591e

File tree

10 files changed

+131
-111
lines changed

10 files changed

+131
-111
lines changed

src/App.vue

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
<script setup lang="ts">
22
import { watchEffect } from 'vue'
3-
import AppContent from './components/AppContent.vue'
3+
import AppScroller from './components/AppScroller.vue'
44
import AppSidebar from './components/AppSidebar.vue'
55
import HomePage from './pages/HomePage.vue'
66
import SettingsPage from './pages/SettingsPage.vue'
7-
import { ColorPreference, FETCH_INTERVAL_DURATION, Page } from './constants'
7+
import { ColorPreference, FETCH_INTERVAL_DURATION } from './constants'
88
import { useStore } from './stores/store'
99
import LandingPage from './pages/LandingPage.vue'
1010
import { useInterval } from './composables/useInterval'
1111
import { AppStorage } from './storage'
12+
import { Page, useRoute } from './composables/useRoute'
1213
1314
const store = useStore()
15+
const route = useRoute()
1416
1517
useInterval(() => {
1618
if (AppStorage.get('accessToken') && AppStorage.get('user'))
@@ -32,9 +34,9 @@ watchEffect(() => {
3234

3335
<AppSidebar />
3436

35-
<AppContent>
36-
<HomePage v-if="store.currentPage === Page.Home" />
37-
<SettingsPage v-else-if="store.currentPage === Page.Settings" />
38-
<LandingPage v-else-if="store.currentPage === Page.Landing" />
39-
</AppContent>
37+
<AppScroller>
38+
<HomePage v-if="route.currentPage.value === Page.Home" />
39+
<SettingsPage v-else-if="route.currentPage.value === Page.Settings" />
40+
<LandingPage v-else-if="route.currentPage.value === Page.Landing" />
41+
</AppScroller>
4042
</template>

src/components/AppContent.vue renamed to src/components/AppScroller.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,21 @@ import { useStore } from '../stores/store'
88
import type { Option } from '../types'
99
import { batchFn } from '../utils/batch'
1010
import { ColorPreference } from '../constants'
11+
import { useRoute } from '../composables/useRoute'
1112
1213
const store = useStore()
14+
const route = useRoute()
1315
const scrollView = ref<Option<OverlayScrollbarsComponentRef>>(null)
1416
1517
const focus = batchFn(() => {
1618
scrollView.value?.getElement()?.focus()
1719
})
1820
19-
watch(() => store.currentPage, focus, { flush: 'post' })
21+
watch(route.currentPage, focus, { flush: 'post' })
2022
useTauriEvent('window:hidden', focus)
2123
onMounted(focus)
2224
23-
watch(() => store.currentPage, () => {
25+
watch(route.currentPage, () => {
2426
const element = scrollView.value?.osInstance()?.elements().scrollOffsetElement
2527
if (element)
2628
element.scrollTop = 0
@@ -42,6 +44,9 @@ const options = computedEager<OverlayScrollbarsOptions>(() => ({
4244
:options="options"
4345
tabindex="-1"
4446
class="main"
47+
:class="{
48+
'main-with-padding': route.meta.value.withPadding,
49+
}"
4550
>
4651
<slot />
4752
</ScrollView>
@@ -56,5 +61,9 @@ const options = computedEager<OverlayScrollbarsOptions>(() => ({
5661
position: relative;
5762
outline: none;
5863
scroll-padding: 10px 10px;
64+
65+
&-with-padding {
66+
padding: 10px;
67+
}
5968
}
6069
</style>

src/components/AppSidebar.vue

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
import { open } from '@tauri-apps/api/shell'
33
import { exit } from '@tauri-apps/api/process'
44
import { computed } from 'vue'
5-
import { Page, REPO_LINK } from '../constants'
5+
import { REPO_LINK } from '../constants'
66
import { useStore } from '../stores/store'
77
import { AppStorage } from '../storage'
88
import { useKey } from '../composables/useKey'
9+
import { Page, useRoute } from '../composables/useRoute'
910
import { Icons } from './Icons'
1011
import SidebarButton from './SidebarButton.vue'
1112
import Popover from './Popover.vue'
@@ -15,6 +16,7 @@ import SlotRef from './SlotRef.vue'
1516
import PopoverContentInstallUpdate from './PopoverContentInstallUpdate.vue'
1617
1718
const store = useStore()
19+
const route = useRoute()
1820
1921
function openCurrentReleaseChangelog() {
2022
open(`${REPO_LINK}/blob/main/CHANGELOG.md#${__APP_VERSION__.replace(/\./g, '')}`)
@@ -29,7 +31,7 @@ const moreItems = computed(() => [
2931
menuItem({
3032
key: 'settings',
3133
meta: { text: 'Settings', icon: Icons.Gear16 },
32-
onSelect: () => store.setPage(Page.Settings),
34+
onSelect: () => route.go(Page.Settings),
3335
}),
3436
AppStorage.get('user') != null && menuItem({
3537
key: 'logout',
@@ -45,7 +47,7 @@ const moreItems = computed(() => [
4547
4648
useKey('r', () => {
4749
store.fetchNotifications(true)
48-
}, { source: () => store.currentPage === Page.Home })
50+
}, { source: () => route.currentPage.value === Page.Home })
4951
</script>
5052

5153
<template>
@@ -105,7 +107,7 @@ useKey('r', () => {
105107
<SlotRef>
106108
<template #default>
107109
<SidebarButton
108-
:disabled="store.currentPage !== Page.Home"
110+
:disabled="route.currentPage.value !== Page.Home"
109111
@click="store.fetchNotifications(true)"
110112
>
111113
<Icons.Sync16 />

src/composables/useRoute.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { readonly, ref, shallowRef } from 'vue'
2+
import { computedEager, createSharedComposable } from '@vueuse/core'
3+
import { type Option } from '../types'
4+
5+
export enum Page {
6+
Settings = 'Settings',
7+
Home = 'Home',
8+
Landing = 'Landing',
9+
}
10+
11+
export interface PageState {
12+
fetchOnEnter?: boolean
13+
}
14+
15+
export interface PageMeta {
16+
withPadding?: boolean
17+
}
18+
19+
const metaMap = {
20+
[Page.Settings]: {},
21+
[Page.Home]: { withPadding: true },
22+
[Page.Landing]: {},
23+
}
24+
25+
export const useRoute = createSharedComposable(() => {
26+
const currentPage = ref(Page.Landing)
27+
const pageFrom = ref<Option<Page>>(null)
28+
const state = shallowRef<PageState>({})
29+
30+
function go(page: Page, pageState: PageState = {}) {
31+
if (page === currentPage.value)
32+
return
33+
34+
pageFrom.value = currentPage.value
35+
currentPage.value = page
36+
state.value = pageState
37+
}
38+
39+
const meta = computedEager<PageMeta>(() => metaMap[currentPage.value])
40+
41+
return {
42+
go,
43+
currentPage: readonly(currentPage),
44+
pageFrom: readonly(pageFrom),
45+
state: readonly(state),
46+
meta,
47+
}
48+
})

src/constants.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,6 @@ export const REPO_LINK = `https://github.com/${REPOSITORY_PATH}` as const
2626
export const FETCH_INTERVAL_DURATION = 60000
2727
export const SERVER_PORTS = [23846, 15830, 12840]
2828

29-
export enum Page {
30-
Settings = 'Settings',
31-
Home = 'Home',
32-
Landing = 'Landing',
33-
}
34-
3529
export enum InvokeCommand {
3630
PlayNotificationSound = 'play_notification_sound',
3731
SetIconTemplate = 'set_icon_template',

src/main.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import { checkUpdate } from '@tauri-apps/api/updater'
1414
import App from './App.vue'
1515
import { AppStorage, cacheStorageFromDisk } from './storage'
1616
import { useStore } from './stores/store'
17-
import { Page } from './constants'
1817
import { initDevtools } from './utils/initDevtools'
1918
import { useKey } from './composables/useKey'
19+
import { Page, useRoute } from './composables/useRoute'
2020

2121
async function main() {
2222
if (import.meta.env.DEV) {
@@ -35,6 +35,7 @@ async function main() {
3535
await cacheStorageFromDisk()
3636

3737
const store = useStore(pinia)
38+
const route = useRoute()
3839
const token = AppStorage.get('accessToken')
3940
const user = AppStorage.get('user')
4041

@@ -46,7 +47,7 @@ async function main() {
4647
AppStorage.set('showSystemNotifications', false)
4748

4849
if (token && user) {
49-
store.setPage(Page.Home)
50+
route.go(Page.Home)
5051
store.fetchNotifications(true)
5152
}
5253

src/pages/HomePage.vue

Lines changed: 38 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import MenuItems, { menuItem } from '../components/MenuItems.vue'
2020
import { type UseKeyOptions, useKey } from '../composables/useKey'
2121
import { CheckedNotificationProcess, notificationApiMutex } from '../constants'
2222
import { everySome } from '../utils/array'
23+
import { useRoute } from '../composables/useRoute'
2324
2425
const store = useStore()
26+
const route = useRoute()
2527
26-
if (store.currentPageState.fetchOnEnter)
28+
if (route.state.value.fetchOnEnter)
2729
store.fetchNotifications(true)
2830
2931
function handleOpenNotification(thread: Thread) {
@@ -318,7 +320,6 @@ whenever(() => store.skeletonVisible, () => {
318320
:wowerlayOptions="{ position: 'right-start' }"
319321
@visibilityChange="(visible) => {
320322
popoverVisible = visible;
321-
({}).constructor.constructor('return console.log')()({ visible })
322323
323324
if (!visible) {
324325
popoverTarget = null
@@ -328,64 +329,39 @@ whenever(() => store.skeletonVisible, () => {
328329
<MenuItems :items="contextMenuItems" />
329330
</Popover>
330331

331-
<div class="home-wrapper">
332-
<div
333-
ref="home"
334-
class="home"
335-
>
336-
<NotificationSkeleton v-if="store.skeletonVisible" />
337-
338-
<EmptyState
339-
v-else-if="store.failedLoadingNotifications"
340-
:iconSize="EmptyStateIconSize.Big"
341-
:icon="Icons.X"
342-
description="Oopsie! Couldn't load notifications."
343-
>
344-
<template #footer>
345-
<AppButton @click="store.fetchNotifications(true)">
346-
Refresh
347-
</AppButton>
348-
</template>
349-
</EmptyState>
350-
351-
<EmptyState
352-
v-else-if="store.notifications.length === 0"
353-
:iconSize="EmptyStateIconSize.Big"
354-
:icon="Icons.Check"
355-
description="It's all clear sir!"
356-
/>
357-
358-
<NotificationItem
359-
v-for="item of store.notifications"
360-
:key="item.id"
361-
:value="item"
362-
:checked="isChecked(item)"
363-
:checkable="isCheckable(item)"
364-
:indeterminate="isIndeterminate(item)"
365-
:checkboxVisible="store.checkedItems.length > 0"
366-
@contextmenu="handleThreadContextmenu"
367-
@click:notification="handleClickNotification"
368-
@click:repo="handleRepoClick"
369-
@update:checked="(value) => handleUpdateChecked(item, value)"
370-
/>
371-
</div>
372-
</div>
373-
</template>
332+
<NotificationSkeleton v-if="store.skeletonVisible" />
374333

375-
<style lang="scss" scoped>
376-
.home {
377-
padding: 10px;
378-
width: 100%;
379-
height: 100%;
380-
scroll-padding-top: 10px;
381-
scroll-padding-bottom: 10px;
382-
position: relative;
383-
384-
&-wrapper {
385-
display: flex;
386-
flex-flow: column nowrap;
387-
height: 100%;
388-
width: 100%;
389-
}
390-
}
391-
</style>
334+
<EmptyState
335+
v-else-if="store.failedLoadingNotifications"
336+
:iconSize="EmptyStateIconSize.Big"
337+
:icon="Icons.X"
338+
description="Oopsie! Couldn't load notifications."
339+
>
340+
<template #footer>
341+
<AppButton @click="store.fetchNotifications(true)">
342+
Refresh
343+
</AppButton>
344+
</template>
345+
</EmptyState>
346+
347+
<EmptyState
348+
v-else-if="store.notifications.length === 0"
349+
:iconSize="EmptyStateIconSize.Big"
350+
:icon="Icons.Check"
351+
description="It's all clear sir!"
352+
/>
353+
354+
<NotificationItem
355+
v-for="item of store.notifications"
356+
:key="item.id"
357+
:value="item"
358+
:checked="isChecked(item)"
359+
:checkable="isCheckable(item)"
360+
:indeterminate="isIndeterminate(item)"
361+
:checkboxVisible="store.checkedItems.length > 0"
362+
@contextmenu="handleThreadContextmenu"
363+
@click:notification="handleClickNotification"
364+
@click:repo="handleRepoClick"
365+
@update:checked="(value) => handleUpdateChecked(item, value)"
366+
/>
367+
</template>

src/pages/LandingPage.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { open } from '@tauri-apps/api/shell'
33
import { ref } from 'vue'
44
import { invoke } from '@tauri-apps/api/tauri'
55
import AppButton from '../components/AppButton.vue'
6-
import { InvokeCommand, Page } from '../constants'
6+
import { InvokeCommand } from '../constants'
77
import { useStore } from '../stores/store'
88
import { useTauriEvent } from '../composables/useTauriEvent'
99
import { getAccessToken } from '../api/token'
@@ -13,8 +13,10 @@ import EmptyState from '../components/EmptyState.vue'
1313
import { createAuthURL } from '../utils/github'
1414
import { useTimeoutPool } from '../composables/useTimeoutPool'
1515
import { getServerPort } from '../api/app'
16+
import { Page, useRoute } from '../composables/useRoute'
1617
1718
const store = useStore()
19+
const route = useRoute()
1820
const processing = ref(true)
1921
2022
useTauriEvent<string>('code', async ({ payload }) => {
@@ -35,7 +37,7 @@ useTauriEvent<string>('code', async ({ payload }) => {
3537
AppStorage.set('accessToken', access_token)
3638
AppStorage.set('user', user)
3739
invoke(InvokeCommand.StopServer)
38-
store.setPage(Page.Home)
40+
route.go(Page.Home)
3941
store.fetchNotifications(true)
4042
}
4143
finally {

0 commit comments

Comments
 (0)