mirror of https://gitee.com/answerdev/answer.git
fix(ImgViewer): Migrate `ImgViewer` from hooks to component
This commit is contained in:
parent
61cb84ac06
commit
d3f02a35d9
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
.img-viewer .cursor-zoom-out {
|
||||
cursor: zoom-out !important;
|
||||
}
|
||||
|
||||
.img-viewer img:not(a img, img.broken) {
|
||||
cursor: zoom-in;
|
||||
}
|
|
@ -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;
|
|
@ -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 };
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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} />
|
||||
) : (
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 };
|
||||
});
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue