Files
outline/app/models/User.ts
2024-03-31 17:28:35 -07:00

214 lines
4.8 KiB
TypeScript

import { subMinutes } from "date-fns";
import { computed, action, observable } from "mobx";
import { now } from "mobx-utils";
import { UserPreferenceDefaults } from "@shared/constants";
import {
NotificationEventDefaults,
NotificationEventType,
TeamPreference,
UserPreference,
UserPreferences,
UserRole,
} from "@shared/types";
import type { NotificationSettings } from "@shared/types";
import { client } from "~/utils/ApiClient";
import Document from "./Document";
import UserMembership from "./UserMembership";
import ParanoidModel from "./base/ParanoidModel";
import Field from "./decorators/Field";
class User extends ParanoidModel {
static modelName = "User";
@Field
@observable
id: string;
@Field
@observable
avatarUrl: string;
@Field
@observable
name: string;
@Field
@observable
color: string;
@Field
@observable
language: string;
@Field
@observable
preferences: UserPreferences | null;
@Field
@observable
notificationSettings: NotificationSettings;
@observable
email: string;
@observable
role: UserRole;
@observable
lastActiveAt: string;
@observable
isSuspended: boolean;
@computed
get initial(): string {
return (this.name ? this.name[0] : "?").toUpperCase();
}
/**
* Whether the user has been invited but not yet signed in.
*/
get isInvited(): boolean {
return !this.lastActiveAt;
}
/**
* Whether the user is an admin.
*/
get isAdmin(): boolean {
return this.role === UserRole.Admin;
}
/**
* Whether the user is a member (editor).
*/
get isMember(): boolean {
return this.role === UserRole.Member;
}
/**
* Whether the user is a viewer.
*/
get isViewer(): boolean {
return this.role === UserRole.Viewer;
}
/**
* Whether the user is a guest.
*/
get isGuest(): boolean {
return this.role === UserRole.Guest;
}
/**
* Whether the user has been recently active. Recently is currently defined
* as within the last 5 minutes.
*
* @returns true if the user has been active recently
*/
@computed
get isRecentlyActive(): boolean {
return new Date(this.lastActiveAt) > subMinutes(now(10000), 5);
}
/**
* Returns whether this user is using a separate editing mode behind an "Edit"
* button rather than seamless always-editing.
*
* @returns True if editing mode is seamless (no button)
*/
@computed
get separateEditMode(): boolean {
return !this.getPreference(
UserPreference.SeamlessEdit,
this.store.rootStore.auth?.team?.getPreference(
TeamPreference.SeamlessEdit
)
);
}
@computed
get memberships(): UserMembership[] {
return this.store.rootStore.userMemberships.orderedData
.filter(
(m) => m.userId === this.id && m.sourceId === null && m.documentId
)
.filter((m) => {
const document = this.store.rootStore.documents.get(m.documentId!);
return !document?.collection;
});
}
/**
* Returns the current preference for the given notification event type taking
* into account the default system value.
*
* @param type The type of notification event
* @returns The current preference
*/
public subscribedToEventType = (type: NotificationEventType) =>
this.notificationSettings[type] ?? NotificationEventDefaults[type] ?? false;
/**
* Sets a preference for the users notification settings on the model and
* saves the change to the server.
*
* @param type The type of notification event
* @param value Set the preference to true/false
*/
@action
setNotificationEventType = async (
eventType: NotificationEventType,
value: boolean
) => {
this.notificationSettings = {
...this.notificationSettings,
[eventType]: value,
};
if (value) {
await client.post(`/users.notificationsSubscribe`, {
eventType,
});
} else {
await client.post(`/users.notificationsUnsubscribe`, {
eventType,
});
}
};
/**
* Get the value for a specific preference key, or return the fallback if
* none is set.
*
* @param key The UserPreference key to retrieve
* @returns The value
*/
getPreference(key: UserPreference, defaultValue = false): boolean {
return (
this.preferences?.[key] ?? UserPreferenceDefaults[key] ?? defaultValue
);
}
/**
* Set the value for a specific preference key.
*
* @param key The UserPreference key to retrieve
* @param value The value to set
*/
setPreference(key: UserPreference, value: boolean) {
this.preferences = {
...this.preferences,
[key]: value,
};
}
getMembership(document: Document) {
return this.store.rootStore.userMemberships.orderedData.find(
(m) => m.documentId === document.id && m.userId === this.id
);
}
}
export default User;