mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-25 16:40:58 -05:00
feat: add changelog modal
This commit is contained in:
parent
5eabd2861f
commit
cd8ab6739b
11 changed files with 239 additions and 4 deletions
2
external/lang
vendored
2
external/lang
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 309648592801a3bb5c1fa1702753f8dadde56cae
|
Subproject commit 296a3d982c6a596176c9bbbd55020cd6ce760b9a
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.5.3-5",
|
"version": "0.5.3-7",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/setup_assets.js --check && vite",
|
"dev": "node scripts/setup_assets.js --check && vite",
|
||||||
"pull": "node scripts/setup_assets.js",
|
"pull": "node scripts/setup_assets.js",
|
||||||
|
|
38
src/assets/changelogs.ts
Normal file
38
src/assets/changelogs.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
type Element =
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
type: "image";
|
||||||
|
src: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ChangelogPost {
|
||||||
|
date: Date;
|
||||||
|
title: string;
|
||||||
|
content: Element[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const changelogEntries: Record<number, ChangelogPost> = {
|
||||||
|
1: {
|
||||||
|
date: new Date("2022-06-12T20:39:16.674Z"),
|
||||||
|
title: "Secure your account with 2FA",
|
||||||
|
content: [
|
||||||
|
"Two-factor authentication is now available to all users, you can now head over to settings to enable recovery codes and an authenticator app.",
|
||||||
|
{
|
||||||
|
type: "image",
|
||||||
|
src: "https://autumn.revolt.chat/attachments/E21kwmuJGcASgkVLiSIW0wV3ggcaOWjW0TQF7cdFNY/image.png",
|
||||||
|
},
|
||||||
|
"Once enabled, you will be prompted on login.",
|
||||||
|
{
|
||||||
|
type: "image",
|
||||||
|
src: "https://autumn.revolt.chat/attachments/LWRYoKR2tE1ggW_Lzm547P1pnrkNgmBaoCAfWvHE74/image.png",
|
||||||
|
},
|
||||||
|
"Other authentication methods coming later, stay tuned!",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const changelogEntryArray = Object.keys(changelogEntries).map(
|
||||||
|
(index) => changelogEntries[index as unknown as number],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const latestChangelog = changelogEntryArray.length;
|
105
src/context/modals/components/Changelog.tsx
Normal file
105
src/context/modals/components/Changelog.tsx
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import { Text } from "preact-i18n";
|
||||||
|
import { useMemo, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
import { CategoryButton, Column, Modal } from "@revoltchat/ui";
|
||||||
|
import type { Action } from "@revoltchat/ui/esm/components/design/atoms/display/Modal";
|
||||||
|
|
||||||
|
import { noopTrue } from "../../../lib/js";
|
||||||
|
|
||||||
|
import {
|
||||||
|
changelogEntries,
|
||||||
|
changelogEntryArray,
|
||||||
|
ChangelogPost,
|
||||||
|
} from "../../../assets/changelogs";
|
||||||
|
import { ModalProps } from "../types";
|
||||||
|
|
||||||
|
const Image = styled.img`
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
`;
|
||||||
|
|
||||||
|
function RenderLog({ post }: { post: ChangelogPost }) {
|
||||||
|
return (
|
||||||
|
<Column>
|
||||||
|
{post.content.map((entry) =>
|
||||||
|
typeof entry === "string" ? (
|
||||||
|
<span>{entry}</span>
|
||||||
|
) : (
|
||||||
|
<Image src={entry.src} />
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changelog rendering modal
|
||||||
|
*/
|
||||||
|
export default function Changelog({
|
||||||
|
initial,
|
||||||
|
onClose,
|
||||||
|
}: ModalProps<"changelog">) {
|
||||||
|
const [log, setLog] = useState(initial);
|
||||||
|
|
||||||
|
const entry = useMemo(
|
||||||
|
() => (log ? changelogEntries[log] : undefined),
|
||||||
|
[log],
|
||||||
|
);
|
||||||
|
|
||||||
|
const actions = useMemo(() => {
|
||||||
|
const arr: Action[] = [
|
||||||
|
{
|
||||||
|
palette: "primary",
|
||||||
|
children: <Text id="app.special.modals.actions.close" />,
|
||||||
|
onClick: noopTrue,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
arr.push({
|
||||||
|
palette: "plain-secondary",
|
||||||
|
children: <Text id="app.special.modals.changelogs.older" />,
|
||||||
|
onClick: () => {
|
||||||
|
setLog(undefined);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}, [log]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
entry?.title ?? (
|
||||||
|
<Text id="app.special.modals.changelogs.title" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
entry ? (
|
||||||
|
dayjs(entry.date).calendar()
|
||||||
|
) : (
|
||||||
|
<Text id="app.special.modals.changelogs.description" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
actions={actions}
|
||||||
|
onClose={onClose}>
|
||||||
|
{entry ? (
|
||||||
|
<RenderLog post={entry} />
|
||||||
|
) : (
|
||||||
|
<Column>
|
||||||
|
{changelogEntryArray.map((entry, index) => (
|
||||||
|
<CategoryButton
|
||||||
|
key={index}
|
||||||
|
onClick={() => setLog(index + 1)}>
|
||||||
|
{entry.title}
|
||||||
|
</CategoryButton>
|
||||||
|
))}
|
||||||
|
</Column>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import {
|
||||||
import type { Client, API } from "revolt.js";
|
import type { Client, API } from "revolt.js";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
|
|
||||||
|
import Changelog from "./components/Changelog";
|
||||||
import MFAEnableTOTP from "./components/MFAEnableTOTP";
|
import MFAEnableTOTP from "./components/MFAEnableTOTP";
|
||||||
import MFAFlow from "./components/MFAFlow";
|
import MFAFlow from "./components/MFAFlow";
|
||||||
import MFARecovery from "./components/MFARecovery";
|
import MFARecovery from "./components/MFARecovery";
|
||||||
|
@ -118,6 +119,7 @@ class ModalControllerExtended extends ModalController<Modal> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const modalController = new ModalControllerExtended({
|
export const modalController = new ModalControllerExtended({
|
||||||
|
changelog: Changelog,
|
||||||
mfa_flow: MFAFlow,
|
mfa_flow: MFAFlow,
|
||||||
mfa_recovery: MFARecovery,
|
mfa_recovery: MFARecovery,
|
||||||
mfa_enable_totp: MFAEnableTOTP,
|
mfa_enable_totp: MFAEnableTOTP,
|
||||||
|
|
|
@ -28,6 +28,10 @@ export type Modal = {
|
||||||
type: "out_of_date";
|
type: "out_of_date";
|
||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: "changelog";
|
||||||
|
initial?: number;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: "test";
|
type: "test";
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,10 @@ export default function SyncManager() {
|
||||||
// Sync settings from Revolt.
|
// Sync settings from Revolt.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (client) {
|
if (client) {
|
||||||
state.sync.pull(client);
|
state.sync
|
||||||
|
.pull(client)
|
||||||
|
.catch(console.error)
|
||||||
|
.finally(() => state.changelog.checkForUpdates());
|
||||||
}
|
}
|
||||||
}, [client]);
|
}, [client]);
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { legacyMigrateForwards, LegacyState } from "./legacy/redux";
|
||||||
import Persistent from "./interfaces/Persistent";
|
import Persistent from "./interfaces/Persistent";
|
||||||
import Syncable from "./interfaces/Syncable";
|
import Syncable from "./interfaces/Syncable";
|
||||||
import Auth from "./stores/Auth";
|
import Auth from "./stores/Auth";
|
||||||
|
import Changelog from "./stores/Changelog";
|
||||||
import Draft from "./stores/Draft";
|
import Draft from "./stores/Draft";
|
||||||
import Experiments from "./stores/Experiments";
|
import Experiments from "./stores/Experiments";
|
||||||
import Layout from "./stores/Layout";
|
import Layout from "./stores/Layout";
|
||||||
|
@ -32,6 +33,7 @@ export const MIGRATIONS = {
|
||||||
*/
|
*/
|
||||||
export default class State {
|
export default class State {
|
||||||
auth: Auth;
|
auth: Auth;
|
||||||
|
changelog: Changelog;
|
||||||
draft: Draft;
|
draft: Draft;
|
||||||
locale: LocaleOptions;
|
locale: LocaleOptions;
|
||||||
experiments: Experiments;
|
experiments: Experiments;
|
||||||
|
@ -54,6 +56,7 @@ export default class State {
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
this.auth = new Auth();
|
this.auth = new Auth();
|
||||||
|
this.changelog = new Changelog();
|
||||||
this.draft = new Draft();
|
this.draft = new Draft();
|
||||||
this.locale = new LocaleOptions();
|
this.locale = new LocaleOptions();
|
||||||
this.experiments = new Experiments();
|
this.experiments = new Experiments();
|
||||||
|
@ -147,6 +150,7 @@ export default class State {
|
||||||
() => stringify(store.toJSON()),
|
() => stringify(store.toJSON()),
|
||||||
async (value) => {
|
async (value) => {
|
||||||
try {
|
try {
|
||||||
|
console.log(id, "updated!");
|
||||||
// Save updated store to local storage.
|
// Save updated store to local storage.
|
||||||
await localforage.setItem(id, JSON.parse(value));
|
await localforage.setItem(id, JSON.parse(value));
|
||||||
|
|
||||||
|
|
72
src/mobx/stores/Changelog.ts
Normal file
72
src/mobx/stores/Changelog.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { action, makeAutoObservable, runInAction } from "mobx";
|
||||||
|
|
||||||
|
import { modalController } from "../../context/modals";
|
||||||
|
|
||||||
|
import { latestChangelog } from "../../assets/changelogs";
|
||||||
|
import Persistent from "../interfaces/Persistent";
|
||||||
|
import Store from "../interfaces/Store";
|
||||||
|
import Syncable from "../interfaces/Syncable";
|
||||||
|
|
||||||
|
export interface Data {
|
||||||
|
viewed?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps track of viewed changelog items
|
||||||
|
*/
|
||||||
|
export default class Changelog implements Store, Persistent<Data>, Syncable {
|
||||||
|
/**
|
||||||
|
* Last viewed changelog ID
|
||||||
|
*/
|
||||||
|
private viewed: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct new Layout store.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.viewed = 0;
|
||||||
|
makeAutoObservable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return "changelog";
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
viewed: this.viewed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@action hydrate(data: Data) {
|
||||||
|
if (data.viewed) {
|
||||||
|
this.viewed = data.viewed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(_key: string, data: unknown, _revision: number): void {
|
||||||
|
this.hydrate(data as Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
toSyncable(): { [key: string]: object } {
|
||||||
|
return {
|
||||||
|
changelog: this.toJSON(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether there are new updates
|
||||||
|
*/
|
||||||
|
checkForUpdates() {
|
||||||
|
if (this.viewed < latestChangelog) {
|
||||||
|
modalController.push({
|
||||||
|
type: "changelog",
|
||||||
|
initial: latestChangelog,
|
||||||
|
});
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.viewed = latestChangelog;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,8 @@ export type SyncKeys =
|
||||||
| "appearance"
|
| "appearance"
|
||||||
| "locale"
|
| "locale"
|
||||||
| "notifications"
|
| "notifications"
|
||||||
| "ordering";
|
| "ordering"
|
||||||
|
| "changelog";
|
||||||
|
|
||||||
export const SYNC_KEYS: SyncKeys[] = [
|
export const SYNC_KEYS: SyncKeys[] = [
|
||||||
"theme",
|
"theme",
|
||||||
|
@ -27,6 +28,7 @@ export const SYNC_KEYS: SyncKeys[] = [
|
||||||
"locale",
|
"locale",
|
||||||
"notifications",
|
"notifications",
|
||||||
"ordering",
|
"ordering",
|
||||||
|
"changelog",
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface Data {
|
export interface Data {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
Globe,
|
Globe,
|
||||||
LogOut,
|
LogOut,
|
||||||
Desktop,
|
Desktop,
|
||||||
|
ListUl,
|
||||||
} from "@styled-icons/boxicons-regular";
|
} from "@styled-icons/boxicons-regular";
|
||||||
import {
|
import {
|
||||||
Bell,
|
Bell,
|
||||||
|
@ -258,6 +259,10 @@ export default observer(() => {
|
||||||
category="pages"
|
category="pages"
|
||||||
custom={
|
custom={
|
||||||
<>
|
<>
|
||||||
|
<ButtonItem compact>
|
||||||
|
<ListUl size={20} />
|
||||||
|
<Text id="app.settings.pages.changelog" />
|
||||||
|
</ButtonItem>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/revoltchat"
|
href="https://github.com/revoltchat"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
Loading…
Reference in a new issue