1
0
mirror of https://github.com/spacebarchat/client.git synced 2024-11-25 19:52:31 +01:00

attachment previews

This commit is contained in:
Puyodead1 2023-08-30 21:52:52 -04:00
parent f946108b21
commit 35d9bbfa6e
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
4 changed files with 99 additions and 53 deletions

View File

@ -1,3 +1,4 @@
import { useModals } from "@mattjennings/react-modal-stack";
import { MessageType } from "@spacebarchat/spacebar-api-types/v9"; import { MessageType } from "@spacebarchat/spacebar-api-types/v9";
import React from "react"; import React from "react";
import Moment from "react-moment"; import Moment from "react-moment";
@ -6,15 +7,13 @@ import { ContextMenuContext } from "../../contexts/ContextMenuContext";
import useLogger from "../../hooks/useLogger"; import useLogger from "../../hooks/useLogger";
import { QueuedMessage } from "../../stores/MessageQueue"; import { QueuedMessage } from "../../stores/MessageQueue";
import { default as MessageObject } from "../../stores/objects/Message"; import { default as MessageObject } from "../../stores/objects/Message";
import { calculateImageRatio, calculateScaledDimensions } from "../../utils/Message";
import { calendarStrings } from "../../utils/i18n"; import { calendarStrings } from "../../utils/i18n";
import Avatar from "../Avatar"; import Avatar from "../Avatar";
import { Link } from "../Link"; import { Link } from "../Link";
import AttachmentPreviewModal from "../modals/AttachmentPreviewModal";
import { IContextMenuItem } from "./../ContextMenuItem"; import { IContextMenuItem } from "./../ContextMenuItem";
// max width/height for images
const maxWidth = 400;
const maxHeight = 300;
type MessageLike = MessageObject | QueuedMessage; type MessageLike = MessageObject | QueuedMessage;
const MessageListItem = styled.li` const MessageListItem = styled.li`
@ -68,43 +67,6 @@ const MessageAttachment = styled.div`
cursor: pointer; cursor: pointer;
`; `;
function calculateImageRatio(width: number, height: number) {
let o = 1;
width > maxWidth && (o = maxWidth / width);
width = Math.round(width * o);
let a = 1;
(height = Math.round(height * o)) > maxHeight && (a = maxHeight / height);
return Math.min(o * a, 1);
}
function calculateScaledDimensions(
originalWidth: number,
originalHeight: number,
ratio: number,
): { scaledWidth: number; scaledHeight: number } {
const deviceResolution = window.devicePixelRatio ?? 1;
let scaledWidth = originalWidth;
let scaledHeight = originalHeight;
if (ratio < 1) {
scaledWidth = Math.round(originalWidth * ratio);
scaledHeight = Math.round(originalHeight * ratio);
}
scaledWidth = Math.min(scaledWidth, maxWidth);
scaledHeight = Math.min(scaledHeight, maxHeight);
if (scaledWidth !== originalWidth || scaledHeight !== originalHeight) {
scaledWidth |= 0;
scaledHeight |= 0;
}
scaledWidth *= deviceResolution;
scaledHeight *= deviceResolution;
return { scaledWidth, scaledHeight };
}
// converts URLs in a string to html links // converts URLs in a string to html links
const Linkify = ({ children }: { children: string }) => { const Linkify = ({ children }: { children: string }) => {
const urlPattern = /\bhttps?:\/\/\S+\b\/?/g; const urlPattern = /\bhttps?:\/\/\S+\b\/?/g;
@ -146,6 +108,7 @@ interface Props {
*/ */
function Message({ message, isHeader, isSending, isFailed }: Props) { function Message({ message, isHeader, isSending, isFailed }: Props) {
const logger = useLogger("Message.tsx"); const logger = useLogger("Message.tsx");
const { openModal } = useModals();
const contextMenu = React.useContext(ContextMenuContext); const contextMenu = React.useContext(ContextMenuContext);
const [contextMenuItems, setContextMenuItems] = React.useState<IContextMenuItem[]>([ const [contextMenuItems, setContextMenuItems] = React.useState<IContextMenuItem[]>([
{ {
@ -285,6 +248,9 @@ function Message({ message, isHeader, isSending, isFailed }: Props) {
], ],
}); });
}} }}
onClick={() => {
openModal(AttachmentPreviewModal, { attachment });
}}
> >
{a} {a}
</MessageAttachment> </MessageAttachment>

View File

@ -0,0 +1,37 @@
import { APIAttachment } from "@spacebarchat/spacebar-api-types/v9";
import { calculateImageRatio, calculateScaledDimensions } from "../../utils/Message";
import { Modal } from "./ModalComponents";
import { AnimatedModalProps } from "./ModalRenderer";
const SCALE_FACTOR = 3.5;
interface Props extends AnimatedModalProps {
attachment: APIAttachment;
}
function AttachmentPreviewModal(props: Props) {
const width = props.attachment.width ?? 0;
const height = props.attachment.height ?? 0;
const maxWidth = 400 * SCALE_FACTOR;
const maxHeight = 300 * SCALE_FACTOR;
const ratio = calculateImageRatio(width, height, maxWidth, maxHeight);
const { scaledWidth, scaledHeight } = calculateScaledDimensions(width, height, ratio, maxWidth, maxHeight);
return (
<Modal
full
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: "transparent",
}}
{...props}
>
<img src={props.attachment.url} width={scaledWidth} height={scaledHeight} />
</Modal>
);
}
export default AttachmentPreviewModal;

View File

@ -1,4 +1,4 @@
import { type StackedModalProps } from "@mattjennings/react-modal-stack"; import { useModals, type StackedModalProps } from "@mattjennings/react-modal-stack";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
import React from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
@ -16,16 +16,6 @@ export const ModalBase = styled(motion.div)`
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
// &::before {
// content: "";
// position: absolute;
// top: 0;
// left: 0;
// right: 0;
// bottom: 0;
// background-color: black;
// opacity: 0.85;
// }
`; `;
/** /**
@ -182,9 +172,13 @@ export const ModalFullContent = styled.div`
interface ModalProps extends StackedModalProps { interface ModalProps extends StackedModalProps {
children: React.ReactNode; children: React.ReactNode;
full?: boolean; full?: boolean;
onClick?: () => void;
style?: React.CSSProperties;
} }
export function Modal(props: ModalProps) { export function Modal(props: ModalProps) {
const { closeModal } = useModals();
return ( return (
<AnimatePresence> <AnimatePresence>
{props.open && ( {props.open && (
@ -202,9 +196,14 @@ export function Modal(props: ModalProps) {
initial="hide" initial="hide"
animate="show" animate="show"
exit="hide" exit="hide"
onClick={() => {
closeModal();
}}
{...props} {...props}
> >
<ModalWrapper full={props.full}>{props.children}</ModalWrapper> <ModalWrapper full={props.full} style={props.style}>
{props.children}
</ModalWrapper>
</ModalBase> </ModalBase>
)} )}
</AnimatePresence> </AnimatePresence>

44
src/utils/Message.ts Normal file
View File

@ -0,0 +1,44 @@
export function calculateImageRatio(width: number, height: number, maxWidth?: number, maxHeight?: number) {
const mw = maxWidth ?? 400;
const mh = maxHeight ?? 300;
let o = 1;
width > mw && (o = mw / width);
width = Math.round(width * o);
let a = 1;
(height = Math.round(height * o)) > mh && (a = mh / height);
return Math.min(o * a, 1);
}
export function calculateScaledDimensions(
originalWidth: number,
originalHeight: number,
ratio: number,
maxWidth?: number,
maxHeight?: number,
): { scaledWidth: number; scaledHeight: number } {
const mw = maxWidth ?? 400;
const mh = maxHeight ?? 300;
const deviceResolution = window.devicePixelRatio ?? 1;
let scaledWidth = originalWidth;
let scaledHeight = originalHeight;
if (ratio < 1) {
scaledWidth = Math.round(originalWidth * ratio);
scaledHeight = Math.round(originalHeight * ratio);
}
scaledWidth = Math.min(scaledWidth, mw);
scaledHeight = Math.min(scaledHeight, mh);
if (scaledWidth !== originalWidth || scaledHeight !== originalHeight) {
scaledWidth |= 0;
scaledHeight |= 0;
}
scaledWidth *= deviceResolution;
scaledHeight *= deviceResolution;
return { scaledWidth, scaledHeight };
}