From fc6a3d8f9c8de4e10bc35b5c9f733764d38ddb7b Mon Sep 17 00:00:00 2001 From: Pavel Torbeev Date: Sat, 9 Sep 2023 19:31:24 +0300 Subject: [PATCH] feat: add nearest type --- src/api/process/types.ts | 14 ++- src/components/Modal/Modal.tsx | 1 - src/hooks/usePreventWindowScroll.ts | 2 +- src/pages/response/ResponsePage.tsx | 34 +----- src/pages/text/TextPage.module.scss | 18 ++++ src/pages/text/TextPage.tsx | 109 ++++++++++++++++---- src/pages/text/utils/getEntriesFromText.ts | 4 +- src/pages/text/utils/getTextFromDetailed.ts | 14 +++ 8 files changed, 142 insertions(+), 54 deletions(-) create mode 100644 src/pages/text/utils/getTextFromDetailed.ts diff --git a/src/api/process/types.ts b/src/api/process/types.ts index a5f8e8f..d79b37c 100644 --- a/src/api/process/types.ts +++ b/src/api/process/types.ts @@ -1,9 +1,17 @@ -export type ScoreType = 'bert' | 'f'; +export type DetailFeatures = [answer: string, metric: number, texts: string[]]; + +export type DetailDescriptor = { + text: string; + features: DetailFeatures; +}; + +export type ScoreType = 'bert' | 'f' | 'nearest'; export type ScoreDescriptor = { [key in ScoreType]: { - text: string; answer: string; + metric?: number; + // detailed?: DetailDescriptor[]; }; }; @@ -15,7 +23,7 @@ export type TextDescriptor = { [key in ScoreType]?: { file?: string; pdf?: string; - text: string; + text: string | DetailDescriptor[]; }; } | null; diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index db4cf2f..f42a8eb 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -16,7 +16,6 @@ export interface ModalProps { export const Modal = (props: ModalProps) => { const { className, children, isOpen, onClose, isClosing, preventWindowScroll } = props; - const nodeRef = useRef(null); usePreventWindowScroll(preventWindowScroll ?? isOpen); diff --git a/src/hooks/usePreventWindowScroll.ts b/src/hooks/usePreventWindowScroll.ts index d3d493c..1177eb1 100644 --- a/src/hooks/usePreventWindowScroll.ts +++ b/src/hooks/usePreventWindowScroll.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect } from 'react'; export const usePreventWindowScroll = (isPrevented?: boolean) => { const preventScroll = useCallback((isPrevented: boolean) => { - document.body.classList.toggle('scroll-prevented', isPrevented); + document.documentElement.classList.toggle('scroll-prevented', isPrevented); }, []); useEffect(() => { diff --git a/src/pages/response/ResponsePage.tsx b/src/pages/response/ResponsePage.tsx index e49bdd4..6877662 100644 --- a/src/pages/response/ResponsePage.tsx +++ b/src/pages/response/ResponsePage.tsx @@ -69,7 +69,7 @@ export const ResponsePage: FC = () => { Имя М. н-с. М. стат. - {/*М. п.*/} + М. c. {/*Рез.*/} Крат. сод. @@ -88,6 +88,10 @@ export const ResponsePage: FC = () => { {text.score.f.answer} {/*| 0.99*/} + + {text.score.nearest.answer} + {/*| 0.99*/} + {/**/} {/* AA+ | 0.95*/} {/**/} @@ -107,34 +111,6 @@ export const ResponsePage: FC = () => { ))} - - {/**/} - {/* 1*/} - {/* {EMDASH}*/} - {/* */} - {/* AA+ | 0.63*/} - {/* */} - {/* */} - {/* AA+ | 0.95*/} - {/* */} - {/* */} - {/* AA+ | 0.95*/} - {/* */} - {/* */} - {/* AA+ | 0.95*/} - {/* */} - {/* */} - {/* {*/} - {/* e.stopPropagation();*/} - {/* setIsOpen(true);*/} - {/* }}>*/} - {/* Открыть*/} - {/* */} - {/* */} - {/**/} )} diff --git a/src/pages/text/TextPage.module.scss b/src/pages/text/TextPage.module.scss index 4878cc4..d1f8520 100644 --- a/src/pages/text/TextPage.module.scss +++ b/src/pages/text/TextPage.module.scss @@ -132,4 +132,22 @@ .TextPage__loader { --loader-size: 96px; color: $color-background-secondary !important; +} + +.TextPage__modalBody { + @include flex-col; + line-height: $line-height-24; +} + +.TextPage__modalParagraph { + margin-top: $spacing-small-3x; + padding-top: $spacing-small-3x; + border-top: 1px solid $color-divider-darker; +} + +:global { + .detailedText { + margin-bottom: 8px; + cursor: pointer; + } } \ No newline at end of file diff --git a/src/pages/text/TextPage.tsx b/src/pages/text/TextPage.tsx index 064680e..dcab7f1 100644 --- a/src/pages/text/TextPage.tsx +++ b/src/pages/text/TextPage.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useMemo, useRef } from 'react'; +import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { Heading, HeadingSize } from '../../components/Heading'; import { useUrlParam } from '../../hooks/useUrlParam'; @@ -7,10 +7,13 @@ import { ETextVariants, Text } from '../../components/Text'; import { Tooltip } from '../../components/Tooltip'; import { Link } from '../../components/Link'; import { useSingleTimeout } from '../../hooks/useSingleTimeout'; -import { ScoreType, useText } from '../../api/process'; +import { DetailDescriptor, ScoreType, useText } from '../../api/process'; import { Loader } from '../../components/Loader'; import { BACKEND_MEDIA_PORT, BACKEND_URL } from '../../config'; +import { getPercentageColor } from '../../utils/getPercentageColor'; +import { ModalBody, ModalContainer, useModal } from '../../components/Modal'; import { getEntriesFromText } from './utils/getEntriesFromText'; +import { getTextFromDetailed } from './utils/getTextFromDetailed'; import s from './TextPage.module.scss'; export type TextFields = { @@ -31,6 +34,7 @@ export const TextPage: FC = () => { }); const scoreType = watch('type'); + const isDetailedScoreType = !(['bert', 'f'] as ScoreType[]).includes(scoreType); const { data: textEntity, @@ -42,13 +46,19 @@ export const TextPage: FC = () => { config: { enabled: !!textId, refetchInterval: (data) => - data?.description?.[scoreType]?.file && data?.description?.[scoreType]?.pdf ? false : TEXT_REFETCH_MS + // !isDetailedScoreType && + !data?.description?.[scoreType]?.file || !data?.description?.[scoreType]?.pdf ? TEXT_REFETCH_MS : false } }); + console.log(textEntity?.description?.nearest?.text); + const parsedText = useMemo( - () => getEntriesFromText(textEntity?.description?.[scoreType]?.text || ''), - [scoreType, textEntity] + () => + isDetailedScoreType + ? getTextFromDetailed(textEntity?.description?.nearest?.text as DetailDescriptor[]) + : getEntriesFromText((textEntity?.description?.[scoreType]?.text as string) || ''), + [isDetailedScoreType, scoreType, textEntity] ); const docxHref = textEntity?.description?.[scoreType]?.file @@ -64,11 +74,26 @@ export const TextPage: FC = () => { const tipRef = useRef(null); const timeout = useSingleTimeout(); + // Модалка + const [isOpen, setIsOpen] = useState(false); + const [activeDetailedIndex, setActiveDetailedIndex] = useState(-1); + const onClose = useCallback(() => { + setIsOpen(false); + }, []); + const { modalIsVisible, isClosing, close } = useModal({ isOpen, onClose }); + const openDetailed = useCallback((index: number = -1) => { + setActiveDetailedIndex(index); + setIsOpen(true); + }, []); + const activeDetailed = (textEntity?.description?.nearest?.text as DetailDescriptor[])?.[activeDetailedIndex]; + useEffect(() => { if (!textRef.current) { return; } + // ------ Обработка хинтов ------ + const resetTip = () => { if (tipRef.current) { tipRef.current.style.opacity = `0`; @@ -84,7 +109,7 @@ export const TextPage: FC = () => { } }; - textRef.current.querySelectorAll('span').forEach((item) => { + textRef.current.querySelectorAll('.hintText').forEach((item) => { item.addEventListener('mouseover', () => { const rect = item.getBoundingClientRect(); const value = Number(item.getAttribute('data-value')); @@ -104,8 +129,18 @@ export const TextPage: FC = () => { window.addEventListener('scroll', resetTip); window.addEventListener('touchmove', resetTip); + + // ------ Обработка текста с похожими текстами ------ + + textRef.current.querySelectorAll('.detailedText').forEach((item) => { + item.addEventListener('click', (e) => { + const index = Number(item.getAttribute('data-index')) ?? -1; + openDetailed(index); + }); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps - }, [parsedText]); + }, [parsedText, openDetailed]); // ------ Обработка ошибки ------ @@ -147,13 +182,21 @@ export const TextPage: FC = () => { {/*| Accuracy: 0.71*/} - {/**/} - {/* Результат по методу{' '}*/} - {/* */} - {/* похожести*/} - {/* */} - {/* : АА+ | Accuracy: 0.63*/} - {/**/} + + Результат по методу{' '} + + схожести + + : {textEntity.score.nearest.answer} + {textEntity.score.nearest.metric && ( + <> + | Точность:{' '} + + {(textEntity.score.nearest.metric / 100).toFixed(2)} + + + )} +
@@ -177,7 +220,7 @@ export const TextPage: FC = () => { { Скачать PDF -
- -
+ {isDetailedScoreType ? ( + <> +
+ + ) : ( + <> +
+
+ + )}
) : ( @@ -209,6 +259,29 @@ export const TextPage: FC = () => {
)} + + + + {activeDetailed && ( + <> +

+ Рейтинг {activeDetailed.features[0]} +

+

+ Точность{' '} + + {(activeDetailed.features[1] / 100).toFixed(2)} + +

+ {activeDetailed.features[2].map((text, index) => ( +

+ {text} +

+ ))} + + )} +
+
); }; diff --git a/src/pages/text/utils/getEntriesFromText.ts b/src/pages/text/utils/getEntriesFromText.ts index 5703f3c..bb99e96 100644 --- a/src/pages/text/utils/getEntriesFromText.ts +++ b/src/pages/text/utils/getEntriesFromText.ts @@ -2,7 +2,7 @@ export const getColorFromValue = (value: number) => { return `rgba(255, 255, 0, ${value > 0.1 ? value + 0.2 : 0})`; }; -export const getEntriesFromText = (html: string) => { +export const getEntriesFromText = (html: string = '') => { let copiedHtml = html; const matches = Array.from(html.matchAll(/]+>(.*?)<\/span>/gi)); @@ -13,7 +13,7 @@ export const getEntriesFromText = (html: string) => { copiedHtml = copiedHtml.replace( match[0], - `${text}` ); diff --git a/src/pages/text/utils/getTextFromDetailed.ts b/src/pages/text/utils/getTextFromDetailed.ts new file mode 100644 index 0000000..82b9283 --- /dev/null +++ b/src/pages/text/utils/getTextFromDetailed.ts @@ -0,0 +1,14 @@ +import { DetailDescriptor } from '../../../api/process'; +import { getColorFromValue } from './getEntriesFromText'; + +export const getTextFromDetailed = (detailed: DetailDescriptor[] = []) => { + let html = ''; + + detailed.forEach((item, index) => { + const [_, metric] = item.features; + const color = getColorFromValue(metric / 100); + html += `

${item.text}

`; + }); + + return html; +};