Skip to content

Commit cf888a2

Browse files
committed
feat: updated settings dialog, settings api fix
1 parent e02745e commit cf888a2

File tree

15 files changed

+219
-185
lines changed

15 files changed

+219
-185
lines changed

src/main/trpc/settings.api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import config from "@shared/config";
66
import { TRPCError } from "@trpc/server";
77
import { observable } from "@trpc/server/observable";
88
import { app, dialog } from "electron";
9+
import { merge } from "lodash-es";
910
import { z } from "zod";
1011
import { publicProcedure, router } from "./trpc";
1112
const settingsChangeEmitter = new EventEmitter();
@@ -59,7 +60,7 @@ export const settingsRouter = router({
5960
return { key, value };
6061
}),
6162
updateMany: publicProcedure.input(z.record(z.any())).mutation(async ({ ctx, input: settings }) => {
62-
appStore.set(settings);
63+
appStore.set(merge(appStore.store, settings));
6364
Object.keys(settings).forEach((key) => {
6465
settingsChangeEmitter.emit(handleKey, { key });
6566
});

src/main/trpc/ytdlp.utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Logger } from "@shared/logger";
99
import { resulter } from "@shared/promises/helper";
1010
import { nt as calvNewerThan } from "calver";
1111
import { app } from "electron";
12+
import { stat, unlink } from "fs/promises";
1213
import { VideoInfo } from "yt-dlp-wrap/types";
1314
import { pushLogToClient } from "./events.ee";
1415
const YTDLP_PLATFORM = platform();
@@ -95,6 +96,9 @@ export class YTDLP {
9596
}
9697
private async downloadUpdate(release: GithubRelease & { version: string }): Promise<{ version: string; path: string }> {
9798
const newYtdlPath = path.join(ytdlpPath, appPlatform.isWindows ? "ytdlp.exe" : "ytdlp");
99+
if (await stat(newYtdlPath).then((stats) => stats.isFile())) {
100+
await unlink(newYtdlPath);
101+
}
98102
await YTDLWrapper.downloadFromGithub(newYtdlPath, release.version, YTDLP_PLATFORM);
99103
return { version: release.version, path: newYtdlPath };
100104
}

src/renderer/src/components/ui/button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const buttonVariants = cva(
1212
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90 [&.active]:bg-primary",
1313
accent: "bg-accent text-accent-foreground shadow hover:bg-accent/90 [&.active]:bg-accent",
1414
destructive: "bg-destructive text-destructive-foreground shadow-sm ring-destructive/40 hover:bg-destructive/90 focus:bg-destructive/90",
15-
outline: "border border-secondary-foreground/10 bg-background/20 ring-secondary/40 hover:bg-backround/30 hover:text-secondary-foreground",
15+
outline: "border border-secondary-foreground/10 bg-background/20 ring-secondary/40 hover:bg-secondary/50 hover:text-secondary-foreground focus:bg-secondary/50",
1616
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
1717
ghost: "hover:bg-primary/5 hover:text-secondary-foreground ring-secondary/30 [&.active]:text-secondary-foreground [&.active]:bg-primary/10",
1818
link: "text-primary underline-offset-4 hover:underline",
Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,86 @@
11
"use client";
22

33
import * as DialogPrimitive from "@radix-ui/react-dialog";
4-
import { Cross2Icon } from "@radix-ui/react-icons";
4+
import { XIcon } from "lucide-react";
55
import * as React from "react";
66

7-
import { cn } from "@renderer/lib/utils";
7+
import { cn } from "@/lib/utils";
88

9-
const Dialog = DialogPrimitive.Root;
9+
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
10+
return <DialogPrimitive.Root data-slot='dialog' {...props} />;
11+
}
1012

11-
const DialogTrigger = DialogPrimitive.Trigger;
13+
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
14+
return <DialogPrimitive.Trigger data-slot='dialog-trigger' {...props} />;
15+
}
1216

13-
const DialogPortal = DialogPrimitive.Portal;
17+
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
18+
return <DialogPrimitive.Portal data-slot='dialog-portal' {...props} />;
19+
}
1420

15-
const DialogClose = DialogPrimitive.Close;
21+
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
22+
return <DialogPrimitive.Close data-slot='dialog-close' {...props} />;
23+
}
1624

