feat: full screen image preview

This commit is contained in:
Kieran 2023-05-04 11:36:21 +01:00
parent 805ce1d96e
commit 5089cd63eb
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
7 changed files with 103 additions and 78 deletions

View File

@ -0,0 +1,14 @@
.modal.spotlight .modal-body {
max-width: 100vw;
}
.modal.spotlight img,
.modal.spotlight video {
max-width: 90vw;
max-height: 90vh;
aspect-ratio: unset;
}
.modal.spotlight .close {
text-align: right;
}

View File

@ -0,0 +1,82 @@
import { ProxyImg } from "Element/ProxyImg";
import React, { MouseEvent, useState } from "react";
import "./MediaElement.css";
import Modal from "Element/Modal";
import Icon from "Icons/Icon";
/*
[
"imeta",
"url https://nostr.build/i/148e3e8cbe29ae268b0d6aad0065a086319d3c3b1fdf8b89f1e2327d973d2d05.jpg",
"blurhash e6A0%UE2t6D*R%?u?a9G?aM|~pM|%LR*RjR-%2NG%2t7_2R*%1IVWB",
"dim 3024x4032"
],
*/
interface MediaElementProps {
mime: string;
url: string;
magnet?: string;
sha256?: string;
blurHash?: string;
}
export function MediaElement(props: MediaElementProps) {
if (props.mime.startsWith("image/")) {
return (
<SpotlightMedia>
<ProxyImg key={props.url} src={props.url} />
</SpotlightMedia>
);
} else if (props.mime.startsWith("audio/")) {
return <audio key={props.url} src={props.url} controls />;
} else if (props.mime.startsWith("video/")) {
return (
<SpotlightMedia>
<video key={props.url} src={props.url} controls />
</SpotlightMedia>
);
} else {
return (
<a
key={props.url}
href={props.url}
onClick={e => e.stopPropagation()}
target="_blank"
rel="noreferrer"
className="ext">
{props.url}
</a>
);
}
}
export function SpotlightMedia({ children }: { children: React.ReactNode }) {
const [showModal, setShowModal] = useState(false);
function onClick(e: MouseEvent<HTMLDivElement>) {
e.stopPropagation();
e.preventDefault();
setShowModal(s => !s);
}
function onClose(e: MouseEvent<HTMLDivElement>) {
e.stopPropagation();
e.preventDefault();
setShowModal(false);
}
return (
<>
{showModal && (
<Modal onClose={onClose} className="spotlight">
<div className="close" onClick={onClose}>
<Icon name="close" />
</div>
{children}
</Modal>
)}
<div onClick={onClick}>{children}</div>
</>
);
}

View File

@ -1,31 +0,0 @@
import { ProxyImg } from "Element/ProxyImg";
interface MediaElementProps {
mime: string;
url: string;
magnet?: string;
sha256?: string;
blurHash?: string;
}
export function MediaElement(props: MediaElementProps) {
if (props.mime.startsWith("image/")) {
return <ProxyImg key={props.url} src={props.url} />;
} else if (props.mime.startsWith("audio/")) {
return <audio key={props.url} src={props.url} controls />;
} else if (props.mime.startsWith("video/")) {
return <video key={props.url} src={props.url} controls />;
} else {
return (
<a
key={props.url}
href={props.url}
onClick={e => e.stopPropagation()}
target="_blank"
rel="noreferrer"
className="ext">
{props.url}
</a>
);
}
}

View File

@ -1,32 +1,15 @@
import "./Modal.css";
import { useEffect, useRef } from "react";
import * as React from "react";
import { useEffect, MouseEventHandler, ReactNode } from "react";
export interface ModalProps {
className?: string;
onClose?: () => void;
children: React.ReactNode;
}
function useOnClickOutside(ref: React.MutableRefObject<Element | null>, onClickOutside: () => void) {
useEffect(() => {
function handleClickOutside(ev: MouseEvent) {
if (ref && ref.current && !ref.current.contains(ev.target as Node)) {
onClickOutside();
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);
onClose?: MouseEventHandler;
children: ReactNode;
}
export default function Modal(props: ModalProps) {
const ref = useRef(null);
const onClose = props.onClose || (() => undefined);
const className = props.className || "";
useOnClickOutside(ref, onClose);
useEffect(() => {
document.body.classList.add("scroll-lock");
@ -34,10 +17,8 @@ export default function Modal(props: ModalProps) {
}, []);
return (
<div className={`modal ${className}`}>
<div ref={ref} className="modal-body">
{props.children}
</div>
<div className={`modal ${className}`} onClick={onClose}>
<div className="modal-body">{props.children}</div>
</div>
);
}

View File

@ -5,7 +5,7 @@ import { findTag, NostrLink } from "Util";
import useEventFeed from "Feed/EventFeed";
import PageSpinner from "Element/PageSpinner";
import Reveal from "Element/Reveal";
import { MediaElement } from "Element/MediaLink";
import { MediaElement } from "Element/MediaElement";
export default function NostrFileHeader({ link }: { link: NostrLink }) {
const ev = useEventFeed(link);

View File

@ -3,7 +3,7 @@ import { FormattedMessage } from "react-intl";
import { FileExtensionRegex } from "Const";
import Reveal from "Element/Reveal";
import useLogin from "Hooks/useLogin";
import { MediaElement } from "Element/MediaLink";
import { MediaElement } from "Element/MediaElement";
interface RevealMediaProps {
creator: string;

View File

@ -417,27 +417,6 @@ div.form-col {
grid-template-columns: auto;
}
.modal {
position: absolute;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.8);
}
.modal .modal-content {
display: flex;
justify-content: center;
}
.modal .modal-content > div {
padding: 10px;
border-radius: 10px;
background-color: var(--gray);
margin-top: 5vh;
}
body.scroll-lock {
overflow: hidden;
height: 100vh;