ui: aspect ratios, url styles in auth prompt

This commit is contained in:
Alejandro Gómez
2025-12-16 17:28:50 +01:00
parent 24d2640774
commit 6126f43e34
7 changed files with 101 additions and 17 deletions

View File

@@ -25,10 +25,10 @@ function AuthToast({
const [loading, setLoading] = useState(false);
return (
<div className="bg-background border border-border shadow-lg p-4 min-w-[350px] max-w-[500px]">
<div className="bg-background border border-border shadow-lg p-4 min-w-[350px] max-w-[500px] overflow-hidden">
<div className="flex items-start gap-3">
<Key className="size-5 text-yellow-500 flex-shrink-0 mt-0.5" />
<div className="flex-1 space-y-3">
<div className="flex-1 space-y-3 min-w-0">
<div>
<div className="font-semibold text-sm text-foreground mb-1">
Authentication Request
@@ -38,7 +38,7 @@ function AuthToast({
showInboxOutbox={false}
variant="prompt"
iconClassname="size-4"
urlClassname="break-all text-sm"
urlClassname="text-sm"
/>
</div>

View File

@@ -691,14 +691,31 @@ export default function ReqViewer({
} = useOutboxRelays(resolvedFilter, outboxOptions);
// Use explicit relays if provided, otherwise use NIP-65 selected relays
const finalRelays = relays || selectedRelays;
// Wait for relay selection to complete before subscribing to prevent multiple reconnections
const finalRelays = useMemo(() => {
// Explicit relays always used immediately
if (relays) {
return relays;
}
// Wait for outbox relay selection to complete before subscribing
// This prevents multiple reconnections during discovery/selection phases
if (relaySelectionPhase !== 'ready') {
return [];
}
return selectedRelays;
}, [relays, relaySelectionPhase, selectedRelays]);
// Get relay state for each relay and calculate connected count
const relayStatesForReq = finalRelays.map((url) => ({
url,
state: relayStates[url],
}));
const relayStatesForReq = useMemo(
() => finalRelays.map((url) => ({
url,
state: relayStates[url],
})),
[finalRelays, relayStates]
);
const connectedCount = relayStatesForReq.filter(
(r) => r.state?.connectionState === "connected",
).length;

View File

@@ -219,14 +219,12 @@ export function MediaEmbed({
? {
aspectRatio: effectiveAspectRatio,
maxHeight: presetStyles.maxHeight,
maxWidth:
preset === "thumbnail" ? presetStyles.maxWidth : "100%",
maxWidth: presetStyles.maxWidth,
contain: "content", // Performance optimization
}
: {
maxHeight: presetStyles.maxHeight,
maxWidth:
preset === "thumbnail" ? presetStyles.maxWidth : "100%",
maxWidth: presetStyles.maxWidth,
}
}
>

View File

@@ -74,7 +74,7 @@ export function RelayLink({
)}
onClick={handleClick}
>
<div className="flex items-center gap-1.5 min-w-0">
<div className="flex items-center gap-1.5 min-w-0 flex-1 overflow-hidden">
{relayInfo?.icon && (
<img
src={relayInfo.icon}
@@ -110,7 +110,12 @@ export function RelayLink({
</HoverCardContent>
</HoverCard>
)}
<span className={cn("text-xs truncate", urlClassname)}>
<span
className={cn(
"text-xs truncate",
urlClassname,
)}
>
{displayUrl}
</span>
</div>

View File

@@ -1,7 +1,10 @@
import { BaseEventContainer, type BaseEventProps } from "./BaseEventRenderer";
import { MediaEmbed } from "../MediaEmbed";
import { RichText } from "../RichText";
import { parseImetaTags } from "@/lib/imeta";
import {
parseImetaTags,
getAspectRatioFromDimensions,
} from "@/lib/imeta";
/**
* Renderer for Kind 20 - Picture Event (NIP-68)
@@ -26,13 +29,24 @@ export function Kind20Renderer({ event }: BaseEventProps) {
{/* Images */}
{images.length > 0 && (
<div className="flex flex-col gap-2">
<div
className={
images.length === 1
? "flex flex-col"
: images.length === 2
? "grid grid-cols-2 gap-2"
: images.length <= 4
? "grid grid-cols-2 gap-2"
: "grid grid-cols-3 gap-2"
}
>
{images.map((img, i) => (
<MediaEmbed
key={i}
url={img.url}
alt={img.alt || title || "Picture"}
preset="preview"
preset={images.length === 1 ? "preview" : "inline"}
aspectRatio={getAspectRatioFromDimensions(img.dim)}
enableZoom
/>
))}

28
src/lib/imeta.test.ts Normal file
View File

@@ -0,0 +1,28 @@
import { describe, it, expect } from "vitest";
import { getAspectRatioFromDimensions } from "./imeta";
describe("getAspectRatioFromDimensions", () => {
it("should parse valid dimension string", () => {
expect(getAspectRatioFromDimensions("1920x1080")).toBe("1920/1080");
expect(getAspectRatioFromDimensions("800x600")).toBe("800/600");
expect(getAspectRatioFromDimensions("1x1")).toBe("1/1");
});
it("should return undefined for invalid formats", () => {
expect(getAspectRatioFromDimensions("1920")).toBe(undefined);
expect(getAspectRatioFromDimensions("1920 x 1080")).toBe(undefined);
expect(getAspectRatioFromDimensions("1920:1080")).toBe(undefined);
expect(getAspectRatioFromDimensions("abc x def")).toBe(undefined);
});
it("should return undefined for invalid dimensions", () => {
expect(getAspectRatioFromDimensions("0x1080")).toBe(undefined);
expect(getAspectRatioFromDimensions("1920x0")).toBe(undefined);
expect(getAspectRatioFromDimensions("-1920x1080")).toBe(undefined);
});
it("should return undefined for empty or missing input", () => {
expect(getAspectRatioFromDimensions("")).toBe(undefined);
expect(getAspectRatioFromDimensions(undefined)).toBe(undefined);
});
});

View File

@@ -156,3 +156,25 @@ export function formatFileSize(bytes?: string | number): string {
return `${displaySize.toFixed(1)} ${units[unitIndex]}`;
}
/**
* Extract aspect ratio from imeta dimensions string
* @param dim - dimensions string like "1920x1080"
* @returns aspect ratio as string like "16/9" or undefined if invalid
*/
export function getAspectRatioFromDimensions(
dim?: string,
): string | undefined {
if (!dim) return undefined;
const match = dim.match(/^(\d+)x(\d+)$/);
if (!match) return undefined;
const width = parseInt(match[1], 10);
const height = parseInt(match[2], 10);
if (width <= 0 || height <= 0) return undefined;
// Return as CSS aspect-ratio value
return `${width}/${height}`;
}