mirror of
https://github.com/usememos/memos.git
synced 2024-11-11 07:24:18 +03:00
feat: view all images of a memo (#393)
* feat: view all images of a memo * fix: function arguments * refactor: unified image preview * refactor: image preview for resource dialog Co-authored-by: XQ <qiaobingxue1998@163.com>
This commit is contained in:
parent
bebcabc292
commit
91a61e058a
@ -1,4 +1,4 @@
|
|||||||
import showPreviewImageCarouselDialog from "./PreviewImageCarouselDialog";
|
import showPreviewImageDialog from "./PreviewImageDialog";
|
||||||
import "../less/image.less";
|
import "../less/image.less";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -11,7 +11,7 @@ const Image: React.FC<Props> = (props: Props) => {
|
|||||||
const { className, imgUrls, index } = props;
|
const { className, imgUrls, index } = props;
|
||||||
|
|
||||||
const handleImageClick = () => {
|
const handleImageClick = () => {
|
||||||
showPreviewImageCarouselDialog(imgUrls, index);
|
showPreviewImageDialog(imgUrls, index);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -161,9 +161,15 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (targetEl.tagName === "IMG") {
|
} else if (targetEl.tagName === "IMG") {
|
||||||
const imgUrl = targetEl.getAttribute("src");
|
const currImgUrl = targetEl.getAttribute("src");
|
||||||
if (imgUrl) {
|
|
||||||
showPreviewImageDialog(imgUrl);
|
if (currImgUrl) {
|
||||||
|
// use regex to get all image urls from memo content
|
||||||
|
const imageUrls = memo.content.match(/!\[.*?\]\((.*?)\)/g)?.map((item) => item.match(/\((.*?)\)/)?.[1] ?? "") ?? [];
|
||||||
|
showPreviewImageDialog(
|
||||||
|
imageUrls,
|
||||||
|
imageUrls.findIndex((item) => item === currImgUrl)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
import * as utils from "../helpers/utils";
|
|
||||||
import Icon from "./Icon";
|
|
||||||
import { generateDialog } from "./Dialog";
|
|
||||||
import "../less/preview-image-carousel-dialog.less";
|
|
||||||
|
|
||||||
interface Props extends DialogProps {
|
|
||||||
imgUrls: string[];
|
|
||||||
index: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PreviewImageDialog: React.FC<Props> = ({ destroy, imgUrls, index }: Props) => {
|
|
||||||
const handleCloseBtnClick = () => {
|
|
||||||
destroy();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDownloadBtnClick = () => {
|
|
||||||
const a = document.createElement("a");
|
|
||||||
a.href = imgUrls[index];
|
|
||||||
a.download = `memos-${utils.getDateTimeString(Date.now())}.png`;
|
|
||||||
a.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImgContainerClick = () => {
|
|
||||||
destroy();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePrevBtnClick = () => {
|
|
||||||
destroy();
|
|
||||||
if (index > 0) {
|
|
||||||
showPreviewImageCarouselDialog(imgUrls, index - 1);
|
|
||||||
} else {
|
|
||||||
showPreviewImageCarouselDialog(imgUrls, imgUrls.length - 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNextBtnClick = () => {
|
|
||||||
destroy();
|
|
||||||
if (index < imgUrls.length - 1) {
|
|
||||||
showPreviewImageCarouselDialog(imgUrls, index + 1);
|
|
||||||
} else {
|
|
||||||
showPreviewImageCarouselDialog(imgUrls, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="btns-container">
|
|
||||||
<button className="btn" onClick={handleCloseBtnClick}>
|
|
||||||
<Icon.X className="icon-img" />
|
|
||||||
</button>
|
|
||||||
<button className="btn" onClick={handleDownloadBtnClick}>
|
|
||||||
<Icon.Download className="icon-img" />
|
|
||||||
</button>
|
|
||||||
<button className="btn" onClick={handlePrevBtnClick}>
|
|
||||||
<Icon.ArrowLeft className="icon-img" />
|
|
||||||
</button>
|
|
||||||
<button className="btn" onClick={handleNextBtnClick}>
|
|
||||||
<Icon.ArrowRight className="icon-img" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="img-container" onClick={handleImgContainerClick}>
|
|
||||||
<img onClick={(e) => e.stopPropagation()} src={imgUrls[index]} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function showPreviewImageCarouselDialog(imgUrls: string[], index: number): void {
|
|
||||||
generateDialog(
|
|
||||||
{
|
|
||||||
className: "preview-image-carousel-dialog",
|
|
||||||
},
|
|
||||||
PreviewImageDialog,
|
|
||||||
{ imgUrls, index }
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,26 +1,42 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import "../less/preview-image-dialog.less";
|
import "../less/preview-image-dialog.less";
|
||||||
|
|
||||||
interface Props extends DialogProps {
|
interface Props extends DialogProps {
|
||||||
imgUrl: string;
|
imgUrls: string[];
|
||||||
|
initialIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PreviewImageDialog: React.FC<Props> = ({ destroy, imgUrl }: Props) => {
|
const PreviewImageDialog: React.FC<Props> = ({ destroy, imgUrls, initialIndex }: Props) => {
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
||||||
|
|
||||||
const handleCloseBtnClick = () => {
|
const handleCloseBtnClick = () => {
|
||||||
destroy();
|
destroy();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownloadBtnClick = () => {
|
const handleDownloadBtnClick = () => {
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.href = imgUrl;
|
a.href = imgUrls[currentIndex];
|
||||||
a.download = `memos-${utils.getDateTimeString(Date.now())}.png`;
|
a.download = `memos-${utils.getDateTimeString(Date.now())}.png`;
|
||||||
a.click();
|
a.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImgContainerClick = () => {
|
const handleImgContainerClick = (event: React.MouseEvent) => {
|
||||||
destroy();
|
if (event.clientX < window.innerWidth / 2) {
|
||||||
|
if (currentIndex > 0) {
|
||||||
|
setCurrentIndex(currentIndex - 1);
|
||||||
|
} else {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currentIndex < imgUrls.length - 1) {
|
||||||
|
setCurrentIndex(currentIndex + 1);
|
||||||
|
} else {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -34,18 +50,21 @@ const PreviewImageDialog: React.FC<Props> = ({ destroy, imgUrl }: Props) => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="img-container" onClick={handleImgContainerClick}>
|
<div className="img-container" onClick={handleImgContainerClick}>
|
||||||
<img onClick={(e) => e.stopPropagation()} src={imgUrl} />
|
<img onClick={(e) => e.stopPropagation()} src={imgUrls[currentIndex]} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function showPreviewImageDialog(imgUrl: string): void {
|
export default function showPreviewImageDialog(imgUrls: string[] | string, initialIndex?: number): void {
|
||||||
generateDialog(
|
generateDialog(
|
||||||
{
|
{
|
||||||
className: "preview-image-dialog",
|
className: "preview-image-dialog",
|
||||||
},
|
},
|
||||||
PreviewImageDialog,
|
PreviewImageDialog,
|
||||||
{ imgUrl }
|
{
|
||||||
|
imgUrls: Array.isArray(imgUrls) ? imgUrls : [imgUrls],
|
||||||
|
initialIndex: initialIndex || 0,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -83,10 +83,17 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
|||||||
inputEl.click();
|
inputEl.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getResouceUrl = useCallback((resource: Resource) => {
|
||||||
|
return `${window.location.origin}/o/r/${resource.id}/${resource.filename}`;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handlePreviewBtnClick = (resource: Resource) => {
|
const handlePreviewBtnClick = (resource: Resource) => {
|
||||||
const resourceUrl = `${window.location.origin}/o/r/${resource.id}/${resource.filename}`;
|
const resourceUrl = getResouceUrl(resource);
|
||||||
if (resource.type.startsWith("image")) {
|
if (resource.type.startsWith("image")) {
|
||||||
showPreviewImageDialog(resourceUrl);
|
showPreviewImageDialog(
|
||||||
|
resources.filter((r) => r.type.startsWith("image")).map((r) => getResouceUrl(r)),
|
||||||
|
resources.findIndex((r) => r.id === resource.id)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
window.open(resourceUrl);
|
window.open(resourceUrl);
|
||||||
}
|
}
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
@import "./mixin.less";
|
|
||||||
|
|
||||||
.preview-image-carousel-dialog {
|
|
||||||
@apply p-0;
|
|
||||||
z-index: 101;
|
|
||||||
background-color: rgba(0, 0, 0, 0.6);
|
|
||||||
|
|
||||||
> .dialog-container {
|
|
||||||
@apply flex flex-col justify-center items-center relative w-full h-full p-0;
|
|
||||||
background-color: unset;
|
|
||||||
|
|
||||||
> .btns-container {
|
|
||||||
@apply fixed top-8 right-8 flex flex-col justify-start items-center;
|
|
||||||
|
|
||||||
> .btn {
|
|
||||||
@apply mb-3 last:mb-0 w-8 h-8 p-1 cursor-pointer rounded opacity-90 bg-gray-300 z-10 shadow-md hover:opacity-70;
|
|
||||||
|
|
||||||
> .icon-img {
|
|
||||||
@apply w-6 h-auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .img-container {
|
|
||||||
@apply w-full h-full p-4 flex flex-col justify-center items-center;
|
|
||||||
background-color: unset;
|
|
||||||
.hide-scroll-bar();
|
|
||||||
|
|
||||||
> img {
|
|
||||||
@apply h-auto w-auto max-w-full max-h-full shadow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user