From 079e7d526d63ff755c1d8ae689c7c457337a001a Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 29 May 2023 18:13:38 +0200 Subject: [PATCH] task locking --- src/Pages/GroupTasks/GroupTasksViewModal.js | 65 +++++++++++++++------ src/utils.js | 56 ++++++++++++++---- 2 files changed, 91 insertions(+), 30 deletions(-) diff --git a/src/Pages/GroupTasks/GroupTasksViewModal.js b/src/Pages/GroupTasks/GroupTasksViewModal.js index 9b92f02..898b101 100644 --- a/src/Pages/GroupTasks/GroupTasksViewModal.js +++ b/src/Pages/GroupTasks/GroupTasksViewModal.js @@ -1,6 +1,5 @@ import { Alert, - Avatar, Button, Form, Input, @@ -10,10 +9,9 @@ import { Result, Steps, Tag, - Tooltip, notification, } from "antd"; -import { useContext } from "react"; +import { useContext, useRef, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Constants, @@ -23,12 +21,9 @@ import { GetDuration, MyAvatar, getUserId, + setNativeValue, } from "../../utils"; -import { - AntDesignOutlined, - InfoCircleOutlined, - LockOutlined, -} from "@ant-design/icons"; +import { InfoCircleOutlined, LockOutlined } from "@ant-design/icons"; export default function GroupTasksViewModal({ isOpen }) { const webSocketContext = useContext(WebSocketContext); @@ -102,7 +97,6 @@ export default function GroupTasksViewModal({ isOpen }) { case Constants.GROUP_TASKS_STATUS.RUNNING: return "process"; case Constants.GROUP_TASKS_STATUS.CANCELED: - return "error"; case Constants.GROUP_TASKS_STATUS.FAILED: return "error"; case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED: @@ -145,7 +139,10 @@ export default function GroupTasksViewModal({ isOpen }) { } taskInputs.push({ - parameterName: specifiedTaskInputs[i].id, + parameterName: + specifiedTaskInputs[i].id.split( + "-" + )[6] /* Format: UUID-STEP-PARAMETER_NAME */, value: specifiedTaskInputs[i].value, }); } @@ -528,6 +525,8 @@ function InputRequiredHandler({ step, taskLockedByUserId, }) { + const [inputFields, setInputFields] = useState({}); + const globalInputs = JSON.parse(currentGroupTask.GlobalInputs); const getDefaultValue = (groupTaskParameter) => { @@ -552,18 +551,29 @@ function InputRequiredHandler({ ); }; - let lastChanges = []; + let lastChanges = useRef([]); + let typingTimer = useRef(); - const onInputChange = (currentGroupTaskId) => { - lastChanges = lastChanges.filter( + const onInputChange = ( + inputValue, + currentGroupTaskId, + groupTaskParameterName + ) => { + setInputFields((fields) => { + return ({ ...fields }[groupTaskParameterName] = inputValue); + }); + + if (taskLockedByUserId !== "") return; + + lastChanges.current = lastChanges.current.filter( (lc) => Date.now() - new Date(lc.changeTime) < Constants.GROUP_TASK_LOCKED_TIME ); - const lastChange = lastChanges.find((lC) => lC.step === step); + const lastChange = lastChanges.current.find((lC) => lC.step === step); if (lastChange === undefined) { - lastChanges.push({ + lastChanges.current.push({ step: step, changeTime: Date.now(), }); @@ -574,6 +584,19 @@ function InputRequiredHandler({ step: step, }); } + + // sync input value with other web clients + clearTimeout(typingTimer.current); + + typingTimer.current = setTimeout(() => { + webSocketContext.SendSocketMessage( + SentMessagesCommands.UpdateGroupTaskStepUserInputValue, + { + element: `${currentGroupTaskId}-${step}-${groupTaskParameterName}`, + value: inputValue, + } + ); + }, 500); }; return ( @@ -592,15 +615,17 @@ function InputRequiredHandler({ > + onChange={(e) => onInputChange( + e.target.value, currentGroupTask.Id, groupTaskParameter.parameterName ) } + value={inputFields[groupTaskParameter.parameterName]} /> ); @@ -616,12 +641,14 @@ function InputRequiredHandler({ > + max={Number.MAX_SAFE_INTEGER} + onChange={(e) => onInputChange( + e, currentGroupTask.Id, groupTaskParameter.parameterName ) diff --git a/src/utils.js b/src/utils.js index bbb18eb..a13ce51 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,6 @@ import { UserOutlined } from "@ant-design/icons"; import { Avatar, Badge, Tooltip } from "antd"; -import { createContext, useEffect, useRef, useState } from "react"; +import { createContext, useContext, useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Buffer } from "buffer"; @@ -111,6 +111,7 @@ const ReceivedMessagesCommands = { UpdateScannerLastUsed: 15, TaskLocked: 16, TaskUnlocked: 17, + UpdateGroupTaskStepUserInputValue: 18, }; // commands sent to the backend server @@ -120,6 +121,7 @@ export const SentMessagesCommands = { TaskContinueTaskStep: 3, ReloadGroupTasks: 4, TaskLocking: 5, + UpdateGroupTaskStepUserInputValue: 6, }; export function WebSocketProvider({ @@ -146,21 +148,19 @@ export function WebSocketProvider({ const navigate = useNavigate(); const StartGroupTasksOpenModalRememberIdRef = useRef(null); - let socket = null; + //let socket = null; const ws = useRef(null); const connect = () => { - socket = new WebSocket(Constants.WS_ADDRESS + "?auth=" + userSession); + ws.current = new WebSocket(Constants.WS_ADDRESS + "?auth=" + userSession); - ws.current = socket; - - socket.onopen = () => { + ws.current.onopen = () => { console.log("connected"); setConnectionBadgeStatus("success"); setIsReady(true); }; - socket.onmessage = (event) => { + ws.current.onmessage = (event) => { const data = JSON.parse(event.data); console.log("received message", data); @@ -301,7 +301,6 @@ export function WebSocketProvider({ break; case ReceivedMessagesCommands.UpdateScannerLastUsed: - console.log("Received", body); setScanners((arr) => { const newArr = [...arr]; @@ -313,7 +312,6 @@ export function WebSocketProvider({ }); break; case ReceivedMessagesCommands.TaskLocked: - console.log("taskLocked", body); setGroupTasksSteps((arr) => { const newArr = [...arr]; @@ -343,13 +341,21 @@ export function WebSocketProvider({ return newArr; }); break; + case ReceivedMessagesCommands.UpdateGroupTaskStepUserInputValue: + const foundInput = document.getElementById(body.element); + + if (foundInput) { + setNativeValue(foundInput, body.value); + } + break; + default: console.error("unknown command", cmd); break; } }; - socket.onclose = (event) => { + ws.current.onclose = (event) => { setIsReady(false); setConnectionBadgeStatus("error"); console.log("closed", event); @@ -374,7 +380,8 @@ export function WebSocketProvider({ useEffect(() => { connect(); - return () => socket.close(); + //return () => socket.close(); + return () => ws.current.close(); }, []); const SendSocketMessage = (cmd, body) => { @@ -406,6 +413,33 @@ export function WebSocketProvider({ ); } +/* +function doEvent(obj, event) { + var event = new Event(event, { target: obj, bubbles: true }); + event.simulated = true; + + let tracker = obj._valueTracker; + if (tracker) { + tracker.setValue(lastValue); + } + + return obj ? obj.dispatchEvent(event) : false; +} */ + +// https://stackoverflow.com/a/52486921 +function setNativeValue(element, value) { + let lastValue = element.value; + element.value = value; + let event = new Event("input", { target: element, bubbles: true }); + // React 15 + event.simulated = true; + // React 16 + let tracker = element._valueTracker; + if (tracker) { + tracker.setValue(lastValue); + } + element.dispatchEvent(event); +} export function FormatDatetime(datetime) { if (datetime === "0001-01-01T00:00:00Z") {