diff --git a/.env.development b/.env.development index 3743930..58cf9aa 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,3 @@ - -VITE_API_FETCH_URL=https://pc-003.techform-dom.local/SmartMenuiserieTemplate/Service.svc/GetSVGLayoutConfiguration -VITE_API_POST_URL=https://pc-003.techform-dom.local/SmartMenuiserieTemplate/Service.svc/ApplicationState \ No newline at end of file +VITE_API_FETCH_URL=http://localhost:5000 +VITE_API_SET_CONTAINER_LIST_URL=http://localhost:5000/SetContainerList +VITE_API_GET_FEEDBACK_URL=http://localhost:5000/GetFeedback \ No newline at end of file diff --git a/.env.production b/.env.production index 3743930..4eb6fb6 100644 --- a/.env.production +++ b/.env.production @@ -1,3 +1,3 @@ - -VITE_API_FETCH_URL=https://pc-003.techform-dom.local/SmartMenuiserieTemplate/Service.svc/GetSVGLayoutConfiguration -VITE_API_POST_URL=https://pc-003.techform-dom.local/SmartMenuiserieTemplate/Service.svc/ApplicationState \ No newline at end of file +VITE_API_FETCH_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/GetSVGLayoutConfiguration +VITE_API_SET_CONTAINER_LIST_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/SetContainerList +VITE_API_GET_FEEDBACK_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/GetFeedback \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..58cb1d4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "eslint.rules.customizations": [ + { "rule": "*", "severity": "warn" } + ] +} \ No newline at end of file diff --git a/public/workers/message_worker.js b/public/workers/message_worker.js new file mode 100644 index 0000000..1996d83 --- /dev/null +++ b/public/workers/message_worker.js @@ -0,0 +1,63 @@ +const DELAY_BEFORE_SEND = 200; +const queue = []; +let messagePacket = []; +onmessage = async(e) => { + let packetLength + + const url = e.data.url; + const state = e.data.state; + const request = { + ApplicationState: state + }; + const dataParsed = JSON.stringify(request, GetCircularReplacerKeepDataStructure()); + queue.push(request); + fetch(url, { + method: 'POST', + headers: new Headers({ + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Content-Type': 'application/json' + }), + body: dataParsed + }) + .then((response) => + response.json() + ) + .then(async(json) => { + messagePacket.push.apply(messagePacket, json.messages); + queue.pop(); + + // The sleep allow the message packet to be filled by + // others requests before being sent as a single batch + // Reducing the wait time will reduce latency but increase error rate + let doLoop = true; + do { + packetLength = messagePacket.length; + await sleep(DELAY_BEFORE_SEND); + const newPacketLength = messagePacket.length; + doLoop = newPacketLength !== packetLength; + packetLength = newPacketLength; + } while (doLoop); + + if (queue.length <= 0 && messagePacket.length > 0) { + console.debug(`[GetFeedback] Packet size before sent: ${messagePacket.length}`) + postMessage(messagePacket) + messagePacket.splice(0); + } + }); +}; + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +function GetCircularReplacerKeepDataStructure() +{ + return (key, value) => { + if (key === 'parent') { + return; + } + + return value; + }; +} + diff --git a/src/Components/API/api.ts b/src/Components/API/api.ts index ac6b52c..986b3e3 100644 --- a/src/Components/API/api.ts +++ b/src/Components/API/api.ts @@ -33,7 +33,7 @@ export async function FetchConfiguration(): Promise { } export async function SetContainerList(request: ISetContainerListRequest): Promise { - const url = import.meta.env.VITE_API_POST_URL; + const url = import.meta.env.VITE_API_SET_CONTAINER_LIST_URL; const dataParsed = JSON.stringify(request, GetCircularReplacerKeepDataStructure()); // The test library cannot use the Fetch API // @ts-expect-error diff --git a/src/Components/Bar/Bar.tsx b/src/Components/Bar/Bar.tsx index 7125f30..ecfb20e 100644 --- a/src/Components/Bar/Bar.tsx +++ b/src/Components/Bar/Bar.tsx @@ -1,4 +1,4 @@ -import { ClockIcon, CubeIcon, LinkIcon } from '@heroicons/react/outline'; +import { ClockIcon, CubeIcon, LinkIcon, MailIcon } from '@heroicons/react/outline'; import * as React from 'react'; import { BarIcon } from './BarIcon'; @@ -7,9 +7,11 @@ interface IBarProps { isSymbolsOpen: boolean isElementsSidebarOpen: boolean isHistoryOpen: boolean + isMessagesOpen: boolean toggleSidebar: () => void toggleSymbols: () => void toggleTimeline: () => void + toggleMessages: () => void } export const BAR_WIDTH = 64; // 4rem @@ -35,6 +37,12 @@ export function Bar(props: IBarProps): JSX.Element { onClick={() => props.toggleTimeline()}> + props.toggleMessages()}> + + ); } diff --git a/src/Components/MessagesSidebar/MessagesSidebar.tsx b/src/Components/MessagesSidebar/MessagesSidebar.tsx new file mode 100644 index 0000000..c166e18 --- /dev/null +++ b/src/Components/MessagesSidebar/MessagesSidebar.tsx @@ -0,0 +1,100 @@ +import { TrashIcon } from '@heroicons/react/outline'; +import * as React from 'react'; +import { FixedSizeList as List } from 'react-window'; +import { MessageType } from '../../Enums/MessageType'; +import { IHistoryState } from '../../Interfaces/IHistoryState'; +import { IMessage } from '../../Interfaces/IMessage'; + +interface IMessagesSidebarProps { + historyState: IHistoryState + isOpen: boolean +} + +const myWorker = new Worker('workers/message_worker.js'); + +function UseWorker( + state: IHistoryState, + messages: IMessage[], + setMessages: React.Dispatch> +): void { + React.useEffect(() => { + // use webworker for the stringify to avoid freezing + myWorker.postMessage({ + state, + url: import.meta.env.VITE_API_GET_FEEDBACK_URL + }); + + return () => { + }; + }, [state]); + + React.useEffect(() => { + myWorker.onmessage = (event) => { + setMessages(messages.concat(event.data as IMessage[])); + }; + }, [messages, setMessages]); +} + +export function MessagesSidebar(props: IMessagesSidebarProps): JSX.Element { + const [messages, setMessages] = React.useState([]); + + UseWorker( + props.historyState, + messages, + setMessages + ); + + function Row({ index, style }: {index: number, style: React.CSSProperties}): JSX.Element { + const reversedIndex = (messages.length - 1) - index; + const message = messages[reversedIndex]; + let classType = ''; + switch (message.type) { + case MessageType.Success: + classType = 'bg-green-400 hover:bg-green-400/60'; + break; + + case MessageType.Warning: + classType = 'bg-yellow-400 hover:bg-yellow-400/60'; + break; + + case MessageType.Error: + classType = 'bg-red-400 hover:bg-red-400/60'; + break; + } + return (

