fix(ImgViewer): Migrate `ImgViewer` from hooks to component

This commit is contained in:
haitaoo 2023-04-20 16:00:27 +08:00
parent 61cb84ac06
commit d3f02a35d9
12 changed files with 127 additions and 117 deletions

View File

@ -8,6 +8,7 @@ import {
} from 'react';
import { markdownToHtml } from '@/services';
import ImgViewer from '@/components/ImgViewer';
import { htmlRender } from './utils';
@ -48,11 +49,13 @@ const Index = ({ value }, ref) => {
});
return (
<div
ref={previewRef}
className="preview-wrap position-relative p-3 bg-light rounded text-break text-wrap mt-2 fmt"
dangerouslySetInnerHTML={{ __html: html }}
/>
<ImgViewer>
<div
ref={previewRef}
className="preview-wrap position-relative p-3 bg-light rounded text-break text-wrap mt-2 fmt"
dangerouslySetInnerHTML={{ __html: html }}
/>
</ImgViewer>
);
};

View File

@ -0,0 +1,7 @@
.img-viewer .cursor-zoom-out {
cursor: zoom-out !important;
}
.img-viewer img:not(a img, img.broken) {
cursor: zoom-in;
}

View File

@ -1,14 +1,13 @@
import { useLayoutEffect, useState, MouseEvent, useEffect } from 'react';
import { FC, MouseEvent, ReactNode, useEffect, useState } from 'react';
import { Modal } from 'react-bootstrap';
import { useLocation } from 'react-router-dom';
import ReactDOM from 'react-dom/client';
import './index.css';
import classnames from 'classnames';
const div = document.createElement('div');
const root = ReactDOM.createRoot(div);
const useImgViewer = () => {
const location = useLocation();
const Index: FC<{
children: ReactNode;
className?: classnames.Argument;
}> = ({ children, className }) => {
const [visible, setVisible] = useState(false);
const [imgSrc, setImgSrc] = useState('');
const onClose = () => {
@ -47,8 +46,18 @@ const useImgViewer = () => {
}
};
useLayoutEffect(() => {
root.render(
useEffect(() => {
return () => {
onClose();
};
}, []);
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
<div
className={classnames('img-viewer', className)}
onClick={checkClickForImgView}>
{children}
<Modal
show={visible}
fullscreen
@ -56,23 +65,16 @@ const useImgViewer = () => {
scrollable
contentClassName="bg-transparent"
onHide={onClose}>
<Modal.Body onClick={onClose} className="p-0 d-flex">
<Modal.Body onClick={onClose} className="img-viewer p-0 d-flex">
<img
className="cursor-zoom-out img-fluid m-auto"
src={imgSrc}
alt={imgSrc}
/>
</Modal.Body>
</Modal>,
);
});
useEffect(() => {
onClose();
}, [location]);
return {
onClose,
checkClickForImgView,
};
</Modal>
</div>
);
};
export default useImgViewer;
export default Index;

View File

@ -39,6 +39,7 @@ import QuestionList from './QuestionList';
import HotQuestions from './HotQuestions';
import HttpErrorContent from './HttpErrorContent';
import CustomSidebar from './CustomSidebar';
import ImgViewer from './ImgViewer';
export {
Avatar,
@ -84,5 +85,6 @@ export {
HotQuestions,
HttpErrorContent,
CustomSidebar,
ImgViewer,
};
export type { EditorRef, JSONSchema, UISchema };

View File

@ -10,7 +10,6 @@ import useChangePasswordModal from './useChangePasswordModal';
import usePageTags from './usePageTags';
import useLoginRedirect from './useLoginRedirect';
import usePromptWithUnload from './usePrompt';
import useImgViewer from './useImgViewer';
export {
useTagModal,
@ -25,5 +24,4 @@ export {
usePageTags,
useLoginRedirect,
usePromptWithUnload,
useImgViewer,
};

View File

@ -120,14 +120,6 @@ a {
cursor: pointer;
}
.cursor-zoom-out {
cursor: zoom-out !important;
}
img:not(a img, img.broken) {
cursor: zoom-in;
}
.resize-none {
resize: none;
}

View File

@ -1,7 +1,7 @@
import { FC, memo, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { JSONSchema, SchemaForm, UISchema } from '@/components';
import { JSONSchema, SchemaForm, UISchema, ImgViewer } from '@/components';
import { FormDataType } from '@/common/interface';
import { brandSetting, getBrandSetting } from '@/services';
import { brandingStore } from '@/stores';
@ -142,7 +142,7 @@ const Index: FC = () => {
}, []);
return (
<div>
<ImgViewer>
<h3 className="mb-4">{t('page_title')}</h3>
<SchemaForm
schema={schema}
@ -151,7 +151,7 @@ const Index: FC = () => {
onSubmit={onSubmit}
onChange={handleOnChange}
/>
</div>
</ImgViewer>
);
};

View File

@ -146,6 +146,9 @@ const Index: FC = () => {
useEffect(() => {
getUsersSetting().then((resp) => {
if (!resp) {
return;
}
const formMeta: Type.FormDataType = {};
Object.keys(formData).forEach((k) => {
let v = resp[k];

View File

@ -15,7 +15,6 @@ import {
HttpErrorContent,
} from '@/components';
import { LoginToContinueModal } from '@/components/Modal';
import { useImgViewer } from '@/hooks';
const Layout: FC = () => {
const location = useLocation();
@ -24,7 +23,6 @@ const Layout: FC = () => {
toastClear();
};
const { code: httpStatusCode, reset: httpStatusReset } = errorCodeStore();
const imgViewer = useImgViewer();
const { show: showLoginToContinueModal } = loginToContinueStore();
useEffect(() => {
@ -40,9 +38,7 @@ const Layout: FC = () => {
}}>
<Header />
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div
className="position-relative page-wrap"
onClick={imgViewer.checkClickForImgView}>
<div className="position-relative page-wrap">
{httpStatusCode ? (
<HttpErrorContent httpCode={httpStatusCode} />
) : (

View File

@ -12,6 +12,7 @@ import {
FormatTime,
htmlRender,
Icon,
ImgViewer,
} from '@/components';
import { formatCount, guard } from '@/utils';
import { following } from '@/services';
@ -114,11 +115,13 @@ const Index: FC<Props> = ({ data, initPage, hasAnswer, isLogged }) => {
return <Tag className="m-1" key={item.slug_name} data={item} />;
})}
</div>
<article
ref={ref}
className="fmt text-break text-wrap mt-4"
dangerouslySetInnerHTML={{ __html: data?.html }}
/>
<ImgViewer>
<article
ref={ref}
className="fmt text-break text-wrap mt-4"
dangerouslySetInnerHTML={{ __html: data?.html }}
/>
</ImgViewer>
<Actions
className="mt-4"

View File

@ -5,7 +5,7 @@ import { Trans, useTranslation } from 'react-i18next';
import MD5 from 'md5';
import type { FormDataType } from '@/common/interface';
import { UploadImg, Avatar, Icon } from '@/components';
import { UploadImg, Avatar, Icon, ImgViewer } from '@/components';
import { loggedUserInfoStore, userCenterStore, siteInfoStore } from '@/stores';
import { useToast } from '@/hooks';
import {
@ -27,7 +27,6 @@ const Index: React.FC = () => {
const [mailHash, setMailHash] = useState('');
const [count] = useState(0);
const [profileAgent, setProfileAgent] = useState<UcSettingAgent>();
const [formData, setFormData] = useState<FormDataType>({
display_name: {
value: '',
@ -343,66 +342,68 @@ const Index: React.FC = () => {
</option>
</Form.Select>
</div>
<div className="d-flex">
{formData.avatar.type === 'gravatar' && (
<Stack>
<Avatar
size="160px"
avatar={formData.avatar.gravatar}
searchStr={`s=256&d=identicon${
count > 0 ? `&t=${new Date().valueOf()}` : ''
}`}
className="me-3 rounded"
/>
<Form.Text className="text-muted mt-1">
<Trans i18nKey="settings.profile.avatar.gravatar_text">
You can change image on
<a
href="https://gravatar.com"
target="_blank"
rel="noreferrer">
gravatar.com
</a>
</Trans>
</Form.Text>
</Stack>
)}
{formData.avatar.type === 'custom' && (
<Stack>
<Stack direction="horizontal" className="align-items-start">
<ImgViewer>
<div className="d-flex">
{formData.avatar.type === 'gravatar' && (
<Stack>
<Avatar
size="160px"
searchStr="s=256"
avatar={formData.avatar.custom}
className="me-2 bg-gray-300 "
avatar={formData.avatar.gravatar}
searchStr={`s=256&d=identicon${
count > 0 ? `&t=${new Date().valueOf()}` : ''
}`}
className="me-3 rounded"
/>
<ButtonGroup vertical className="fit-content">
<UploadImg
type="avatar"
disabled={!usersSetting.allow_update_avatar}
uploadCallback={avatarUpload}>
<Icon name="cloud-upload" />
</UploadImg>
<Button
variant="outline-secondary"
disabled={!usersSetting.allow_update_avatar}
onClick={removeCustomAvatar}>
<Icon name="trash" />
</Button>
</ButtonGroup>
<Form.Text className="text-muted mt-1">
<Trans i18nKey="settings.profile.avatar.gravatar_text">
You can change image on
<a
href="https://gravatar.com"
target="_blank"
rel="noreferrer">
gravatar.com
</a>
</Trans>
</Form.Text>
</Stack>
<Form.Text className="text-muted mt-1">
<Trans i18nKey="settings.profile.avatar.text">
You can upload your image.
</Trans>
</Form.Text>
</Stack>
)}
{formData.avatar.type === 'default' && (
<Avatar size="160px" avatar="" />
)}
</div>
)}
{formData.avatar.type === 'custom' && (
<Stack>
<Stack direction="horizontal" className="align-items-start">
<Avatar
size="160px"
searchStr="s=256"
avatar={formData.avatar.custom}
className="me-2 bg-gray-300 "
/>
<ButtonGroup vertical className="fit-content">
<UploadImg
type="avatar"
disabled={!usersSetting.allow_update_avatar}
uploadCallback={avatarUpload}>
<Icon name="cloud-upload" />
</UploadImg>
<Button
variant="outline-secondary"
disabled={!usersSetting.allow_update_avatar}
onClick={removeCustomAvatar}>
<Icon name="trash" />
</Button>
</ButtonGroup>
</Stack>
<Form.Text className="text-muted mt-1">
<Trans i18nKey="settings.profile.avatar.text">
You can upload your image.
</Trans>
</Form.Text>
</Stack>
)}
{formData.avatar.type === 'default' && (
<Avatar size="160px" avatar="" />
)}
</div>
</ImgViewer>
<Form.Control
isInvalid={formData.avatar.isInvalid}
className="d-none"

View File

@ -13,6 +13,16 @@ interface SiteInfoType {
updateUsers: (users: SiteInfoType['users']) => void;
}
const defaultUsersConf: AdminSettingsUsers = {
allow_update_avatar: false,
allow_update_bio: false,
allow_update_display_name: false,
allow_update_location: false,
allow_update_username: false,
allow_update_website: false,
default_avatar: 'system',
};
const siteInfo = create<SiteInfoType>((set) => ({
siteInfo: {
name: DEFAULT_SITE_NAME,
@ -22,15 +32,7 @@ const siteInfo = create<SiteInfoType>((set) => ({
contact_email: '',
permalink: 1,
},
users: {
allow_update_avatar: false,
allow_update_bio: false,
allow_update_display_name: false,
allow_update_location: false,
allow_update_username: false,
allow_update_website: false,
default_avatar: 'system',
},
users: defaultUsersConf,
version: '',
revision: '',
update: (params) =>
@ -50,6 +52,7 @@ const siteInfo = create<SiteInfoType>((set) => ({
},
updateUsers: (users) => {
set(() => {
users ||= defaultUsersConf;
return { users };
});
},