mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-17 19:07:06 +02:00
ui: aspect ratios, url styles in auth prompt
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
28
src/lib/imeta.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user