1
1
mirror of https://github.com/neosubhamoy/pytubepp-helper.git synced 2026-02-04 03:12:22 +05:30

(feat): added update notification preference toggle in settings

This commit is contained in:
2025-03-01 23:13:44 +05:30
Verified
parent d835184b30
commit 802d8c1eab
10 changed files with 399 additions and 660 deletions

924
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@
"@radix-ui/react-progress": "^1.1.2",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-toast": "^1.2.5",
"@radix-ui/react-tooltip": "^1.1.7",
"@tauri-apps/api": "^2.0.0",

View File

@@ -7,6 +7,7 @@ use std::path::PathBuf;
pub struct Config {
pub port: u16,
pub theme: String,
pub notify_updates: bool,
}
impl Default for Config {
@@ -14,6 +15,7 @@ impl Default for Config {
Self {
port: 3030,
theme: "system".to_string(),
notify_updates: true,
}
}
}

View File

@@ -7,6 +7,7 @@ use std::path::PathBuf;
pub struct Config {
pub port: u16,
pub theme: String,
pub notify_updates: bool,
}
impl Default for Config {
@@ -14,6 +15,7 @@ impl Default for Config {
Self {
port: 3030,
theme: "system".to_string(),
notify_updates: true
}
}
}

View File

@@ -5,16 +5,20 @@ import { listen } from "@tauri-apps/api/event";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { ThemeProvider } from "@/components/theme-provider";
import { Config, WebSocketMessage } from "@/types";
import { sendStreamInfo } from "@/lib/utils";
import { compareVersions, sendStreamInfo } from "@/lib/utils";
import { Toaster } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { check as checkAppUpdate } from "@tauri-apps/plugin-updater";
import { isPermissionGranted, requestPermission, sendNotification } from "@tauri-apps/plugin-notification";
import { downloadDir, join } from "@tauri-apps/api/path";
import { fetch } from '@tauri-apps/plugin-http';
import * as fs from "@tauri-apps/plugin-fs"
function App({ children }: { children: React.ReactNode }) {
const appWindow = getCurrentWebviewWindow()
const [appConfig, setAppConfig] = useState<Config | null>(null);
const [isAppUpdateChecked, setIsAppUpdateChecked] = useState(false);
const [isExtensionUpdateChecked, setIsExtensionUpdateChecked] = useState(false);
// Prevent right click context menu in production
if (!import.meta.env.DEV) {
@@ -91,10 +95,50 @@ function App({ children }: { children: React.ReactNode }) {
}
};
if (!isAppUpdateChecked) {
const checkForExtensionUpdates = async () => {
let permissionGranted = await isPermissionGranted();
if (!permissionGranted) {
const permission = await requestPermission();
permissionGranted = permission === 'granted';
}
try {
setIsExtensionUpdateChecked(true)
const downloadDirPath = await downloadDir()
const extensionManifestPath = await join(downloadDirPath, "pytubepp-extension-chrome", "manifest.json")
const extensionManifestExists = await fs.exists(extensionManifestPath)
if (extensionManifestExists) {
const currentManifest = JSON.parse(await fs.readTextFile(extensionManifestPath))
const response = await fetch('https://github.com/neosubhamoy/pytubepp-extension/releases/latest/download/latest.json', {
method: 'GET',
});
if (response.ok) {
const data = await response.json()
if (compareVersions(data.version, currentManifest.version) === 1) {
console.log(`extension update available v${data.version}`);
if (permissionGranted) {
sendNotification({ title: `Extension Update Available (v${data.version})`, body: `A newer version of PytubePP Extension is available. Please update to the latest version to get the best experience!` });
}
}
}
else {
console.error('Failed to fetch latest extension version');
}
} else {
console.log('Currently installed extension\'s manifest not found')
}
} catch (error) {
console.error(error);
}
};
if (!isAppUpdateChecked && appConfig?.notify_updates) {
checkForUpdates();
}
}, [])
if (!isExtensionUpdateChecked && appConfig?.notify_updates) {
checkForExtensionUpdates();
}
}, [appConfig])
return (
<ThemeProvider defaultTheme={appConfig?.theme || "system"} storageKey="vite-ui-theme">

View File

@@ -0,0 +1,29 @@
"use client"
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }

View File

@@ -18,7 +18,7 @@ export async function isInstalled(program: string, arg: string): Promise<{ insta
return { installed: false, output: output.stdout };
}
} catch (error) {
console.error(error);
console.error(program + ':', error);
return { installed: false, output: null };
}
}

View File

