34
packages/app/src/Components/Embed/GenericPlayer.tsx
Normal file
34
packages/app/src/Components/Embed/GenericPlayer.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import Icon from "../Icons/Icon";
|
||||||
|
import { ProxyImg } from "../ProxyImg";
|
||||||
|
|
||||||
|
export default function GenericPlayer({ url, poster }: { url: string; poster: string }) {
|
||||||
|
const [play, setPlay] = useState(false);
|
||||||
|
|
||||||
|
if (!play) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative aspect-video"
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setPlay(true);
|
||||||
|
}}>
|
||||||
|
<ProxyImg className="absolute" src={poster} />
|
||||||
|
<div className="absolute w-full h-full opacity-0 hover:opacity-100 hover:bg-black/30 flex items-center justify-center transition">
|
||||||
|
<Icon name="play-square-outline" size={50} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className="aspect-video w-full"
|
||||||
|
src={url}
|
||||||
|
frameBorder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowFullScreen={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -38,7 +38,9 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-preview-image {
|
.link-preview-container img,
|
||||||
|
.link-preview-container video,
|
||||||
|
.link-preview-container iframe {
|
||||||
margin: 0 0 15px 0 !important;
|
margin: 0 0 15px 0 !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
background-image: var(--img-url);
|
background-image: var(--img-url);
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import "./LinkPreview.css";
|
import "./LinkPreview.css";
|
||||||
|
|
||||||
import { CSSProperties, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { LRUCache } from "typescript-lru-cache";
|
import { LRUCache } from "typescript-lru-cache";
|
||||||
|
|
||||||
import { MediaElement } from "@/Components/Embed/MediaElement";
|
import { MediaElement } from "@/Components/Embed/MediaElement";
|
||||||
import Spinner from "@/Components/Icons/Spinner";
|
import Spinner from "@/Components/Icons/Spinner";
|
||||||
import { LinkPreviewData, NostrServices } from "@/External/NostrServices";
|
import { LinkPreviewData, NostrServices } from "@/External/NostrServices";
|
||||||
import useImgProxy from "@/Hooks/useImgProxy";
|
|
||||||
|
import { ProxyImg } from "../ProxyImg";
|
||||||
|
import GenericPlayer from "./GenericPlayer";
|
||||||
|
|
||||||
async function fetchUrlPreviewInfo(url: string) {
|
async function fetchUrlPreviewInfo(url: string) {
|
||||||
const api = new NostrServices("https://nostr.api.v0l.io");
|
const api = new NostrServices("https://nostr.api.v0l.io");
|
||||||
@ -23,7 +25,6 @@ const cache = new LRUCache<string, LinkPreviewData>({
|
|||||||
|
|
||||||
const LinkPreview = ({ url }: { url: string }) => {
|
const LinkPreview = ({ url }: { url: string }) => {
|
||||||
const [preview, setPreview] = useState<LinkPreviewData | null>(cache.get(url));
|
const [preview, setPreview] = useState<LinkPreviewData | null>(cache.get(url));
|
||||||
const { proxy } = useImgProxy();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@ -59,6 +60,9 @@ const LinkPreview = ({ url }: { url: string }) => {
|
|||||||
if (link && videoType.startsWith("video/")) {
|
if (link && videoType.startsWith("video/")) {
|
||||||
return <MediaElement url={link} mime={videoType} />;
|
return <MediaElement url={link} mime={videoType} />;
|
||||||
}
|
}
|
||||||
|
if (link && videoType.startsWith("text/html") && preview?.image) {
|
||||||
|
return <GenericPlayer url={link} poster={preview?.image} />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (type?.startsWith("image")) {
|
if (type?.startsWith("image")) {
|
||||||
const urlTags = ["og:image:secure_url", "og:image:url", "og:image"];
|
const urlTags = ["og:image:secure_url", "og:image:url", "og:image"];
|
||||||
@ -69,9 +73,7 @@ const LinkPreview = ({ url }: { url: string }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (preview?.image) {
|
if (preview?.image) {
|
||||||
const backgroundImage = preview?.image ? `url(${proxy(preview?.image)})` : "";
|
return <ProxyImg src={preview?.image} />;
|
||||||
const style = { "--img-url": backgroundImage } as CSSProperties;
|
|
||||||
return <div className="link-preview-image" style={style}></div>;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,7 @@ const TwitchEmbed = ({ link }: { link: string }) => {
|
|||||||
const args = `?channel=${channel}&parent=${window.location.hostname}&muted=true`;
|
const args = `?channel=${channel}&parent=${window.location.hostname}&muted=true`;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<iframe
|
<iframe src={`https://player.twitch.tv/${args}`} className="w-max" allowFullScreen={true} />
|
||||||
src={`https://player.twitch.tv/${args}`}
|
|
||||||
className="w-max"
|
|
||||||
allowFullScreen={true}
|
|
||||||
// eslint-disable-next-line react/no-unknown-property
|
|
||||||
credentialless=""
|
|
||||||
/>
|
|
||||||
<a href={link} target="_blank" rel="noreferrer" onClick={e => e.stopPropagation()} className="ext">
|
<a href={link} target="_blank" rel="noreferrer" onClick={e => e.stopPropagation()} className="ext">
|
||||||
{link}
|
{link}
|
||||||
</a>
|
</a>
|
||||||
|
@ -83,7 +83,7 @@ export function Note(props: NoteProps) {
|
|||||||
<div className="body" onClick={e => goToEvent(e, ev)}>
|
<div className="body" onClick={e => goToEvent(e, ev)}>
|
||||||
<NoteText {...props} translated={translated} showTranslation={showTranslation} />
|
<NoteText {...props} translated={translated} showTranslation={showTranslation} />
|
||||||
{translated && <TranslationInfo translated={translated} setShowTranslation={setShowTranslation} />}
|
{translated && <TranslationInfo translated={translated} setShowTranslation={setShowTranslation} />}
|
||||||
{ev.kind === EventKind.Polls && <Poll ev={ev} />}
|
{ev.kind === EventKind.Polls && <Poll ev={ev} zaps={[]} />}
|
||||||
{optionsMerged.showFooter && (
|
{optionsMerged.showFooter && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<NoteFooter ev={ev} replyCount={props.threadChains?.get(chainKey(ev))?.length} />
|
<NoteFooter ev={ev} replyCount={props.threadChains?.get(chainKey(ev))?.length} />
|
||||||
|
@ -380,17 +380,25 @@ export class Query extends EventEmitter<QueryEvents> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#canSendQuery(c: Connection, q: BuiltRawReqFilter) {
|
#canSendQuery(c: Connection, q: BuiltRawReqFilter) {
|
||||||
|
// query is not for this relay
|
||||||
if (q.relay && q.relay !== c.Address) {
|
if (q.relay && q.relay !== c.Address) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// cannot send unless relay is tagged on ephemeral relay connection
|
||||||
if (!q.relay && c.Ephemeral) {
|
if (!q.relay && c.Ephemeral) {
|
||||||
this.#log("Cant send non-specific REQ to ephemeral connection %O %O %O", q, q.relay, c);
|
this.#log("Cant send non-specific REQ to ephemeral connection %O %O %O", q, q.relay, c);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// search not supported, cant send
|
||||||
if (q.filters.some(a => a.search) && !c.supportsNip(Nips.Search)) {
|
if (q.filters.some(a => a.search) && !c.supportsNip(Nips.Search)) {
|
||||||
this.#log("Cant send REQ to non-search relay", c.Address);
|
this.#log("Cant send REQ to non-search relay", c.Address);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// query already closed, cant send
|
||||||
|
if (this.canRemove()) {
|
||||||
|
this.#log("Cant send REQ when query is closed", this.id, q);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user