Skip to content

Commit 22c4891

Browse files
committed
Added release notes support
1 parent 0dce2c4 commit 22c4891

File tree

8 files changed

+742
-21
lines changed

8 files changed

+742
-21
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"lucide-react": "^0.460.0",
3939
"react": "^18.2.0",
4040
"react-dom": "^18.2.0",
41+
"react-markdown": "^10.1.0",
4142
"tailwind-merge": "^2.5.4",
4243
"tailwindcss-animate": "^1.0.7"
4344
},

pnpm-lock.yaml

Lines changed: 673 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/app-sidebar.tsx

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,34 @@
11
import {
2-
Sidebar,
3-
SidebarContent,
4-
SidebarFooter,
5-
SidebarGroup,
6-
SidebarGroupContent,
7-
SidebarHeader,
8-
SidebarMenu,
9-
SidebarMenuButton,
10-
SidebarMenuItem,
11-
useSidebar,
2+
Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar,
123
} from "@/components/ui/sidebar"
13-
import { ChevronDoubleRight, CircleNotch, DownloadSolid, SquareSolid } from "@mynaui/icons-react";
14-
import { LargeP, P, TinyP } from "./typography";
4+
import { ChevronDoubleRight, CircleNotch, DownloadSolid, FileTextSolid, SquareSolid } from "@mynaui/icons-react";
5+
import { LargeP, P, SmallP, TinyP } from "./typography";
156
import { Button } from "./ui/button";
167
import { Link, useRouterState } from "@tanstack/react-router";
178
import { useEffect, useState } from "react";
189
import { app } from "@tauri-apps/api";
19-
import { useSetAtom } from "jotai/react";
10+
import { useAtom, useSetAtom } from "jotai/react";
2011
import { appStateAtom } from "@/lib/store";
2112
import { routes } from "@/lib/routes";
2213
import { invoke } from "@tauri-apps/api/core";
2314
import { logEvent } from "firebase/analytics";
2415
import { getFirebaseAnalytics } from "@/lib/firebase";
16+
import { fetchGithubRelease } from "@/lib/utils";
17+
import DialogUI from "./dialog";
18+
import Markdown from 'react-markdown'
2519

2620
const analytics = getFirebaseAnalytics();
2721

