Files
vega/src/components/feed/MediaCards.tsx
Jure 5cbaa7b741 Rename Wrystr to Vega
Named after Jurij Vega (1754-1802), Slovenian mathematician who made
knowledge accessible through his logarithm tables. Rebrand before
OpenSats application (April 1).

- Product name, window title, identifiers, binary name all renamed
- Cargo package: wrystr -> vega, wrystr_lib -> vega_lib
- PKGBUILD: wrystr -> vega (new AUR package name)
- Release workflow: artifact names, release text updated
- README: new tagline referencing Jurij Vega
- DB migration: auto-renames wrystr.db -> vega.db on first launch
- Keyring service name kept as "wrystr" for backward compatibility
- localStorage keys kept as wrystr_* to preserve existing user data

Pending manual steps:
- Rename GitHub repo hoornet/wrystr -> hoornet/vega
- Create new AUR package vega-git, orphan wrystr-git
- Update local .desktop launcher
2026-03-30 21:14:02 +02:00

154 lines
5.3 KiB
TypeScript

import { ContentSegment } from "../../lib/parsing";
import { usePodcastStore } from "../../stores/podcast";
export function VideoBlock({ sources }: { sources: string[] }) {
if (sources.length === 0) return null;
return (
<div className="mt-2 flex flex-col gap-2">
{sources.map((src, i) => (
<video
key={i}
src={src}
controls
playsInline
preload="metadata"
className="max-w-full max-h-80 rounded-sm bg-bg-raised border border-border"
onError={(e) => { (e.target as HTMLVideoElement).style.display = "none"; }}
/>
))}
</div>
);
}
function cleanAudioName(url: string): string {
const raw = url.split("/").pop()?.split("?")[0] ?? url;
// Remove file extension
const name = raw.replace(/\.(mp3|m4a|ogg|opus|wav|flac|aac)$/i, "");
// Decode URI components and replace common separators with spaces
try {
return decodeURIComponent(name).replace(/[-_]+/g, " ");
} catch {
return name.replace(/[-_]+/g, " ");
}
}
export function AudioBlock({ sources }: { sources: string[] }) {
const play = usePodcastStore((s) => s.play);
if (sources.length === 0) return null;
return (
<div className="mt-2 flex flex-col gap-2">
{sources.map((src, i) => {
const name = cleanAudioName(src);
return (
<button
key={i}
onClick={(e) => {
e.stopPropagation();
play({
guid: `audio:${src}`,
title: name,
enclosureUrl: src,
pubDate: 0,
duration: 0,
description: "",
showTitle: "",
showArtworkUrl: "",
});
}}
className="rounded-sm bg-bg-raised border border-border p-3 flex items-center gap-3 hover:bg-bg-hover transition-colors text-left w-full"
>
<div className="w-8 h-8 rounded-full bg-accent/10 flex items-center justify-center shrink-0">
<svg width="10" height="12" viewBox="0 0 10 12" fill="currentColor" className="text-accent ml-0.5">
<polygon points="0,0 10,6 0,12" />
</svg>
</div>
<div className="min-w-0 flex-1">
<div className="text-[12px] text-text truncate">{name}</div>
<div className="text-[10px] text-text-dim">audio · play in vega</div>
</div>
</button>
);
})}
</div>
);
}
export function YouTubeCard({ seg }: { seg: ContentSegment }) {
return (
<a
href={seg.value}
target="_blank"
rel="noopener noreferrer"
className="mt-2 flex items-center gap-3 rounded-sm bg-bg-raised border border-border p-3 hover:bg-bg-hover transition-colors cursor-pointer"
>
<img
src={`https://img.youtube.com/vi/${seg.mediaId}/hqdefault.jpg`}
alt=""
className="w-28 h-16 rounded-sm object-cover shrink-0"
loading="lazy"
/>
<div className="min-w-0">
<div className="text-[11px] text-text-muted">YouTube</div>
<div className="text-[12px] text-accent truncate">{seg.value}</div>
</div>
</a>
);
}
export function VimeoCard({ seg }: { seg: ContentSegment }) {
return (
<a
href={seg.value}
target="_blank"
rel="noopener noreferrer"
className="mt-2 flex items-center gap-3 rounded-sm bg-bg-raised border border-border p-3 hover:bg-bg-hover transition-colors cursor-pointer"
>
<div className="w-10 h-10 rounded-full bg-black/40 flex items-center justify-center shrink-0">
<svg width="14" height="14" viewBox="0 0 20 20" fill="white"><polygon points="6,3 17,10 6,17" /></svg>
</div>
<div className="min-w-0">
<div className="text-[11px] text-text-muted">Vimeo</div>
<div className="text-[12px] text-accent truncate">{seg.value}</div>
</div>
</a>
);
}
export function SpotifyCard({ seg }: { seg: ContentSegment }) {
return (
<a
href={seg.value}
target="_blank"
rel="noopener noreferrer"
className="mt-2 flex items-center gap-3 rounded-sm bg-bg-raised border border-border p-3 hover:bg-bg-hover transition-colors cursor-pointer"
>
<div className="w-10 h-10 rounded-full bg-[#1DB954]/20 flex items-center justify-center shrink-0">
<span className="text-[#1DB954] text-lg font-bold">S</span>
</div>
<div className="min-w-0">
<div className="text-[11px] text-text-muted">Spotify · {seg.mediaType}</div>
<div className="text-[12px] text-accent truncate">{seg.value}</div>
</div>
</a>
);
}
export function TidalCard({ seg }: { seg: ContentSegment }) {
return (
<a
href={seg.value}
target="_blank"
rel="noopener noreferrer"
className="mt-2 flex items-center gap-3 rounded-sm bg-bg-raised border border-border p-3 hover:bg-bg-hover transition-colors cursor-pointer"
>
<div className="w-10 h-10 rounded-full bg-white/10 flex items-center justify-center shrink-0">
<span className="text-white text-lg font-bold">T</span>
</div>
<div className="min-w-0">
<div className="text-[11px] text-text-muted">Tidal · {seg.mediaType}</div>
<div className="text-[12px] text-accent truncate">{seg.value}</div>
</div>
</a>
);
}