feat: wip

This commit is contained in:
三咲智子 2023-01-03 09:23:16 +08:00
parent 1816530f84
commit f2d125ed4e
No known key found for this signature in database
GPG key ID: 69992F2250DFD93E
14 changed files with 64 additions and 40 deletions

View file

@ -3,7 +3,7 @@ setupPageHeader()
provideGlobalCommands() provideGlobalCommands()
// We want to trigger rerendering the page when account changes // We want to trigger rerendering the page when account changes
const key = computed(() => `${currentUser.value?.server ?? currentServer.value}:${currentUser.value?.account.id || ''}`) const key = computed(() => `${currentServer.value}:${isGuest.value ? '[anonymous]' : currentUser.value!.account!.id || ''}`)
</script> </script>
<template> <template>

View file

@ -41,8 +41,8 @@ onMastoInit(async () => {
// Optimize rendering for the common case of being logged in, only show visual feedback for disabled user-only items // Optimize rendering for the common case of being logged in, only show visual feedback for disabled user-only items
// when we know there is no user. // when we know there is no user.
const noUserDisable = computed(() => !isMastoInitialised.value || (props.userOnly && !currentUser.value)) const noUserDisable = computed(() => !isMastoInitialised.value || (props.userOnly && isGuest.value))
const noUserVisual = computed(() => isMastoInitialised.value && props.userOnly && !currentUser.value) const noUserVisual = computed(() => isMastoInitialised.value && props.userOnly && isGuest.value)
</script> </script>
<template> <template>

View file

@ -2,12 +2,16 @@
<VDropdown v-if="isMastoInitialised && currentUser" sm:hidden> <VDropdown v-if="isMastoInitialised && currentUser" sm:hidden>
<div style="-webkit-touch-callout: none;"> <div style="-webkit-touch-callout: none;">
<AccountAvatar <AccountAvatar
v-if="!currentUser.guest"
ref="avatar" ref="avatar"
:account="currentUser.account" :account="currentUser.account"
h-8 h-8
w-8 w-8
:draggable="false" :draggable="false"
/> />
<div v-else>
TODO: Guest
</div>
</div> </div>
<template #popper="{ hide }"> <template #popper="{ hide }">

View file

@ -1,6 +1,6 @@
<script setup> <script setup>
const disabled = computed(() => !isMastoInitialised.value || !currentUser.value) const disabled = computed(() => !isMastoInitialised.value || isGuest.value)
const disabledVisual = computed(() => isMastoInitialised.value && !currentUser.value) const disabledVisual = computed(() => isMastoInitialised.value && isGuest.value)
</script> </script>
<template> <template>

View file

@ -181,7 +181,7 @@ defineExpose({
</script> </script>
<template> <template>
<div v-if="isMastoInitialised && currentUser" flex="~ col gap-4" py3 px2 sm:px4> <div v-if="isMastoInitialised && currentUser && currentUser.account && !isGuest" flex="~ col gap-4" py3 px2 sm:px4>
<template v-if="draft.editingStatus"> <template v-if="draft.editingStatus">
<div flex="~ col gap-1"> <div flex="~ col gap-1">
<div id="state-editing" text-secondary self-center> <div id="state-editing" text-secondary self-center>

View file

@ -1,8 +1,14 @@
<script setup lang="ts">
// workaround: cannot use TSNonNull `!` in template
const account = $computed(() => currentUser.value!.account!)
</script>
<template> <template>
<VDropdown :distance="0" placement="top-start"> <VDropdown :distance="0" placement="top-start">
<button btn-action-icon :aria-label="$t('action.switch_account')"> <button btn-action-icon :aria-label="$t('action.switch_account')">
<div :class="{ 'hidden xl:block': currentUser }" i-ri:more-2-line /> <div :class="{ 'hidden xl:block': currentUser }" i-ri:more-2-line />
<AccountAvatar v-if="currentUser" xl:hidden :account="currentUser.account" w-9 h-9 /> <AccountAvatar v-if="!isGuest" xl:hidden :account="account" w-9 h-9 />
<span v-else>TODO: Guest</span>
</button> </button>
<template #popper="{ hide }"> <template #popper="{ hide }">
<UserSwitcher @click="hide" /> <UserSwitcher @click="hide" />

View file

@ -6,7 +6,7 @@ const all = useUsers()
const router = useRouter() const router = useRouter()
const masto = useMasto() const masto = useMasto()
const switchUser = (user: UserLogin) => { const switchUser = (user: UserLogin) => {
if (!user.guest && !currentUser.value?.guest && user.account.id === currentUser.value?.account.id) if (!user.guest && !isGuest.value && user.account.id === currentUser.value!.account!.id)
router.push(getAccountRoute(user.account)) router.push(getAccountRoute(user.account))
else else
masto.loginTo(user) masto.loginTo(user)
@ -25,7 +25,8 @@ const switchUser = (user: UserLogin) => {
hover="filter-none op100" hover="filter-none op100"
@click="switchUser(user)" @click="switchUser(user)"
> >
<AccountAvatar w-13 h-13 :account="user.account" /> <AccountAvatar v-if="!user.guest" w-13 h-13 :account="user.account" />
<span v-else>TODO: Guest</span>
</button> </button>
</template> </template>
</div> </div>

View file

@ -8,7 +8,7 @@ export interface PushManagerSubscriptionInfo {
subscription: PushSubscription | null subscription: PushSubscription | null
} }
export interface RequiredUserLogin extends Required<Omit<UserLogin, 'account' | 'pushSubscription'>> { export interface RequiredUserLogin extends Required<Pick<UserLogin, 'server' | 'token' | 'vapidKey'>> {
pushSubscription?: MastoPushSubscription pushSubscription?: MastoPushSubscription
} }

View file

@ -6,6 +6,7 @@ import type {
SubscriptionResult, SubscriptionResult,
} from '~/composables/push-notifications/types' } from '~/composables/push-notifications/types'
import { STORAGE_KEY_NOTIFICATION, STORAGE_KEY_NOTIFICATION_POLICY } from '~/constants' import { STORAGE_KEY_NOTIFICATION, STORAGE_KEY_NOTIFICATION_POLICY } from '~/constants'
import type { UserLogin } from '~/types'
const supportsPushNotifications = typeof window !== 'undefined' const supportsPushNotifications = typeof window !== 'undefined'
&& 'serviceWorker' in navigator && 'serviceWorker' in navigator
@ -68,10 +69,10 @@ export const usePushManager = () => {
if (!isSupported) if (!isSupported)
return 'not-supported' return 'not-supported'
if (!currentUser.value) if (isGuest.value)
return 'no-user' return 'no-user'
const { pushSubscription, server, token, vapidKey, account: { acct } } = currentUser.value const { pushSubscription, server, token, vapidKey, account: { acct } } = currentUser.value as UserLogin<true>
if (!token || !server || !vapidKey) if (!token || !server || !vapidKey)
return 'invalid-vapid-key' return 'invalid-vapid-key'
@ -86,10 +87,8 @@ export const usePushManager = () => {
return 'notification-denied' return 'notification-denied'
} }
currentUser.value.pushSubscription = await createPushSubscription( currentUser.value!.pushSubscription = await createPushSubscription(
{ { pushSubscription, server, token, vapidKey },
pushSubscription, server, token, vapidKey,
},
notificationData ?? { notificationData ?? {
alerts: { alerts: {
follow: true, follow: true,
@ -110,7 +109,7 @@ export const usePushManager = () => {
} }
const unsubscribe = async () => { const unsubscribe = async () => {
if (!isSupported || !isSubscribed || !currentUser.value) if (!isSupported || !isSubscribed || !checkUser(currentUser.value))
return false return false
await removePushNotifications(currentUser.value) await removePushNotifications(currentUser.value)

View file

@ -43,28 +43,30 @@ const initializeUsers = async (): Promise<Ref<UserLogin[]> | RemovableRef<UserLo
const users = await initializeUsers() const users = await initializeUsers()
const instances = useLocalStorage<Record<string, Instance>>(STORAGE_KEY_SERVERS, mock ? mock.server : {}, { deep: true }) const instances = useLocalStorage<Record<string, Instance>>(STORAGE_KEY_SERVERS, mock ? mock.server : {}, { deep: true })
const currentUserId = useLocalStorage<string>(STORAGE_KEY_CURRENT_USER, mock ? mock.user.account.id : '') const currentUserId = useLocalStorage<string>(STORAGE_KEY_CURRENT_USER, mock ? mock.user.account.id : '')
const isGuestId = computed(() => currentUserId.value.startsWith('[anonymous]@'))
export const currentUser = computed<UserLogin | undefined>(() => { export const currentUser = computed<UserLogin | undefined>(() => {
if (!currentUserId.value) if (!currentUserId.value)
// Fallback to the first account // Fallback to the first account
return users.value[0] return users.value[0]
const user = users.value.find(user => user.account?.id === currentUserId.value) if (isGuestId.value) {
if (user) const server = currentUserId.value.replace('[anonymous]@', '')
return user return users.value.find(user => user.guest && user.server === server)
}) }
export const isGuest = computed( return users.value.find(user => user.account?.id === currentUserId.value)
() => currentUserId.value.startsWith('[anonymous]@') || !currentUser.value?.account?.acct, })
)
export const currentServer = computed<string>(() => currentUser.value?.server || DEFAULT_SERVER) export const currentServer = computed<string>(() => currentUser.value?.server || DEFAULT_SERVER)
export const currentInstance = computed<null | Instance>(() => { export const currentInstance = computed<null | Instance>(() => {
return instances.value[currentServer.value] ?? null return instances.value[currentServer.value] ?? null
}) })
export const checkUser = (val: UserLogin | undefined): val is UserLogin<true> => !!(val && !val.guest)
export const isGuest = computed(() => !checkUser(currentUser.value))
export const currentUserHandle = computed(() => export const currentUserHandle = computed(() =>
isGuest.value ? '[anonymous]' : currentUser.value!.account!.acct isGuestId.value ? '[anonymous]' : currentUser.value!.account!.acct
, ,
) )
@ -75,7 +77,7 @@ export const characterLimit = computed(() => currentInstance.value?.configuratio
async function loginTo(user?: UserLogin) { async function loginTo(user?: UserLogin) {
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const server = user?.server || (route.params.server as string) const server = user?.server || (route.params.server as string) || DEFAULT_SERVER
const masto = await loginMasto({ const masto = await loginMasto({
url: `https://${server}`, url: `https://${server}`,
accessToken: user?.token, accessToken: user?.token,
@ -87,9 +89,11 @@ async function loginTo(user?: UserLogin) {
if (!user?.token) { if (!user?.token) {
const instance = await masto.instances.fetch() const instance = await masto.instances.fetch()
currentUserId.value = `[anonymous]@${server}`
instances.value[server] = instance instances.value[server] = instance
if (!users.value.some(u => u.server === server && u.guest))
users.value.push({ server, guest: true }) users.value.push({ server, guest: true })
currentUserId.value = `[anonymous]@${server}`
} }
else { else {
@ -183,7 +187,7 @@ export async function removePushNotificationData(user: UserLogin, fromSWPushMana
} }
} }
export async function removePushNotifications(user: UserLogin) { export async function removePushNotifications(user: UserLogin<true>) {
if (!user.pushSubscription) if (!user.pushSubscription)
return return
@ -213,9 +217,10 @@ export async function signout() {
if (!users.value.some((u, i) => u.server === currentUser.value!.server && i !== index)) if (!users.value.some((u, i) => u.server === currentUser.value!.server && i !== index))
delete instances.value[currentUser.value.server] delete instances.value[currentUser.value.server]
if (checkUser(currentUser.value)) {
await removePushNotifications(currentUser.value) await removePushNotifications(currentUser.value)
await removePushNotificationData(currentUser.value) await removePushNotificationData(currentUser.value)
}
currentUserId.value = '' currentUserId.value = ''
// Remove the current user from the users // Remove the current user from the users
@ -234,7 +239,7 @@ export async function signout() {
const notifications = reactive<Record<string, undefined | [Promise<WsEvents>, number]>>({}) const notifications = reactive<Record<string, undefined | [Promise<WsEvents>, number]>>({})
export const useNotifications = () => { export const useNotifications = () => {
const id = currentUser.value?.account!.id const id = $computed(() => currentUser.value?.account?.id)
const masto = useMasto() const masto = useMasto()
const clearNotifications = () => { const clearNotifications = () => {
@ -292,7 +297,7 @@ export function useUserLocalStorage<T extends object>(key: string, initial: () =
const all = storages.get(key) as Ref<Record<string, T>> const all = storages.get(key) as Ref<Record<string, T>>
return computed(() => { return computed(() => {
const id = isGuest.value const id = isGuestId.value
? '[anonymous]' ? '[anonymous]'
: currentUser.value!.account!.acct : currentUser.value!.account!.acct
all.value[id] = Object.assign(initial(), all.value[id] || {}) all.value[id] = Object.assign(initial(), all.value[id] || {})

View file

@ -5,7 +5,7 @@ export default defineNuxtRouteMiddleware((to) => {
return return
onMastoInit(() => { onMastoInit(() => {
if (!currentUser.value) if (isGuest.value)
return navigateTo(`/${currentServer.value}/public`) return navigateTo(`/${currentServer.value}/public`)
if (to.path === '/') if (to.path === '/')
return navigateTo('/home') return navigateTo('/home')

View file

@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { invoke } from '@vueuse/shared'
const { t } = useI18n() const { t } = useI18n()
const tabs = $computed(() => [ const tabs = $computed(() => [
@ -20,7 +18,7 @@ const tabs = $computed(() => [
{ {
to: `/${currentServer.value}/explore/users`, to: `/${currentServer.value}/explore/users`,
display: t('tab.for_you'), display: t('tab.for_you'),
disabled: !isMastoInitialised.value || !currentUser.value, disabled: !isMastoInitialised.value || isGuest.value,
}, },
] as const) ] as const)
</script> </script>

View file

@ -8,6 +8,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
server: query.server, server: query.server,
token: query.token, token: query.token,
vapidKey: typeof query.vapid_key === 'string' ? query.vapid_key : undefined, vapidKey: typeof query.vapid_key === 'string' ? query.vapid_key : undefined,
guest: false,
} }
: currentUser.value : currentUser.value

View file

@ -12,12 +12,22 @@ export interface AppInfo {
vapid_key: string vapid_key: string
} }
export type UserLogin = { interface UserLoginWithToken {
account: AccountCredentials
guest: false
}
interface UserLoginGuest {
account?: undefined
guest: true
}
export type UserLogin<WithToken extends boolean = boolean> = {
server: string server: string
token?: string token?: string
vapidKey?: string vapidKey?: string
pushSubscription?: PushSubscription pushSubscription?: PushSubscription
} & ({ account: AccountCredentials; guest: false } | { account?: undefined; guest: true }) } & ((WithToken extends false ? UserLoginGuest : never) | (WithToken extends true ? UserLoginWithToken : never))
export interface ElkMasto extends MastoClient { export interface ElkMasto extends MastoClient {
loginTo(user?: UserLogin): Promise<MastoClient> loginTo(user?: UserLogin): Promise<MastoClient>