diff --git a/README.md b/README.md index d1f3ac6..6398cbb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -# SCWeb +# React sc-web ### Environment -The following .env example file is required for the correct work of the application. +The following .env example file is required for the correct work of the application. + +You can copy the file _.env.example_ and rename it to the _.env_ ``` PORT=3000 @@ -10,32 +12,70 @@ SC_URL="ws://localhost:8090/ws_json" API_URL="http://localhost:8000" ``` -### Installation +### Installation, build and run -The installation process should be done with `npm install`. +To start the project you need to do three steps: +1. Install ostis-web-platform with correct branches +2. Build ostis-web-platform +3. Install react-sc-web +4. Run ostis-web-platform and react-sc-web -### Running locally +#### Install ostis-web-platform with correct branches +Installation instructions you can find in the repository [**ostis-web-platform**](https://github.com/ostis-ai/ostis-web-platform). -Application starts with `npm start` on port 3000. +But you should use the following branches +- sc-machine - _main_ +- sc-web - _feature/add-scg-iframe_ +- ims.ostis.kb - _main_ -**IMPORTANT!!!** +To checkout them, use the following commands: +```sh +cd ostis-web-platform +cd sc-machine +git fetch origin main +git checkout FETCH_HEAD -Before start react-sc-web install and start -[**ostis-web-platform**](https://github.com/ostis-ai/ostis-web-platform). +cd ../ims.ostis.kb +git fetch origin main +git checkout FETCH_HEAD -To start the platform use next commands: +cd ../sc-web +git fetch origin feature/add-scg-iframe +git checkout FETCH_HEAD +``` +#### Build ostis-web-platform ```sh -# Launch knowledge processing machine cd ostis-web-platform -./scripts/run_sc_server.sh -# *or launch in docker* -docker compose run -p 8090:8090 machine +./scripts/build_sc_machine.sh +./scripts/build_sc_web.sh +./scripts/build_sc_kb.sh +``` + +#### Install react-sc-web +```sh +cd ostis-web-platform +git clone git@github.com:ostis-ai/react-sc-web.git +cd react-sc-web +npm install +``` + +#### Run ostis-web-platform and react-sc-web +```sh +cd ostis-web-platform +./scripts/run_sc_server.sh # *or launch in docker* docker compose run -p 8090:8090 machine + # *in another terminal* -# Launch semantic web interface +cd ostis-web-platform python3 sc-web/server/app.py --allowed_origins=http://localhost:3000 + +# *in another terminal* +cd ostis-web-platform/react-sc-web +npm start ``` +Application starts on port 3000. + ### Creating production build To create production build run `npm run build`.\ @@ -76,6 +116,5 @@ To analyze the code and find problems according to the given rules, described in ├── README.md ├── package-lock.json ├── package.json -├── tsconfig.json └── tsconfig.json ``` diff --git a/src/components/ConfirmAction/ConfirmAction.module.scss b/src/components/ConfirmAction/ConfirmAction.module.scss new file mode 100644 index 0000000..7d5785f --- /dev/null +++ b/src/components/ConfirmAction/ConfirmAction.module.scss @@ -0,0 +1,43 @@ +@import '~@assets/styles/variables/colors.scss'; + +.wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + gap: 24px; + + width: fit-content; + min-width: 380px; + height: fit-content; + + padding: 16px; + + background: $white; + + border: 1px solid $popup-border; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25); + border-radius: 12px; +} + +.title { + font-family: 'Roboto'; + font-style: normal; + font-weight: 500; + font-size: 18px; + line-height: 21px; + + color: $dark-grey; +} + +.buttons { + display: flex; + align-items: center; + justify-content: space-between; + + width: 100%; +} + +.button { + width: fit-content; +} diff --git a/src/components/ConfirmAction/ConfirmAction.tsx b/src/components/ConfirmAction/ConfirmAction.tsx new file mode 100644 index 0000000..bf5305c --- /dev/null +++ b/src/components/ConfirmAction/ConfirmAction.tsx @@ -0,0 +1,34 @@ +import { FC, ReactNode } from 'react'; +import { Button } from '@components/Button'; +import styles from './ConfirmAction.module.scss'; + +interface IProps { + onComplete: () => void; + onClose: () => void; + title: string; + content: ReactNode | string; + completeBtnText: string; + className?: string; +} + +export const ConfirmAction: FC = ({ onComplete, onClose, title, content, completeBtnText = 'Подтвердить' }) => { + const onCompleteFunc = () => { + onComplete(); + onClose(); + }; + + return ( +
+
{title}
+
{content}
+
+ + +
+
+ ); +}; diff --git a/src/components/ConfirmAction/index.tsx b/src/components/ConfirmAction/index.tsx new file mode 100644 index 0000000..f33829d --- /dev/null +++ b/src/components/ConfirmAction/index.tsx @@ -0,0 +1 @@ +export * from './ConfirmAction'; diff --git a/src/components/Scg/Scg.tsx b/src/components/Scg/Scg.tsx index 840ac39..68ccd0a 100644 --- a/src/components/Scg/Scg.tsx +++ b/src/components/Scg/Scg.tsx @@ -1,9 +1,13 @@ -import { Scg as UiKitScg, useToast } from 'ostis-ui-lib'; -import { FC, useCallback } from 'react'; +import { langToKeynode, snakeToCamelCase, useBooleanState, useLanguage, Popup } from 'ostis-ui-lib'; +import { FC, useEffect, useRef, useState } from 'react'; +import { scUtils } from '@api'; import { removeFromCache } from '@api/requests/scn'; -import { Notification } from '@components/Notification'; +import { ConfirmAction } from '@components/ConfirmAction'; import { scgUrl } from '@constants'; -import { useScNavigation } from '@hooks/useScNavigation'; +import { confirmClearScenePopupContent, confirmDeletePopupContent } from './constants'; +import { Frame, StyledSpinner, Wrap } from './styled'; + +import { EWindowEvents, ITarget, IWindowEventData } from './types'; interface IProps { question?: number; @@ -11,63 +15,94 @@ interface IProps { show?: boolean; } +const SPINER_COLOR = '#5896C0'; + export const Scg: FC = ({ question, className, show = false }) => { - const { goToActiveFormatCommand } = useScNavigation(); - const { addToast } = useToast(); + const [isReady, setIsready] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [targetNode, setTargetNode] = useState(null); + const [isConfirmDeletePopupShown, showConfirmDeletePopup, hideConfirmDeletePopup] = + useBooleanState(false); + const [isConfirmClearScenePopupShown, showConfirmClearScenePopup, hideConfirmClearScenePopup] = + useBooleanState(false); + const ref = useRef(null); + const targetRef = useRef(null); + const lang = useLanguage(); - const onUpdateScg = useCallback(() => { - if (question) removeFromCache(question); - }, [question]); + useEffect(() => { + const iframe = ref.current; + if (!iframe) return; - const onOpenFragment = useCallback( - (fragmentAddr: number) => { - goToActiveFormatCommand(fragmentAddr, 'ui_menu_view_get_user_fragment'); - }, - [goToActiveFormatCommand], - ); + window.onmessage = function (event: MessageEvent) { + switch (event.data.type) { + case EWindowEvents.onInitializationFinished: + setIsready(true); + setIsLoading(false); + break; + case EWindowEvents.deleteScgElement: + showConfirmDeletePopup(); + break; + case EWindowEvents.clearScene: + showConfirmClearScenePopup(); + break; + } + }; + }, [question]); - const onEmptyFragment = useCallback(() => { - addToast( - , - { - position: 'bottomRight', - duration: 2000, - }, - ); - }, [addToast]); + useEffect(() => { + (async () => { + if (!isReady || !show || !question) return; + const iframe = ref.current; + if (!iframe) return; + const { ...rest } = await scUtils.findKeynodes(langToKeynode[lang]); + const activeLangKeynode = rest[snakeToCamelCase(langToKeynode[lang])]; + removeFromCache(question); + iframe.contentWindow && + iframe.contentWindow.postMessage( + { type: 'renderScg', addr: question, lang: activeLangKeynode.value }, + '*', + ); + })(); + }, [isReady, question, show, lang]); - const onFullfilledFragment = useCallback(() => { - addToast( - , - { - position: 'bottomRight', - duration: 2000, - }, - ); - }, [addToast]); + targetRef.current = targetNode?.element || null; return ( - + <> + {isConfirmDeletePopupShown && ( + + + ref.current && + ref.current.contentWindow && + ref.current.contentWindow.postMessage({ type: 'deleteScgElement' }, '*') + } + onClose={hideConfirmDeletePopup} + title="Удаление" + content={confirmDeletePopupContent} + completeBtnText="Удалить" + /> + + )} + {isConfirmClearScenePopupShown && ( + + + ref.current && + ref.current.contentWindow && + ref.current.contentWindow.postMessage({ type: 'clearScene' }, '*') + } + onClose={hideConfirmClearScenePopup} + title="Очистка пространства" + content={confirmClearScenePopupContent} + completeBtnText="Очистить" + /> + + )} + + {isLoading && } + + + ); }; diff --git a/src/components/Scg/constants.tsx b/src/components/Scg/constants.tsx new file mode 100644 index 0000000..4ef5274 --- /dev/null +++ b/src/components/Scg/constants.tsx @@ -0,0 +1,16 @@ +import { Popup } from './styled'; + +export const confirmDeletePopupContent = ( + + Вы уверены, что хотите удалить выделенные элементы из базы знаний? + Это действие нельзя будет отменить после синхронизации изменений. + +); + +export const confirmClearScenePopupContent = ( + + В результе этого действия из рабочего пространства будут удалены все элементы. +
+ Это действие нельзя отменить. +
+); diff --git a/src/components/Scg/styled.ts b/src/components/Scg/styled.ts new file mode 100644 index 0000000..a07ee5b --- /dev/null +++ b/src/components/Scg/styled.ts @@ -0,0 +1,43 @@ +import { Spinner } from 'ostis-ui-lib'; +import styled from 'styled-components'; + +export const Wrap = styled.div<{ show?: boolean }>` + position: absolute; + height: calc(100vh - 80px - 36px); + + display: flex; + justify-content: center; + align-items: center; + + display: ${(props) => (props.show ? 'block' : 'none')}; +`; + +export const StyledSpinner = styled(Spinner)` + position: absolute; + left: 50%; + top: 50%; + + transform: translate3d(-50%, -50%, 0); +`; + +export const Frame = styled.iframe` + width: 100%; + height: 100%; + + margin: -7px; + + border: 0; +`; + +export const Popup = styled.div<{ isClear?: boolean }>` + width: ${(props) => (props.isClear ? '383px' : '344px')}; + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + font-size: 18px; + line-height: 21px; + color: #323232; + b { + font-weight: 500; + } +`; diff --git a/src/components/Scg/types.ts b/src/components/Scg/types.ts new file mode 100644 index 0000000..e4dc4e7 --- /dev/null +++ b/src/components/Scg/types.ts @@ -0,0 +1,15 @@ +export interface ITarget { + element: HTMLElement; + addr?: number; +} + +export interface IWindowEventData { + type: string; + payload?: Record; +} + +export const enum EWindowEvents { + deleteScgElement = 'deleteScgElement', + clearScene = 'clearScene', + onInitializationFinished = 'onInitializationFinished', +} diff --git a/src/components/ScgPage/ScgPage.module.scss b/src/components/ScgPage/ScgPage.module.scss index d2aacca..0219e2c 100644 --- a/src/components/ScgPage/ScgPage.module.scss +++ b/src/components/ScgPage/ScgPage.module.scss @@ -4,7 +4,7 @@ height: calc(100vh - 80px - 36px); top: 116px; right: 0; - z-index: 5; + z-index: 3; } .boundary { diff --git a/src/constants/common.ts b/src/constants/common.ts index 8ed6e13..c3a7b6c 100644 --- a/src/constants/common.ts +++ b/src/constants/common.ts @@ -32,4 +32,4 @@ export const defaultValues = { xmlData: '', }; -export const scgUrl = IS_DEV ? `${API_URL}/iframe/scg` : `${API_URL}/scg`; +export const scgUrl = `${API_URL}/scg`;