17-
const DialogOverlay = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Overlay>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>>(
18-
({ className, ...props }, ref) => (
25+
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
26+
return (
1927
<DialogPrimitive.Overlay
20-
ref={ref}
28+
data-slot='dialog-overlay'
2129
className={cn(
22-
"fixed inset-0 z-50 bg-black/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
30+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
2331
className,
2432
)}
2533
{...props}
2634
/>
27-
),
28-
);
29-
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
35+
);
36+
}
3037

31-
const DialogContent = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Content>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>>(
32-
({ className, children, ...props }, ref) => (
33-
<DialogPortal>
38+
function DialogContent({
39+
className,
40+
children,
41+
showCloseButton = true,
42+
...props
43+
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
44+
showCloseButton?: boolean;
45+
}) {
46+
return (
47+
<DialogPortal data-slot='dialog-portal'>
3448
<DialogOverlay />
3549
<DialogPrimitive.Content
36-
ref={ref}
50+
data-slot='dialog-content'
3751
className={cn(
38-
"fixed left-[50%] top-[50%] z-50 grid w-full outline-none overflow-y-auto max-h-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background py-6 px-4 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
52+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
3953
className,
4054
)}
4155
{...props}>
4256
{children}
43-
<DialogPrimitive.Close className='absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground'>
44-
<Cross2Icon className='h-4 w-4' />
45-
<span className='sr-only'>Close</span>
46-
</DialogPrimitive.Close>
57+
{showCloseButton && (
58+
<DialogPrimitive.Close
59+
data-slot='dialog-close'
60+
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
61+
<XIcon />
62+
<span className='sr-only'>Close</span>
63+
</DialogPrimitive.Close>
64+
)}
4765
</DialogPrimitive.Content>
4866
</DialogPortal>
49-
),
50-
);
51-
DialogContent.displayName = DialogPrimitive.Content.displayName;
67+
);
68+
}
5269

53-
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
54-
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
55-
);
56-
DialogHeader.displayName = "DialogHeader";
70+
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
71+
return <div data-slot='dialog-header' className={cn("flex flex-col gap-2 text-center sm:text-left", className)} {...props} />;
72+
}
5773

58-
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
59-
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
60-
);
61-
DialogFooter.displayName = "DialogFooter";
74+
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
75+
return <div data-slot='dialog-footer' className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)} {...props} />;
76+
}
6277

63-
const DialogTitle = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Title>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>>(
64-
({ className, ...props }, ref) => <DialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold leading-none tracking-tight", className)} {...props} />,
65-
);
66-
DialogTitle.displayName = DialogPrimitive.Title.displayName;
78+
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
79+
return <DialogPrimitive.Title data-slot='dialog-title' className={cn("text-lg leading-none font-semibold", className)} {...props} />;
80+
}
6781

68-
const DialogDescription = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Description>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>>(
69-
({ className, ...props }, ref) => <DialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />,
70-
);
71-
DialogDescription.displayName = DialogPrimitive.Description.displayName;
82+
function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
83+
return <DialogPrimitive.Description data-slot='dialog-description' className={cn("text-muted-foreground text-sm", className)} {...props} />;
84+
}
7285

7386
export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger };

