feat(ImgViewer): Add the ability to click on an image to view it full screen
This commit is contained in:
@@ -9,6 +9,7 @@ import useUserModal from './useUserModal';
|
||||
import useChangePasswordModal from './useChangePasswordModal';
|
||||
import usePageTags from './usePageTags';
|
||||
import usePromptWithUnload from './usePrompt';
|
||||
import useImgViewer from './useImgViewer';
|
||||
|
||||
export {
|
||||
useTagModal,
|
||||
@@ -22,4 +23,5 @@ export {
|
||||
useChangePasswordModal,
|
||||
usePageTags,
|
||||
usePromptWithUnload,
|
||||
useImgViewer,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { useLayoutEffect, useState, MouseEvent } from 'react';
|
||||
import { Modal } from 'react-bootstrap';
|
||||
|
||||
import ReactDOM from 'react-dom/client';
|
||||
|
||||
const div = document.createElement('div');
|
||||
const root = ReactDOM.createRoot(div);
|
||||
|
||||
const useImgViewer = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [imgSrc, setImgSrc] = useState('');
|
||||
const onClose = () => {
|
||||
setVisible(false);
|
||||
setImgSrc('');
|
||||
};
|
||||
|
||||
const checkIfInLink = (target) => {
|
||||
let ret = false;
|
||||
let el = target.parentElement;
|
||||
while (el) {
|
||||
if (el.nodeName.toLowerCase() === 'a') {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
el = el.parentElement;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
const checkClickForImgView = (evt: MouseEvent<HTMLElement>) => {
|
||||
const { target } = evt;
|
||||
// @ts-ignore
|
||||
if (target.nodeName.toLowerCase() !== 'img') {
|
||||
return;
|
||||
}
|
||||
const img = target as HTMLImageElement;
|
||||
if (!img.naturalWidth || !img.naturalHeight) {
|
||||
return;
|
||||
}
|
||||
const src = img.currentSrc || img.src;
|
||||
if (src && checkIfInLink(img) === false) {
|
||||
setImgSrc(src);
|
||||
setVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
root.render(
|
||||
<Modal
|
||||
show={visible}
|
||||
fullscreen
|
||||
centered
|
||||
scrollable
|
||||
contentClassName="bg-transparent"
|
||||
onHide={onClose}>
|
||||
<Modal.Body>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-element-interactions */}
|
||||
<img
|
||||
className="cursor-zoom-out img-fluid position-absolute top-50 start-50 translate-middle"
|
||||
src={imgSrc}
|
||||
alt={imgSrc}
|
||||
onClick={onClose}
|
||||
/>
|
||||
</Modal.Body>
|
||||
</Modal>,
|
||||
);
|
||||
});
|
||||
return {
|
||||
onClose,
|
||||
checkClickForImgView,
|
||||
};
|
||||
};
|
||||
|
||||
export default useImgViewer;
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
// https://gist.github.com/chaance/2f3c14ec2351a175024f62fd6ba64aa6
|
||||
// The link above is an example of implementing usePromt with useBlocer.
|
||||
// The link above is an example of implementing usePrompt with useBlocker.
|
||||
interface PromptProps {
|
||||
when: boolean;
|
||||
beforeUnload?: boolean;
|
||||
|
||||
@@ -120,6 +120,10 @@ a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cursor-zoom-out {
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
.resize-none {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
@@ -14,12 +14,14 @@ import {
|
||||
PageTags,
|
||||
} from '@/components';
|
||||
import { LoginToContinueModal } from '@/components/Modal';
|
||||
import { useImgViewer } from '@/hooks';
|
||||
|
||||
const Layout: FC = () => {
|
||||
const { msg: toastMsg, variant, clear: toastClear } = toastStore();
|
||||
const closeToast = () => {
|
||||
toastClear();
|
||||
};
|
||||
const imgViewer = useImgViewer();
|
||||
const { show: showLoginToContinueModal } = loginToContinueStore();
|
||||
return (
|
||||
<HelmetProvider>
|
||||
@@ -30,7 +32,10 @@ const Layout: FC = () => {
|
||||
revalidateOnFocus: false,
|
||||
}}>
|
||||
<Header />
|
||||
<div className="position-relative page-wrap">
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
|
||||
<div
|
||||
className="position-relative page-wrap"
|
||||
onClick={imgViewer.checkClickForImgView}>
|
||||
<Outlet />
|
||||
</div>
|
||||
<Toast msg={toastMsg} variant={variant} onClose={closeToast} />
|
||||
|
||||
@@ -18,7 +18,7 @@ const Index: FC<Props> = ({ visible, introduction, data }) => {
|
||||
<h5 className="mb-3">{t('about_me')}</h5>
|
||||
{introduction ? (
|
||||
<div
|
||||
className="mb-4 text-break"
|
||||
className="mb-4 text-break fmt"
|
||||
dangerouslySetInnerHTML={{ __html: introduction }}
|
||||
/>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user