@@ -234,8 +234,6 @@ export default function HomePage() {
const checkForExtensionUpdates = async () => {
try {
const downloadDirPath = await downloadDir()
// const extensionDirPath = await join(downloadDirPath, "pytubepp-extension-chrome")
// const extensionDirExists = await fs.exists(extensionDirPath)
const extensionManifestPath = await join(downloadDirPath, "pytubepp-extension-chrome", "manifest.json")
const extensionManifestExists = await fs.exists(extensionManifestPath)
if (extensionManifestExists) {

View File

@@ -16,12 +16,15 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
import { getPlatformInfo } from "@/lib/platform-utils";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useTheme } from "@/components/theme-provider"
import { Switch } from "@/components/ui/switch";
const DEFAULT_PORT = 3030;
const DEFAULT_THEME = "system";
const DEFAULT_NOTIFY_UPDATES = true;
const settingsFormSchema = z.object({
port: z.number().min(3000, { message: "Port must be greater than 3000" }).max(3999, { message: "Port must be less than 3999" }),
theme: z.enum(["system", "dark", "light"]),
theme: z.enum(["system", "dark", "light"], { message: "Invalid theme" }),
notify_updates: z.boolean({ message: "Not a boolean value" }),
})
export default function SettingsPage() {
@@ -37,13 +40,14 @@ export default function SettingsPage() {
defaultValues: {
port: DEFAULT_PORT,
theme: DEFAULT_THEME,
notify_updates: DEFAULT_NOTIFY_UPDATES,
},
});
useEffect(() => {
const subscription = settingsForm.watch((value) => {
if (appConfig) {
setIsFormDirty(value.port !== appConfig.port || value.theme !== appConfig.theme);
setIsFormDirty(value.port !== appConfig.port || value.theme !== appConfig.theme || value.notify_updates !== appConfig.notify_updates);
}
});
return () => subscription.unsubscribe();
@@ -54,7 +58,7 @@ export default function SettingsPage() {
const config: Config = await invoke("get_config");
if (config) {
setAppConfig(config);
settingsForm.reset({ port: config.port, theme: config.theme });
settingsForm.reset({ port: config.port, theme: config.theme, notify_updates: config.notify_updates });
}
}
getConfig().catch(console.error);
@@ -81,7 +85,8 @@ export default function SettingsPage() {
const updatedConfig: Config = await invoke("update_config", {
newConfig: {
port: Number(settingsForm.getValues().port),
theme: settingsForm.getValues().theme
theme: settingsForm.getValues().theme,
notify_updates: settingsForm.getValues().notify_updates,
}
});
setAppConfig(updatedConfig);
@@ -97,7 +102,7 @@ export default function SettingsPage() {
try {
const config: Config = await invoke("reset_config");
setAppConfig(config);
settingsForm.reset({ port: config.port, theme: config.theme });
settingsForm.reset({ port: config.port, theme: config.theme, notify_updates: config.notify_updates });
setIsFormDirty(false);
toast("Settings reset to default");
} catch (error) {
@@ -106,7 +111,7 @@ export default function SettingsPage() {
}
}
const isUsingDefaultConfig = appConfig?.port === DEFAULT_PORT && appConfig?.theme === DEFAULT_THEME;
const isUsingDefaultConfig = appConfig?.port === DEFAULT_PORT && appConfig?.theme === DEFAULT_THEME && appConfig?.notify_updates === DEFAULT_NOTIFY_UPDATES;
return (
<div className="container">
@@ -152,7 +157,7 @@ export default function SettingsPage() {
</div>
</div>
<div className={clsx("mt-5", !platformInfo?.isWindows && "mx-3")}>
<div className="flex flex-col min-h-[55vh] overflow-y-scroll">
<div className="flex flex-col min-h-[55vh] max-h-[58vh] overflow-y-scroll">
<Form {...settingsForm}>
<form onSubmit={settingsForm.handleSubmit(updateConfig)}>
<FormField
@@ -188,7 +193,7 @@ export default function SettingsPage() {
control={settingsForm.control}
name="theme"
render={({ field }) => (
<FormItem>
<FormItem className="mb-2">
<FormLabel>Theme</FormLabel>
<FormControl>
<Select {...field} onValueChange={(value) => field.onChange(value)}>
@@ -204,16 +209,39 @@ export default function SettingsPage() {
</SelectContent>
</Select>
</FormControl>
<FormDescription>
Choose app interface theme
</FormDescription>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={settingsForm.control}
name="notify_updates"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm mb-4 mt-3">
<div className="space-y-0.5">
<FormLabel>Notify Updates</FormLabel>
<FormDescription>
Notify for app and component updates (Recommended)
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<Button className="hidden" ref={saveButtonRef} type="submit">Save</Button>
</form>
</Form>
</div>
<div className="flex justify-between items-center border-t border-muted-foreground/50 pt-2 relative">
<div className="tintbar absolute -top-[0.10rem] left-0 -translate-y-full w-full h-5 bg-gradient-to-b from-transparent to-background"></div>
<div className="tintbar absolute -top-[0.05rem] left-0 -translate-y-full w-full h-5 bg-gradient-to-b from-transparent to-background"></div>
<div className="flex flex-col">
<p>PytubePP Helper <span className="text-muted-foreground">|</span> <span className="text-sm text-muted-foreground">v{appVersion}-beta</span></p>
<p className="text-xs text-muted-foreground">© {new Date().getFullYear()} - <a href="https://github.com/neosubhamoy/pytubepp-helper/blob/main/LICENSE" target="_blank">MIT License</a> - Made with by <a href="https://neosubhamoy.com" target="_blank">Subhamoy</a></p>

View File

@@ -1,6 +1,7 @@
export interface Config {
port: number;
theme: "system" | "dark" | "light";
notify_updates: boolean;
}
export interface PlatformInfo {