+ {message.text} +

); + } + + const isOpenClasses = props.isOpen ? 'left-16' : '-left-64'; + return ( +
+
+ Messages + +
+ + {Row} + +
+ ); +}; diff --git a/src/Components/UI/UI.tsx b/src/Components/UI/UI.tsx index f232434..3561453 100644 --- a/src/Components/UI/UI.tsx +++ b/src/Components/UI/UI.tsx @@ -12,6 +12,7 @@ import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol'; import { Symbols } from '../Symbols/Symbols'; import { SymbolsSidebar } from '../SymbolsSidebar/SymbolsSidebar'; import { PropertyType } from '../../Enums/PropertyType'; +import { MessagesSidebar } from '../MessagesSidebar/MessagesSidebar'; interface IUIProps { selectedContainer: IContainerModel | undefined @@ -36,16 +37,19 @@ interface IUIProps { function CloseOtherSidebars( setIsSidebarOpen: React.Dispatch>, - setIsSymbolsOpen: React.Dispatch> + setIsSymbolsOpen: React.Dispatch>, + setIsMessagesOpen: React.Dispatch> ): void { setIsSidebarOpen(false); setIsSymbolsOpen(false); + setIsMessagesOpen(false); } export function UI(props: IUIProps): JSX.Element { const [isSidebarOpen, setIsSidebarOpen] = React.useState(true); const [isSymbolsOpen, setIsSymbolsOpen] = React.useState(false); const [isHistoryOpen, setIsHistoryOpen] = React.useState(false); + const [isMessagesOpen, setIsMessagesOpen] = React.useState(false); let buttonRightOffsetClasses = 'right-12'; if (isSidebarOpen || isHistoryOpen || isSymbolsOpen) { @@ -62,15 +66,20 @@ export function UI(props: IUIProps): JSX.Element { isSymbolsOpen={isSymbolsOpen} isElementsSidebarOpen={isSidebarOpen} isHistoryOpen={isHistoryOpen} + isMessagesOpen={isMessagesOpen} toggleSidebar={() => { - CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen); + CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen, setIsMessagesOpen); setIsSidebarOpen(!isSidebarOpen); } } toggleSymbols={() => { - CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen); + CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen, setIsMessagesOpen); setIsSymbolsOpen(!isSymbolsOpen); } } - toggleTimeline={() => setIsHistoryOpen(!isHistoryOpen)} /> + toggleTimeline={() => setIsHistoryOpen(!isHistoryOpen)} + toggleMessages={() => { + CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen, setIsMessagesOpen); + setIsMessagesOpen(!isMessagesOpen); + } }/> + { response.setHeader('Content-Type', 'application/json'); const url = request.url; let json; - if (url === '/ApplicationState') { + if (url === '/SetContainerList') { const buffers = []; for await (const chunk of request) { buffers.push(chunk);