mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-22 02:12:38 +01:00
attachment previews
This commit is contained in:
parent
f946108b21
commit
35d9bbfa6e
@ -1,3 +1,4 @@
|
||||
import { useModals } from "@mattjennings/react-modal-stack";
|
||||
import { MessageType } from "@spacebarchat/spacebar-api-types/v9";
|
||||
import React from "react";
|
||||
import Moment from "react-moment";
|
||||
@ -6,15 +7,13 @@ import { ContextMenuContext } from "../../contexts/ContextMenuContext";
|
||||
import useLogger from "../../hooks/useLogger";
|
||||
import { QueuedMessage } from "../../stores/MessageQueue";
|
||||
import { default as MessageObject } from "../../stores/objects/Message";
|
||||
import { calculateImageRatio, calculateScaledDimensions } from "../../utils/Message";
|
||||
import { calendarStrings } from "../../utils/i18n";
|
||||
import Avatar from "../Avatar";
|
||||
import { Link } from "../Link";
|
||||
import AttachmentPreviewModal from "../modals/AttachmentPreviewModal";
|
||||
import { IContextMenuItem } from "./../ContextMenuItem";
|
||||
|
||||
// max width/height for images
|
||||
const maxWidth = 400;
|
||||
const maxHeight = 300;
|
||||
|
||||
type MessageLike = MessageObject | QueuedMessage;
|
||||
|
||||
const MessageListItem = styled.li`
|
||||
@ -68,43 +67,6 @@ const MessageAttachment = styled.div`
|
||||
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
|
||||
const Linkify = ({ children }: { children: string }) => {
|
||||
const urlPattern = /\bhttps?:\/\/\S+\b\/?/g;
|
||||
@ -146,6 +108,7 @@ interface Props {
|
||||
*/
|
||||
function Message({ message, isHeader, isSending, isFailed }: Props) {
|
||||
const logger = useLogger("Message.tsx");
|
||||
const { openModal } = useModals();
|
||||
const contextMenu = React.useContext(ContextMenuContext);
|
||||
const [contextMenuItems, setContextMenuItems] = React.useState<IContextMenuItem[]>([
|
||||
{
|
||||
@ -285,6 +248,9 @@ function Message({ message, isHeader, isSending, isFailed }: Props) {
|
||||
],
|
||||
});
|
||||
}}
|
||||
onClick={() => {
|
||||
openModal(AttachmentPreviewModal, { attachment });
|
||||
}}
|
||||
>
|
||||
{a}
|
||||
</MessageAttachment>
|
||||
|
37
src/components/modals/AttachmentPreviewModal.tsx
Normal file
37
src/components/modals/AttachmentPreviewModal.tsx
Normal 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;
|
@ -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 React from "react";
|
||||
import styled from "styled-components";
|
||||
@ -16,16 +16,6 @@ export const ModalBase = styled(motion.div)`
|
||||
display: flex;
|
||||
justify-content: 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 {
|
||||
children: React.ReactNode;
|
||||
full?: boolean;
|
||||
onClick?: () => void;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export function Modal(props: ModalProps) {
|
||||
const { closeModal } = useModals();
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{props.open && (
|
||||
@ -202,9 +196,14 @@ export function Modal(props: ModalProps) {
|
||||
initial="hide"
|
||||
animate="show"
|
||||
exit="hide"
|
||||
onClick={() => {
|
||||
closeModal();
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<ModalWrapper full={props.full}>{props.children}</ModalWrapper>
|
||||
<ModalWrapper full={props.full} style={props.style}>
|
||||
{props.children}
|
||||
</ModalWrapper>
|
||||
</ModalBase>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
44
src/utils/Message.ts
Normal file
44
src/utils/Message.ts
Normal 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 };
|
||||
}
|
Loading…
Reference in New Issue
Block a user