mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-11 07:56:50 +02:00
Add download button to Zapstore app renderers
Add a download button for the latest release to both the feed and detail renderers for kind 32267 (Zapstore App Metadata). The feed renderer shows a compact version button, while the detail renderer shows a prominent download button in the header. Both fetch the latest release and link to its file metadata event (kind 1063) for download.
This commit is contained in:
@@ -147,6 +147,7 @@ function PlatformItem({ platform }: { platform: Platform }) {
|
||||
export function ZapstoreAppDetailRenderer({
|
||||
event,
|
||||
}: ZapstoreAppDetailRendererProps) {
|
||||
const { addWindow } = useGrimoire();
|
||||
const appName = getAppName(event);
|
||||
const summary = getAppSummary(event);
|
||||
const iconUrl = getAppIcon(event);
|
||||
@@ -186,6 +187,19 @@ export function ZapstoreAppDetailRenderer({
|
||||
});
|
||||
}, [releases]);
|
||||
|
||||
// Get the latest release for the header download button
|
||||
const latestRelease = sortedReleases[0] || null;
|
||||
const latestFileEventId = latestRelease
|
||||
? getReleaseFileEventId(latestRelease)
|
||||
: null;
|
||||
const latestVersion = latestRelease ? getReleaseVersion(latestRelease) : null;
|
||||
|
||||
const handleDownloadLatest = () => {
|
||||
if (latestFileEventId) {
|
||||
addWindow("open", { pointer: { id: latestFileEventId } });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 p-6 max-w-4xl mx-auto">
|
||||
{/* Header Section */}
|
||||
@@ -206,7 +220,18 @@ export function ZapstoreAppDetailRenderer({
|
||||
|
||||
{/* App Title & Summary */}
|
||||
<div className="flex flex-col gap-2 flex-1 min-w-0">
|
||||
<h1 className="text-3xl font-bold">{appName}</h1>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<h1 className="text-3xl font-bold">{appName}</h1>
|
||||
{latestFileEventId && (
|
||||
<button
|
||||
onClick={handleDownloadLatest}
|
||||
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors flex-shrink-0"
|
||||
>
|
||||
<FileDown className="size-4" />
|
||||
{latestVersion ? `Download v${latestVersion}` : "Download"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{summary && (
|
||||
<p className="text-muted-foreground text-base">{summary}</p>
|
||||
)}
|
||||
|
||||
@@ -6,28 +6,92 @@ import {
|
||||
import {
|
||||
getAppName,
|
||||
getAppSummary,
|
||||
getAppIdentifier,
|
||||
detectPlatforms,
|
||||
getReleaseVersion,
|
||||
getReleaseFileEventId,
|
||||
} from "@/lib/zapstore-helpers";
|
||||
import { PlatformIcon } from "./zapstore/PlatformIcon";
|
||||
import { use$ } from "applesauce-react/hooks";
|
||||
import eventStore from "@/services/event-store";
|
||||
import { useMemo } from "react";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { FileDown } from "lucide-react";
|
||||
|
||||
/**
|
||||
* Renderer for Kind 32267 - App Metadata
|
||||
* Clean feed view with app name, summary, and platform icons
|
||||
* Clean feed view with app name, summary, platform icons, and download button
|
||||
*/
|
||||
export function ZapstoreAppRenderer({ event }: BaseEventProps) {
|
||||
const { addWindow } = useGrimoire();
|
||||
const appName = getAppName(event);
|
||||
const summary = getAppSummary(event);
|
||||
const identifier = getAppIdentifier(event);
|
||||
const platforms = detectPlatforms(event);
|
||||
|
||||
// Query for releases that reference this app
|
||||
const releasesFilter = useMemo(() => {
|
||||
if (!identifier) {
|
||||
return { kinds: [30063], ids: [] };
|
||||
}
|
||||
return {
|
||||
kinds: [30063],
|
||||
"#a": [`32267:${event.pubkey}:${identifier}`],
|
||||
};
|
||||
}, [event.pubkey, identifier]);
|
||||
|
||||
const releases = use$(
|
||||
() => eventStore.timeline(releasesFilter),
|
||||
[releasesFilter],
|
||||
);
|
||||
|
||||
// Get the latest release (by version or created_at)
|
||||
const latestRelease = useMemo(() => {
|
||||
if (!releases || releases.length === 0) return null;
|
||||
return [...releases].sort((a, b) => {
|
||||
const versionA = getReleaseVersion(a);
|
||||
const versionB = getReleaseVersion(b);
|
||||
if (versionA && versionB) {
|
||||
return versionB.localeCompare(versionA, undefined, { numeric: true });
|
||||
}
|
||||
return b.created_at - a.created_at;
|
||||
})[0];
|
||||
}, [releases]);
|
||||
|
||||
const latestFileEventId = latestRelease
|
||||
? getReleaseFileEventId(latestRelease)
|
||||
: null;
|
||||
const latestVersion = latestRelease ? getReleaseVersion(latestRelease) : null;
|
||||
|
||||
const handleDownload = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (latestFileEventId) {
|
||||
addWindow("open", { pointer: { id: latestFileEventId } });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseEventContainer event={event}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<ClickableEventTitle
|
||||
event={event}
|
||||
className="text-base font-semibold text-foreground"
|
||||
>
|
||||
{appName}
|
||||
</ClickableEventTitle>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<ClickableEventTitle
|
||||
event={event}
|
||||
className="text-base font-semibold text-foreground"
|
||||
>
|
||||
{appName}
|
||||
</ClickableEventTitle>
|
||||
|
||||
{latestFileEventId && (
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
className="flex items-center gap-1.5 px-2 py-1 text-xs font-medium text-primary border border-primary/20 rounded hover:bg-primary/10 transition-colors flex-shrink-0"
|
||||
title={latestVersion ? `Download v${latestVersion}` : "Download"}
|
||||
>
|
||||
<FileDown className="size-3" />
|
||||
{latestVersion ? `v${latestVersion}` : "Download"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{summary && (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
|
||||
Reference in New Issue
Block a user