One-click backups for AT Protocol

feat: add open in browser

Turtlepaw 29040224 227ed58d

Changed files
+69 -5
src
routes
+69 -5
src/routes/Home.tsx
··· 13 13 import { createBackupDir, getBackupDir } from "@/lib/paths"; 14 14 import { settingsManager } from "@/lib/settings"; 15 15 import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs"; 16 - import { openPath } from "@tauri-apps/plugin-opener"; 16 + import { openPath, openUrl } from "@tauri-apps/plugin-opener"; 17 17 import { 18 18 ChevronDown, 19 19 FileText, ··· 25 25 LoaderCircleIcon, 26 26 Package, 27 27 Settings as SettingsIcon, 28 + SquareArrowOutUpRight, 28 29 User, 29 30 Users, 30 31 } from "lucide-react"; 31 32 import { useEffect, useRef, useState } from "react"; 32 33 import { toast } from "sonner"; 33 34 import Settings from "./Settings"; 35 + import { 36 + Dialog, 37 + DialogClose, 38 + DialogContent, 39 + DialogDescription, 40 + DialogFooter, 41 + DialogHeader, 42 + DialogTitle, 43 + DialogTrigger, 44 + } from "@/components/ui/dialog"; 34 45 35 46 export function Home({ 36 47 profile, ··· 451 462 <div className="flex items-center justify-between mb-2"> 452 463 <div className="flex items-center gap-2"> 453 464 {getRecordTypeIcon(type)} 454 - <span className="text-white/80 text-sm font-medium"> 455 - {type} 456 - </span> 465 + <Dialog> 466 + <DialogTrigger className="gap-2 p-0 text-inherit hover:underline flex items-center cursor-pointer"> 467 + <span className="text-white/80 text-sm font-medium"> 468 + {type} 469 + </span> 470 + </DialogTrigger> 471 + <DialogContent> 472 + <DialogHeader> 473 + <DialogTitle> 474 + Open link in browser 475 + </DialogTitle> 476 + <DialogDescription> 477 + This will open the URL in your browser: 478 + <div className="border-[1.8px] border-[--secondary] rounded-sm px-3 py-2 mt-3 text-base"> 479 + https:// 480 + <span className="font-semibold"> 481 + {getUrlFromNamespace(type)} 482 + </span> 483 + </div> 484 + </DialogDescription> 485 + <DialogFooter> 486 + <DialogClose> 487 + <Button 488 + variant="outline" 489 + className="cursor-pointer" 490 + > 491 + Cancel 492 + </Button> 493 + </DialogClose> 494 + <DialogClose> 495 + <Button 496 + onClick={() => 497 + openUrl( 498 + `https://${getUrlFromNamespace( 499 + type 500 + )}` 501 + ) 502 + } 503 + className="cursor-pointer" 504 + > 505 + Open{" "} 506 + <SquareArrowOutUpRight size={14} /> 507 + </Button> 508 + </DialogClose> 509 + </DialogFooter> 510 + </DialogHeader> 511 + </DialogContent> 512 + </Dialog> 457 513 </div> 458 514 <div className="flex items-center gap-2"> 459 515 <span className="text-white font-semibold"> 460 516 {count.toLocaleString()} 461 517 </span> 462 - <span className="text-white/60 text-xs"> 518 + <span className="text-white/60 text-sm"> 463 519 ({percentage.toFixed(1)}%) 464 520 </span> 465 521 </div> ··· 479 535 </div> 480 536 ); 481 537 } 538 + 539 + function getUrlFromNamespace(type: string) { 540 + const parts = type.split("."); 541 + if (parts.length >= 2) { 542 + return `${parts[1]}.${parts[0]}`; 543 + } 544 + return type; // Not enough parts to swap 545 + }