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:
924
package-lock.json
generated
924
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
50
src/App.tsx
50
src/App.tsx
@@ -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">
|
||||
|
||||
29
src/components/ui/switch.tsx
Normal file
29
src/components/ui/switch.tsx
Normal 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 }
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export interface Config {
|
||||
port: number;
|
||||
theme: "system" | "dark" | "light";
|
||||
notify_updates: boolean;
|
||||
}
|
||||
|
||||
export interface PlatformInfo {
|
||||
|
||||
Reference in New Issue
Block a user