src/renderer/src/components/ui/responsive-tabs.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ function TabNavbar({ children, defaultTab, orientation = "horizontal", indicator
7373
<TabContext.Provider value={contextValue}>
7474
<nav
7575
className={cn(
76-
`relative flex select-none ${isMobile ? "items-stretch" : isVertical ? "flex-col items-stretch pb-10" : "items-center "} border-border ${isMobile && !isVertical ? "" : isVertical ? "border-r h-full pr-2" : "border-b"}`,
76+
`relative flex select-none border-border`,
77+
isMobile && "items-stretch",
78+
!isMobile && isVertical ? "flex-col items-stretch pb-10" : "items-center",
79+
!isMobile && isVertical ? "border-r h-full" : "border-b",
7780
className,
7881
)}>
7982
{isMobile && orientation === "horizontal" ? (
@@ -97,7 +100,7 @@ function TabNavbar({ children, defaultTab, orientation = "horizontal", indicator
97100
</Sheet>
98101
) : (
99102
<>
100-
<div className={cn(`flex ${orientation === "vertical" ? "flex-col space-y-1 truncate flex-auto" : "items-center flex-auto space-x-1"}`)}>{children}</div>
103+
<div className={cn(`flex ${orientation === "vertical" ? "flex-col space-y-1 flex-auto" : "items-center flex-auto space-x-1"}`)}>{children}</div>
101104
<ActiveTabIndicator />
102105
</>
103106
)}
@@ -190,7 +193,7 @@ function ActiveTabIndicator() {
190193
!isInitialRender && (
191194
<>
192195
<motion.div
193-
className='bg-primary absolute rounded-full'
196+
className='bg-primary absolute rounded-full -z-1'
194197
initial={false}
195198
animate={getIndicatorStyles()}
196199
transition={{
@@ -201,7 +204,7 @@ function ActiveTabIndicator() {
201204
/>
202205
{orientation === "vertical" && (
203206
<motion.div
204-
className='bg-gradient-to-r from-primary/0 to-primary/40 blur absolute rounded-full'
207+
className='bg-linear-to-r from-primary/0 to-primary/40 blur absolute rounded-full -z-1'
205208
style={{
206209
[indicatorPosition !== "right" ? "marginLeft" : "marginRight"]: "-" + getIndicatorStyles().width,
207210
}}

src/renderer/src/components/ui/tooltip.tsx

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,63 @@
33
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
44
import * as React from "react";
55

6+
import { Slot } from "@radix-ui/react-slot";
67
import { cn } from "@renderer/lib/utils";
78

8-
const TooltipProvider = TooltipPrimitive.Provider;
9+
function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
10+
return <TooltipPrimitive.Provider data-slot='tooltip-provider' delayDuration={delayDuration} {...props} />;
11+
}
912

10-
const Tooltip = TooltipPrimitive.Root;
13+
function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
14+
return (
15+
<TooltipProvider>
16+
<TooltipPrimitive.Root data-slot='tooltip' {...props} />
17+
</TooltipProvider>
18+
);
19+
}
1120

12-
const TooltipTrigger = TooltipPrimitive.Trigger;
21+
function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
22+
return <TooltipPrimitive.Trigger data-slot='tooltip-trigger' {...props} />;
23+
}
1324

14-
const TooltipContent = React.forwardRef<React.ElementRef<typeof TooltipPrimitive.Content>, React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>>(
15-
({ className, sideOffset = 4, ...props }, ref) => (
16-
<TooltipPrimitive.Content
17-
ref={ref}
18-
sideOffset={sideOffset}
19-
className={cn(
20-
"z-50 overflow-hidden rounded-md bg-secondary px-3 py-1.5 text-xs text-secondary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
21-
className,
22-
)}
23-
{...props}
24-
/>
25-
),
26-
);
27-
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
25+
function TooltipContent({ className, sideOffset = 0, children, ...props }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
26+
return (
27+
<TooltipPrimitive.Portal>
28+
<TooltipPrimitive.Content
29+
data-slot='tooltip-content'
30+
sideOffset={sideOffset}
31+
className={cn(
32+
"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
33+
className,
34+
)}
35+
{...props}>
36+
{children}
37+
</TooltipPrimitive.Content>
38+
</TooltipPrimitive.Portal>
39+
);
40+
}
2841

2942
const QTooltip = React.forwardRef<
3043
React.ElementRef<typeof Tooltip>,
3144
React.ComponentPropsWithoutRef<typeof TooltipContent> & React.PropsWithChildren<{ content: string | React.ReactNode }>
3245
>(({ className, children, content, asChild, side, ...props }, ref) => {
46+
const TriggerSlot = asChild ? Slot : "button";
3347
return (
34-
<Tooltip delayDuration={350} {...props}>
48+
<Tooltip delayDuration={350} defaultOpen={false} {...props}>
3549
<TooltipTrigger disabled={!content} asChild={asChild} className='cursor-auto'>
36-
{children}
50+
<TriggerSlot>{children}</TriggerSlot>
3751
</TooltipTrigger>
3852
<TooltipContent
3953
align='center'
4054
{...{ side, ...props }}
4155
sideOffset={4}
4256
updatePositionStrategy='optimized'
4357
ref={ref}
44-
className='bg-white dark:bg-background-2 border border-muted dark:border-muted/60 shadow-md p-2.5 text-primary'>
58+
className='bg-white dark:bg-background border border-muted fill-muted dark:border-muted/60 shadow-md text-primary'>
4559
{typeof content === "string" ? <div>{content}</div> : content}
4660
</TooltipContent>
4761
</Tooltip>
4862
);
4963
});
50-
64+
QTooltip.displayName = "QTooltip";
5165
export { QTooltip, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };

src/renderer/src/pages/components/link-list.tsx

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -235,36 +235,40 @@ export default function LinkList(props: { className?: string }) {
235235
}
236236
},
237237
});
238+
const noResults = useMemo(() => !filteredItems.length, [filteredItems]);
239+
const hasSearch = useMemo(() => !!search?.length, [search]);
238240
return (
239241
<div className={cn("flex flex-col gap-2 relative", props.className)}>
240-
<VList className='flex-grow relative flex flex-col py-2.5 px-2' style={{ height: "100%" }}>
242+
<VList className='grow relative flex flex-col py-2.5 px-2' style={{ height: "100%" }}>
241243
{isFetching && <SuspenseLoader className='absolute bg-background inset-0' />}
242244
{filteredItems?.map((d) => (
243245
<div className='h-12' key={d.id}>
244246
<LinkListItem {...(d as any)} />
245247
</div>
246248
))}
247249
</VList>
248-
{!filteredItems.length && (
250+
{noResults && (
249251
<div className='absolute bg-background inset-0 flex items-center justify-center text-muted-foreground gap-4 flex-col'>
250-
<span>No results found. Try a different search.</span>
252+
{hasSearch ? <span>No results found. Try a different search.</span> : <span>No downloads found. Add a link to start downloading.</span>}
251253
<pre className='flex flex-col bg-muted/20 border border-muted/40 p-2 rounded-md'>
252254
{["type:video|audio", "size:<100mb, <=10gb, etc", "status:success|error|active"].map((line) => (
253255
<span key={line} className='text-xs text-muted-foreground'>
254256
{line}
255257
</span>
256258
))}
257259
</pre>
258-
<Button
259-
variant={"ghost"}
260-
size={"sm"}
261-
className='px-2'
262-
onClick={() => {
263-
setSearch("");
264-
}}>
265-
<LucideRefreshCcw />
266-
<span>Clear search</span>
267-
</Button>
260+
{hasSearch && (
261+
<Button
262+
variant={"ghost"}
263+
size={"sm"}
264+
className='px-2'
265+
onClick={() => {
266+
setSearch("");
267+
}}>
268+
<LucideRefreshCcw />
269+
<span>Clear search</span>
270+
</Button>
271+
)}
268272
</div>
269273
)}
270274
</div>

src/renderer/src/pages/components/settings/context.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { atom, getDefaultStore, useAtom } from "jotai";
2-
import { PropsWithChildren, createContext, useCallback, useContext, useEffect } from "react";
3-
import SettingsDialog from "./dialog";
2+
import { Provider, createContext, useCallback, useContext, useEffect } from "react";
43

54
type SettingsContextType = {
65
open: boolean;
@@ -16,7 +15,7 @@ const setShowSettings = (open: boolean) => {
1615
console.log("setShowSettings", v);
1716
};
1817
const SettingsContext = createContext<SettingsContextType>({} as any);
19-
const SettingsContextProvider = ({ children }: PropsWithChildren) => {
18+
const SettingsContextProvider: Provider<SettingsContextType> = ({ value, ...props }) => {
2019
const [settings, setSettings] = useAtom(settingsContext);
2120
const showSettings = useCallback(() => {
2221
setSettings((s) => ({ ...s, open: true }));
@@ -27,12 +26,7 @@ const SettingsContextProvider = ({ children }: PropsWithChildren) => {
2726
useEffect(() => {
2827
console.log("settings", settings);
2928
}, [settings]);
30-
return (
31-
<SettingsContext.Provider value={{ open: settings.open, showSettings, closeSettings }}>
32-
{children}
33-
<SettingsDialog />
34-
</SettingsContext.Provider>
35-
);
29+
return <SettingsContext.Provider value={{ ...value, open: settings.open, showSettings, closeSettings }} {...props} />;
3630
};
3731

3832
const useSettings = () => {

0 commit comments

Comments
 (0)