mirror of
https://github.com/versia-pub/frontend.git
synced 2026-03-13 03:29:16 +01:00
refactor: ♻️ Rewrite state system to use Pinia for composer and auth
This commit is contained in:
parent
a6db9e059d
commit
b510782a30
80 changed files with 999 additions and 1011 deletions
|
|
@ -1,9 +1,7 @@
|
|||
import type { Client } from "@versia/client";
|
||||
import type { Account } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const useAccountFromAcct = (
|
||||
client: MaybeRef<Client | null>,
|
||||
acct: string,
|
||||
): {
|
||||
account: Ref<z.infer<typeof Account> | null>;
|
||||
|
|
@ -11,13 +9,12 @@ export const useAccountFromAcct = (
|
|||
} => {
|
||||
const output = ref(null as z.infer<typeof Account> | null);
|
||||
const isLoading = ref(true);
|
||||
const authStore = useAuthStore();
|
||||
|
||||
ref(client)
|
||||
.value?.lookupAccount(acct)
|
||||
.then((res) => {
|
||||
isLoading.value = false;
|
||||
output.value = res.data;
|
||||
});
|
||||
authStore.client.lookupAccount(acct).then((res) => {
|
||||
isLoading.value = false;
|
||||
output.value = res.data;
|
||||
});
|
||||
|
||||
return { account: output, isLoading };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import type { Client } from "@versia/client";
|
||||
import type { Status } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
import { type TimelineOptions, useTimeline } from "./Timeline";
|
||||
|
||||
export function useAccountTimeline(
|
||||
client: Client,
|
||||
accountId: string,
|
||||
options: Partial<TimelineOptions<z.infer<typeof Status>>> = {},
|
||||
) {
|
||||
return useTimeline(client, {
|
||||
fetchFunction: (client, opts) =>
|
||||
client.getAccountStatuses(accountId, opts),
|
||||
const authStore = useAuthStore();
|
||||
|
||||
return useTimeline({
|
||||
fetchFunction: (opts) =>
|
||||
authStore.client.getAccountStatuses(accountId, opts),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
import type { CredentialApplication } from "@versia/client/schemas";
|
||||
import { StorageSerializers } from "@vueuse/core";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const useAppData = () => {
|
||||
return useLocalStorage<z.infer<typeof CredentialApplication> | null>(
|
||||
"versia:app_data",
|
||||
null,
|
||||
{
|
||||
serializer: StorageSerializers.object,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
|
@ -1,66 +1,47 @@
|
|||
import type { Client } from "@versia/client";
|
||||
import type { RolePermission } from "@versia/client/schemas";
|
||||
import { toast } from "vue-sonner";
|
||||
import * as m from "~~/paraglide/messages.js";
|
||||
|
||||
export const useCacheRefresh = (client: MaybeRef<Client | null>) => {
|
||||
export const useCacheRefresh = () => {
|
||||
const authStore = useAuthStore();
|
||||
const { identity } = storeToRefs(authStore);
|
||||
|
||||
authStore.client.getInstance().then((res) => {
|
||||
authStore.updateActiveIdentity({
|
||||
instance: res.data,
|
||||
});
|
||||
});
|
||||
|
||||
// Refresh custom emojis and instance data and me on every reload
|
||||
watch(
|
||||
[identity, client],
|
||||
async () => {
|
||||
console.info("Refreshing emoji, instance and account cache");
|
||||
if (identity.value) {
|
||||
toValue(client)
|
||||
?.verifyAccountCredentials()
|
||||
identity,
|
||||
async (oldIdentity, newIdentity) => {
|
||||
if (newIdentity && newIdentity.id !== oldIdentity?.id) {
|
||||
console.info("Refreshing emoji, instance and account cache");
|
||||
authStore.client
|
||||
.verifyAccountCredentials()
|
||||
.then((res) => {
|
||||
if (identity.value) {
|
||||
identity.value.account = res.data;
|
||||
}
|
||||
authStore.updateActiveIdentity({
|
||||
account: res.data,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
const code = err.response.status;
|
||||
|
||||
if (code === 401) {
|
||||
// Reset tokenData
|
||||
identity.value = null;
|
||||
authStore.setActiveIdentity(null);
|
||||
toast.error(m.fancy_this_wasp_renew(), {
|
||||
description: m.real_weird_deer_stop(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
toValue(client)
|
||||
?.getInstanceCustomEmojis()
|
||||
.then((res) => {
|
||||
if (identity.value) {
|
||||
identity.value.emojis = res.data;
|
||||
}
|
||||
authStore.client.getInstanceCustomEmojis().then((res) => {
|
||||
authStore.updateActiveIdentity({
|
||||
emojis: res.data,
|
||||
});
|
||||
|
||||
toValue(client)
|
||||
?.getAccountRoles(identity.value.account.id)
|
||||
.then((res) => {
|
||||
const roles = res.data;
|
||||
|
||||
// Get all permissions and deduplicate
|
||||
const permissions = roles
|
||||
?.flatMap((r) => r.permissions)
|
||||
.filter((p, i, arr) => arr.indexOf(p) === i);
|
||||
|
||||
if (identity.value) {
|
||||
identity.value.permissions =
|
||||
permissions as unknown as RolePermission[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toValue(client)
|
||||
?.getInstance()
|
||||
.then((res) => {
|
||||
if (identity.value) {
|
||||
identity.value.instance = res.data;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{ flush: "sync", immediate: true },
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
import { Client } from "@versia/client";
|
||||
import type { Token } from "@versia/client/schemas";
|
||||
import { toast } from "vue-sonner";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const useClient = (
|
||||
origin?: MaybeRef<URL>,
|
||||
customToken: MaybeRef<z.infer<typeof Token> | null> = null,
|
||||
): Ref<Client> => {
|
||||
const apiHost = window.location.origin;
|
||||
const domain = identity.value?.instance.domain;
|
||||
|
||||
return ref(
|
||||
new Client(
|
||||
toValue(origin) ??
|
||||
(domain ? new URL(`https://${domain}`) : new URL(apiHost)),
|
||||
toValue(customToken)?.access_token ??
|
||||
identity.value?.tokens.access_token ??
|
||||
undefined,
|
||||
{
|
||||
globalCatch: (error) => {
|
||||
toast.error(
|
||||
error.response.data.error ??
|
||||
"No error message provided",
|
||||
);
|
||||
},
|
||||
throwOnError: false,
|
||||
},
|
||||
),
|
||||
) as Ref<Client>;
|
||||
};
|
||||
|
||||
export const client = useClient();
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import type { Account, Attachment, Status } from "@versia/client/schemas";
|
||||
import mitt from "mitt";
|
||||
import type { z } from "zod";
|
||||
import type { Identity } from "./Identities";
|
||||
|
||||
type ApplicationEvents = {
|
||||
"note:reply": z.infer<typeof Status>;
|
||||
|
|
@ -23,7 +22,6 @@ type ApplicationEvents = {
|
|||
"account:report": z.infer<typeof Account>;
|
||||
"account:update": z.infer<typeof Account>;
|
||||
"attachment:view": z.infer<typeof Attachment>;
|
||||
"identity:change": Identity;
|
||||
"preferences:open": undefined;
|
||||
error: {
|
||||
code: string;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,14 @@
|
|||
import type { Client } from "@versia/client";
|
||||
import type { ExtendedDescription } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const useExtendedDescription = (client: MaybeRef<Client | null>) => {
|
||||
if (!ref(client).value) {
|
||||
return ref(null as z.infer<typeof ExtendedDescription> | null);
|
||||
}
|
||||
export const useExtendedDescription = () => {
|
||||
const store = useAuthStore();
|
||||
|
||||
const output = ref(null as z.infer<typeof ExtendedDescription> | null);
|
||||
|
||||
ref(client)
|
||||
.value?.getInstanceExtendedDescription()
|
||||
.then((res) => {
|
||||
output.value = res.data;
|
||||
});
|
||||
store.client.getInstanceExtendedDescription().then((res) => {
|
||||
output.value = res.data;
|
||||
});
|
||||
|
||||
return output;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import type { Client } from "@versia/client";
|
||||
import type { Status } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
import { type TimelineOptions, useTimeline } from "./Timeline";
|
||||
|
||||
export function useGlobalTimeline(
|
||||
client: Client,
|
||||
options: Partial<TimelineOptions<z.infer<typeof Status>>> = {},
|
||||
) {
|
||||
return useTimeline(client, {
|
||||
const authStore = useAuthStore();
|
||||
|
||||
return useTimeline({
|
||||
// TODO: Implement global timeline in client sdk
|
||||
fetchFunction: (client, opts) => client.getPublicTimeline(opts),
|
||||
fetchFunction: (opts) => authStore.client.getPublicTimeline(opts),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import type { Client } from "@versia/client";
|
||||
import type { Status } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
import { type TimelineOptions, useTimeline } from "./Timeline";
|
||||
|
||||
export function useHomeTimeline(
|
||||
client: Client,
|
||||
options: Partial<TimelineOptions<z.infer<typeof Status>>> = {},
|
||||
) {
|
||||
return useTimeline(client, {
|
||||
fetchFunction: (client, opts) => client.getHomeTimeline(opts),
|
||||
const authStore = useAuthStore();
|
||||
|
||||
return useTimeline({
|
||||
fetchFunction: (opts) => authStore.client.getHomeTimeline(opts),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,104 +0,0 @@
|
|||
import type {
|
||||
Account,
|
||||
CustomEmoji,
|
||||
Instance,
|
||||
RolePermission,
|
||||
Token,
|
||||
} from "@versia/client/schemas";
|
||||
import { StorageSerializers, useLocalStorage } from "@vueuse/core";
|
||||
import { ref, watch } from "vue";
|
||||
import type { z } from "zod";
|
||||
|
||||
/**
|
||||
* Represents an identity with associated tokens, account, instance, permissions, and emojis.
|
||||
*/
|
||||
export interface Identity {
|
||||
id: string;
|
||||
tokens: z.infer<typeof Token>;
|
||||
account: z.infer<typeof Account>;
|
||||
instance: z.infer<typeof Instance>;
|
||||
permissions: RolePermission[];
|
||||
emojis: z.infer<typeof CustomEmoji>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable to manage multiple identities.
|
||||
* @returns A reactive reference to an array of identities.
|
||||
*/
|
||||
function useIdentities(): Ref<Identity[]> {
|
||||
return useLocalStorage<Identity[]>("versia:identities", [], {
|
||||
serializer: StorageSerializers.object,
|
||||
});
|
||||
}
|
||||
|
||||
export const identities = useIdentities();
|
||||
|
||||
const currentId = useLocalStorage<string | null>(
|
||||
"versia:identities:current",
|
||||
null,
|
||||
);
|
||||
|
||||
const current = ref<Identity | null>(null);
|
||||
|
||||
/**
|
||||
* Composable to manage the current identity.
|
||||
* @returns A reactive reference to the current identity or null if not set.
|
||||
*/
|
||||
function useCurrentIdentity(): Ref<Identity | null> {
|
||||
// Initialize current identity
|
||||
function updateCurrentIdentity() {
|
||||
current.value =
|
||||
identities.value.find((i) => i.id === currentId.value) ?? null;
|
||||
}
|
||||
|
||||
// Watch for changes in identities
|
||||
watch(
|
||||
identities,
|
||||
(ids) => {
|
||||
if (ids.length === 0) {
|
||||
current.value = null;
|
||||
currentId.value = null;
|
||||
} else {
|
||||
updateCurrentIdentity();
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// Watch for changes in currentId
|
||||
watch(currentId, updateCurrentIdentity);
|
||||
|
||||
// Watch for changes in current identity
|
||||
watch(
|
||||
current,
|
||||
(newCurrent) => {
|
||||
if (newCurrent) {
|
||||
currentId.value = newCurrent.id;
|
||||
const index = identities.value.findIndex(
|
||||
(i) => i.id === newCurrent.id,
|
||||
);
|
||||
if (index !== -1) {
|
||||
// Update existing identity
|
||||
identities.value[index] = newCurrent;
|
||||
} else {
|
||||
// Add new identity
|
||||
identities.value.push(newCurrent);
|
||||
}
|
||||
} else {
|
||||
// Remove current identity
|
||||
identities.value = identities.value.filter(
|
||||
(i) => i.id !== currentId.value,
|
||||
);
|
||||
currentId.value = identities.value[0]?.id ?? null;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// Initial setup
|
||||
updateCurrentIdentity();
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
export const identity = useCurrentIdentity();
|
||||
|
|
@ -2,10 +2,6 @@ import type { Client } from "@versia/client";
|
|||
import type { Instance, TermsOfService } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const useInstance = () => {
|
||||
return computed(() => identity.value?.instance);
|
||||
};
|
||||
|
||||
export const useInstanceFromClient = (client: MaybeRef<Client>) => {
|
||||
if (!client) {
|
||||
return ref(null as z.infer<typeof Instance> | null);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import type { Client } from "@versia/client";
|
||||
import type { Status } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
import { type TimelineOptions, useTimeline } from "./Timeline";
|
||||
|
||||
export function useLocalTimeline(
|
||||
client: Client,
|
||||
options: Partial<TimelineOptions<z.infer<typeof Status>>> = {},
|
||||
) {
|
||||
return useTimeline(client, {
|
||||
fetchFunction: (client, opts) => client.getLocalTimeline(opts),
|
||||
const authStore = useAuthStore();
|
||||
|
||||
return useTimeline({
|
||||
fetchFunction: (opts) => authStore.client.getLocalTimeline(opts),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
import type { Client } from "@versia/client";
|
||||
import type { Status } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const useNote = (
|
||||
client: MaybeRef<Client | null>,
|
||||
noteId: MaybeRef<string | null>,
|
||||
) => {
|
||||
if (!(toValue(client) && toValue(noteId))) {
|
||||
export const useNote = (noteId: MaybeRef<string | null>) => {
|
||||
if (!toValue(noteId)) {
|
||||
return ref(null as z.infer<typeof Status> | null);
|
||||
}
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const output = ref(null as z.infer<typeof Status> | null);
|
||||
|
||||
watchEffect(() => {
|
||||
toValue(noteId) &&
|
||||
toValue(client)
|
||||
?.getStatus(toValue(noteId) as string)
|
||||
authStore.client
|
||||
.getStatus(toValue(noteId) as string)
|
||||
.then((res) => {
|
||||
output.value = res.data;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,21 +1,15 @@
|
|||
import type { Client } from "@versia/client";
|
||||
import type { Context } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const useNoteContext = (
|
||||
client: MaybeRef<Client | null>,
|
||||
noteId: MaybeRef<string | null>,
|
||||
) => {
|
||||
if (!ref(client).value) {
|
||||
return ref(null as z.infer<typeof Context> | null);
|
||||
}
|
||||
export const useNoteContext = (noteId: MaybeRef<string | null>) => {
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const output = ref(null as z.infer<typeof Context> | null);
|
||||
|
||||
watchEffect(() => {
|
||||
if (toValue(noteId)) {
|
||||
ref(client)
|
||||
.value?.getStatusContext(toValue(noteId) ?? "")
|
||||
authStore.client
|
||||
.getStatusContext(toValue(noteId) ?? "")
|
||||
.then((res) => {
|
||||
output.value = res.data;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ import type { z } from "zod";
|
|||
import { type TimelineOptions, useTimeline } from "./Timeline";
|
||||
|
||||
export function useNotificationTimeline(
|
||||
client: Client,
|
||||
options: Partial<TimelineOptions<z.infer<typeof Notification>>> = {},
|
||||
) {
|
||||
return useTimeline(client, {
|
||||
fetchFunction: (client, opts) => client.getNotifications(opts),
|
||||
const authStore = useAuthStore();
|
||||
|
||||
return useTimeline({
|
||||
fetchFunction: (opts) => authStore.client.getNotifications(opts),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
export const usePermissions = () => {
|
||||
return computed(() => identity.value?.permissions ?? []);
|
||||
};
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
import type { Client } from "@versia/client";
|
||||
import type { Status } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
import { type TimelineOptions, useTimeline } from "./Timeline";
|
||||
|
||||
export function usePublicTimeline(
|
||||
client: Client,
|
||||
options: Partial<TimelineOptions<z.infer<typeof Status>>> = {},
|
||||
) {
|
||||
return useTimeline(client, {
|
||||
fetchFunction: (client, opts) => client.getPublicTimeline(opts),
|
||||
const authStore = useAuthStore();
|
||||
|
||||
return useTimeline({
|
||||
fetchFunction: (opts) => authStore.client.getPublicTimeline(opts),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,19 @@
|
|||
import type { Client } from "@versia/client";
|
||||
import type { Relationship } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const useRelationship = (
|
||||
client: MaybeRef<Client | null>,
|
||||
accountId: MaybeRef<string | null>,
|
||||
) => {
|
||||
export const useRelationship = (accountId: MaybeRef<string | null>) => {
|
||||
const relationship = ref(null as z.infer<typeof Relationship> | null);
|
||||
const isLoading = ref(false);
|
||||
const authStore = useAuthStore();
|
||||
|
||||
if (!identity.value) {
|
||||
if (!authStore.isSignedIn) {
|
||||
return { relationship, isLoading };
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (toValue(accountId)) {
|
||||
toValue(client)
|
||||
?.getRelationship(toValue(accountId) ?? "")
|
||||
authStore.client
|
||||
.getRelationship(toValue(accountId) ?? "")
|
||||
.then((res) => {
|
||||
relationship.value = res.data;
|
||||
});
|
||||
|
|
@ -28,14 +25,14 @@ export const useRelationship = (
|
|||
if (newOutput?.following !== oldOutput?.following) {
|
||||
isLoading.value = true;
|
||||
if (newOutput?.following) {
|
||||
toValue(client)
|
||||
?.followAccount(toValue(accountId) ?? "")
|
||||
authStore.client
|
||||
.followAccount(toValue(accountId) ?? "")
|
||||
.finally(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
} else {
|
||||
toValue(client)
|
||||
?.unfollowAccount(toValue(accountId) ?? "")
|
||||
authStore.client
|
||||
.unfollowAccount(toValue(accountId) ?? "")
|
||||
.finally(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
import type { Instance } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const useSSOConfig = (): Ref<z.infer<
|
||||
typeof Instance.shape.sso
|
||||
> | null> => {
|
||||
const instance = useInstance();
|
||||
|
||||
return computed(() => instance.value?.sso || null);
|
||||
};
|
||||
|
|
@ -4,18 +4,20 @@ import { useIntervalFn } from "@vueuse/core";
|
|||
import type { z } from "zod";
|
||||
|
||||
export interface TimelineOptions<T> {
|
||||
fetchFunction: (client: Client, options: object) => Promise<Output<T[]>>;
|
||||
fetchFunction: (options: object) => Promise<Output<T[]>>;
|
||||
updateInterval?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export function useTimeline<
|
||||
T extends z.infer<typeof Status> | z.infer<typeof Notification>,
|
||||
>(client: Client, options: TimelineOptions<T>) {
|
||||
>(options: TimelineOptions<T>) {
|
||||
const items = ref<T[]>([]) as Ref<T[]>;
|
||||
const isLoading = ref(false);
|
||||
const hasReachedEnd = ref(false);
|
||||
const error = ref<Error | null>(null);
|
||||
const authStore = useAuthStore();
|
||||
const { identity } = storeToRefs(authStore);
|
||||
|
||||
const nextMaxId = ref<string | undefined>(undefined);
|
||||
const prevMinId = ref<string | undefined>(undefined);
|
||||
|
|
@ -29,7 +31,7 @@ export function useTimeline<
|
|||
error.value = null;
|
||||
|
||||
try {
|
||||
const response = await options.fetchFunction(client, {
|
||||
const response = await options.fetchFunction({
|
||||
limit: options.limit || 20,
|
||||
max_id: direction === "next" ? nextMaxId.value : undefined,
|
||||
min_id: direction === "prev" ? prevMinId.value : undefined,
|
||||
|
|
@ -99,6 +101,17 @@ export function useTimeline<
|
|||
pause();
|
||||
});
|
||||
|
||||
watch(identity, (newIdentity, oldIdentity) => {
|
||||
if (newIdentity?.id !== oldIdentity?.id) {
|
||||
// Reload timeline when identity changes
|
||||
items.value = [];
|
||||
nextMaxId.value = undefined;
|
||||
prevMinId.value = undefined;
|
||||
hasReachedEnd.value = false;
|
||||
error.value = null;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
items,
|
||||
isLoading,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue