diff --git a/package-lock.json b/package-lock.json index 8405f59..2470b21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@hookform/resolvers": "^3.10.0", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-toast": "^1.2.5", "@radix-ui/react-tooltip": "^1.1.7", @@ -1177,6 +1178,71 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.2.tgz", + "integrity": "sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", diff --git a/package.json b/package.json index b3be4cc..2eda8b5 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@hookform/resolvers": "^3.10.0", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-toast": "^1.2.5", "@radix-ui/react-tooltip": "^1.1.7", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1ec9a43..de8e119 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3334,7 +3334,7 @@ dependencies = [ [[package]] name = "pytubepp-helper" -version = "0.7.0" +version = "0.6.0" dependencies = [ "directories", "fix-path-env", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index fb56a87..3fcaa63 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pytubepp-helper" -version = "0.7.0" +version = "0.6.0" description = "PytubePP Helper" authors = ["neosubhamoy"] edition = "2021" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e53a1da..f63f044 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -20,7 +20,7 @@ }, "productName": "pytubepp-helper", "mainBinaryName": "pytubepp-helper", - "version": "0.7.0", + "version": "0.6.0", "identifier": "com.neosubhamoy.pytubepp.helper", "plugins": { "updater": { diff --git a/src/App.tsx b/src/App.tsx index bbd03b5..cc23a95 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,7 +15,7 @@ function App({ children }: { children: React.ReactNode }) { const appWindow = getCurrentWebviewWindow() const [isAppUpdateChecked, setIsAppUpdateChecked] = useState(false); - // Prevent context menu in production + // Prevent right click context menu in production if (!import.meta.env.DEV) { document.oncontextmenu = (event) => { event.preventDefault() @@ -66,14 +66,14 @@ function App({ children }: { children: React.ReactNode }) { const permission = await requestPermission(); permissionGranted = permission === 'granted'; } - setIsAppUpdateChecked(true); try { + setIsAppUpdateChecked(true); const update = await checkAppUpdate(); if (update) { - console.log(`found update ${update.version} from ${update.date} with notes ${update.body}`); - if (permissionGranted) { - sendNotification({ title: 'Update Available', body: 'A new version of pytubepp-helper is available. Please update to the latest version.' }); - } + console.log(`app update available v${update.version}`); + if (permissionGranted) { + sendNotification({ title: `Update Available (v${update.version})`, body: `A newer version of PytubePP Helper is available. Please update to the latest version to get the best experience!` }); + } } } catch (error) { console.error(error); diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..e13d2bc --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,35 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } \ No newline at end of file diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..d0f6a47 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,75 @@ +import * as React from "react" +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } \ No newline at end of file diff --git a/src/components/ui/notification-badge.tsx b/src/components/ui/notification-badge.tsx new file mode 100644 index 0000000..ae1d8f1 --- /dev/null +++ b/src/components/ui/notification-badge.tsx @@ -0,0 +1,37 @@ +import { Badge, BadgeProps } from '@/components/ui/badge'; +import { cn } from '@/lib/utils'; + +export interface NotificationBadgeProps extends BadgeProps { + label?: string | number; + show?: boolean; +} + +export const NotificationBadge = ({ + label, + className, + show, + children, + ...props +}: NotificationBadgeProps) => { + const showBadge = + typeof label !== 'undefined' && (typeof show === 'undefined' || show); + return ( +
+ {children} + {showBadge && ( + + {'' + label} + + )} +
+ ); +}; \ No newline at end of file diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx new file mode 100644 index 0000000..011c22c --- /dev/null +++ b/src/components/ui/progress.tsx @@ -0,0 +1,27 @@ +"use client" + +import * as React from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" +import { cn } from "@/lib/utils" + +const Progress = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, value, ...props }, ref) => ( + + + +)) +Progress.displayName = ProgressPrimitive.Root.displayName + +export { Progress } \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index d2c645e..028e295 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,6 +5,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; import App from "@/App"; import HomePage from "@/pages/home"; import SettingsPage from "@/pages/settings"; +import NotificationsPage from "@/pages/notifications"; ReactDOM.createRoot(document.getElementById('root')!).render( @@ -13,6 +14,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render( } /> } /> + } /> diff --git a/src/pages/home.tsx b/src/pages/home.tsx index fc0f1ce..ab574da 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -6,10 +6,12 @@ import { Button } from "@/components/ui/button"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" import { InstalledPrograms } from "@/types"; import { compareVersions, extractVersion, isInstalled, registerMacFiles } from "@/lib/utils"; -import { CircleCheck, TriangleAlert, CircleAlert, Settings, RefreshCcw, Loader2, PackagePlus } from "lucide-react"; +import { CircleCheck, TriangleAlert, CircleAlert, Settings, RefreshCcw, Loader2, PackagePlus, Bell } from "lucide-react"; import { getPlatformInfo } from "@/lib/platform-utils"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useToast } from "@/hooks/use-toast"; +import { NotificationBadge } from "@/components/ui/notification-badge"; +import { check as checkAppUpdate } from "@tauri-apps/plugin-updater"; export default function HomePage() { const { toast } = useToast(); @@ -20,6 +22,7 @@ export default function HomePage() { const [macOsVersion, setMacOsVersion] = useState(null) const [distroId, setDistroId] = useState(null) const [distroPkgMngr, setDistroPkgMngr] = useState(null) + const [isAppUpdateAvailable, setIsAppUpdateAvailable] = useState(false); const [installedPrograms, setInstalledPrograms] = useState({ winget: { installed: false, @@ -199,6 +202,18 @@ export default function HomePage() { init(); }, []); + useEffect(() => { + const checkForUpdates = async () => { + try { + const update = await checkAppUpdate(); + setIsAppUpdateAvailable(update ? true : false); + } catch (error) { + console.error(error); + } + }; + checkForUpdates(); + }, []); + return (
@@ -206,7 +221,23 @@ export default function HomePage() {
- + + + notifications + + + + + +

refresh

+
+
+
+
+ { + isLoading ? ( +
+
+
+
+ +

ckecking...

+
+
+
+
+ ) : appUpdate ? ( +
+
+ + + PytubePP Helper v{appUpdate.version} - Update Available + A newer version of PytubePP Helper is available. Please update to the latest version to get the best experience! + { + isUpdating && ( + + ) + } + + +
+ { + isUpdating && ( +
+ +

Downloading...

+
+ ) + } +
+
+ + +
+
+
+
+
+ ) : ( +
+
+
+
+

No Notifications

+

You are all caught up! for now 😉

+
+
+
+
+ ) + } +
+
+ ) +}