2822
export function AppSidebar() {
2923
const { resolvedLocation } = useRouterState();
30-
const setAppState = useSetAtom(appStateAtom);
24+
const [appState, setAppState] = useAtom(appStateAtom);
3125

3226
const { toggleSidebar, open } = useSidebar();
3327
const [version, setVersion] = useState("");
28+
const [releaseNotes, setReleaseNotes] = useState("");
3429
const [newVersion, setNewVersion] = useState("");
3530
const [isUpdating, setIsUpdating] = useState(false);
31+
const [viewedNotes, setViewedNotes] = useState(true);
3632

3733
useEffect(() => {
3834
app.getVersion().then(v => setVersion(v));
@@ -41,6 +37,7 @@ export function AppSidebar() {
4137

4238
useEffect(() => {
4339
logEvent(analytics, "started_app", { version });
40+
getReleaseNotes(version);
4441
}, [version]);
4542

4643
useEffect(() => {
@@ -72,6 +69,22 @@ export function AppSidebar() {
7269
setIsUpdating(false);
7370
}
7471

72+
const getReleaseNotes = async (tag: string) => {
73+
const release = await fetchGithubRelease(`v${tag}`);
74+
if (release) {
75+
const body = release.body as string;
76+
const notes = body.replace(/^(.|\n|\r)*### Release Notes\r\n/g, "");
77+
setReleaseNotes(notes);
78+
}
79+
}
80+
81+
const handleSetReleaseNotesAsViewed = (isOpen: boolean) => {
82+
if (appState.viewedNotesForVersion === version) return;
83+
setAppState(state => {
84+
state.viewedNotesForVersion = version;
85+
});
86+
}
87+
7588
return (
7689
<Sidebar collapsible="icon">
7790
<SidebarHeader>
@@ -111,8 +124,28 @@ export function AppSidebar() {
111124
</SidebarGroup>
112125
</SidebarContent>
113126
<SidebarFooter className="flex flex-col items-end gap-4">
114-
{newVersion &&
115-
<SidebarMenu>
127+
<SidebarMenu className="flex flex-col gap-2">
128+
{(version && releaseNotes) &&
129+
<SidebarMenuItem className="flex gap-2 justify-center items-center shadow-lg">
130+
<DialogUI
131+
title={`Release Notes (v${version})`}
132+
trigger={
133+
<SidebarMenuButton size="lg" className="!bg-card border">
134+
<FileTextSolid className={`transition-all ml-[0.41rem] ${open && "!size-7 ml-0"}`} />
135+
<div className="flex-1 text-sm leading-tight flex-col gap-1">
136+
<P className="truncate">Release Notes</P>
137+
<TinyP className="truncate">v{version}</TinyP>
138+
</div>
139+
</SidebarMenuButton>
140+
}
141+
defaultOpen={appState.viewedNotesForVersion !== version}
142+
onOpenChange={handleSetReleaseNotesAsViewed}
143+
>
144+
<Markdown>{releaseNotes}</Markdown>
145+
</DialogUI>
146+
</SidebarMenuItem>
147+
}
148+
{newVersion &&
116149
<SidebarMenuItem className="flex gap-2 justify-center items-center shadow-lg shadow-green-600 animate-pulse">
117150
<SidebarMenuButton size="lg" className="!bg-card border border-green-900" onClick={handleInstallUpdate} disabled={isUpdating}>
118151
{isUpdating
@@ -125,8 +158,8 @@ export function AppSidebar() {
125158
</div>
126159
</SidebarMenuButton>
127160
</SidebarMenuItem>
128-
</SidebarMenu>
129-
}
161+
}
162+
</SidebarMenu>
130163
<Button className="size-8" variant="outline" size="icon" onClick={toggleSidebar}>
131164
<ChevronDoubleRight className={`transition-transform ${open ? "rotate-180" : ""}`} />
132165
</Button>

src/components/dialog.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ type DialogUIProps = {
88
description?: string;
99
footer?: ReactNode;
1010
className?: string;
11+
defaultOpen?: boolean;
12+
onOpenChange?: (isOpen: boolean) => void;
1113
}
12-
export default function DialogUI({ children, trigger, title, description, footer, className = "" }: DialogUIProps) {
14+
export default function DialogUI({ children, trigger, title, description, footer, className = "", defaultOpen = false, onOpenChange }: DialogUIProps) {
1315

1416
return (
15-
<Dialog>
17+
<Dialog defaultOpen={defaultOpen} onOpenChange={(isOpen) => onOpenChange && onOpenChange(isOpen)}>
1618
<DialogTrigger asChild>
1719
{trigger}
1820
</DialogTrigger>

src/lib/store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const sourceDownloadOptions = Object.fromEntries(Object.keys(SOURCES).map
1717
export type AppStateT = {
1818
key: string;
1919
version: number;
20+
viewedNotesForVersion?: string;
2021
isSidePanelOpen: boolean;
2122
libraryRootPath: string;
2223
sourceDownloadOptions: {
@@ -26,6 +27,7 @@ export type AppStateT = {
2627
export const appStateAtom = atomWithImmer<AppStateT>({
2728
key: 'appState',
2829
version: 2,
30+
viewedNotesForVersion: undefined,
2931
isSidePanelOpen: true,
3032
libraryRootPath: "",
3133
sourceDownloadOptions: sourceDownloadOptions

src/lib/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,12 @@ export const hashString = (str: string, seed = 0) => {
1919

2020
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16);
2121
};
22+
23+
export const fetchGithubRelease = async (tag: string) => {
24+
try {
25+
const res = await fetch(`https://api.github.com/repos/hanadigital/novelscraper/releases/tags/${tag}`);
26+
return (await res.json()) as { [key: string]: any };
27+
} catch (err) {
28+
console.error(err);
29+
}
30+
}

src/routes/__root.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ function RootComponent() {
9191
if (app.version === 1) {
9292
delete (app as AppStateV1T).downloadBatchSize;
9393
delete (app as AppStateV1T).downloadBatchDelay;
94+
app.viewedNotesForVersion
9495
app.sourceDownloadOptions = sourceDownloadOptions;
9596
app.version = 2;
9697
}

src/routes/settings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ function RouteComponent() {
9797
<TabsList>
9898
{Object.entries(SOURCES).map(([id, v]) => <TabsTrigger key={id} value={id}>{v.name}</TabsTrigger>)}
9999
</TabsList>
100-
{Object.entries(SOURCES).map(([id, v]) => <TabsContent value={id} className="flex flex-col gap-4 mt-4">
100+
{Object.entries(SOURCES).map(([id, v]) => <TabsContent key={id} value={id} className="flex flex-col gap-4 mt-4">
101101
<CounterUI
102102
title="Batch Size"
103103
count={appState.sourceDownloadOptions[id as SourceIDsT].downloadBatchSize}

0 commit comments

Comments
 (0)