task locking

main
alex 2023-05-29 18:13:38 +02:00
parent 837e7ee23d
commit 079e7d526d
2 changed files with 91 additions and 30 deletions

View File

@ -1,6 +1,5 @@
import { import {
Alert, Alert,
Avatar,
Button, Button,
Form, Form,
Input, Input,
@ -10,10 +9,9 @@ import {
Result, Result,
Steps, Steps,
Tag, Tag,
Tooltip,
notification, notification,
} from "antd"; } from "antd";
import { useContext } from "react"; import { useContext, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { import {
Constants, Constants,
@ -23,12 +21,9 @@ import {
GetDuration, GetDuration,
MyAvatar, MyAvatar,
getUserId, getUserId,
setNativeValue,
} from "../../utils"; } from "../../utils";
import { import { InfoCircleOutlined, LockOutlined } from "@ant-design/icons";
AntDesignOutlined,
InfoCircleOutlined,
LockOutlined,
} from "@ant-design/icons";
export default function GroupTasksViewModal({ isOpen }) { export default function GroupTasksViewModal({ isOpen }) {
const webSocketContext = useContext(WebSocketContext); const webSocketContext = useContext(WebSocketContext);
@ -102,7 +97,6 @@ export default function GroupTasksViewModal({ isOpen }) {
case Constants.GROUP_TASKS_STATUS.RUNNING: case Constants.GROUP_TASKS_STATUS.RUNNING:
return "process"; return "process";
case Constants.GROUP_TASKS_STATUS.CANCELED: case Constants.GROUP_TASKS_STATUS.CANCELED:
return "error";
case Constants.GROUP_TASKS_STATUS.FAILED: case Constants.GROUP_TASKS_STATUS.FAILED:
return "error"; return "error";
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED: case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
@ -145,7 +139,10 @@ export default function GroupTasksViewModal({ isOpen }) {
} }
taskInputs.push({ taskInputs.push({
parameterName: specifiedTaskInputs[i].id, parameterName:
specifiedTaskInputs[i].id.split(
"-"
)[6] /* Format: UUID-STEP-PARAMETER_NAME */,
value: specifiedTaskInputs[i].value, value: specifiedTaskInputs[i].value,
}); });
} }
@ -528,6 +525,8 @@ function InputRequiredHandler({
step, step,
taskLockedByUserId, taskLockedByUserId,
}) { }) {
const [inputFields, setInputFields] = useState({});
const globalInputs = JSON.parse(currentGroupTask.GlobalInputs); const globalInputs = JSON.parse(currentGroupTask.GlobalInputs);
const getDefaultValue = (groupTaskParameter) => { const getDefaultValue = (groupTaskParameter) => {
@ -552,18 +551,29 @@ function InputRequiredHandler({
); );
}; };
let lastChanges = []; let lastChanges = useRef([]);
let typingTimer = useRef();
const onInputChange = (currentGroupTaskId) => { const onInputChange = (
lastChanges = lastChanges.filter( inputValue,
currentGroupTaskId,
groupTaskParameterName
) => {
setInputFields((fields) => {
return ({ ...fields }[groupTaskParameterName] = inputValue);
});
if (taskLockedByUserId !== "") return;
lastChanges.current = lastChanges.current.filter(
(lc) => (lc) =>
Date.now() - new Date(lc.changeTime) < Constants.GROUP_TASK_LOCKED_TIME 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) { if (lastChange === undefined) {
lastChanges.push({ lastChanges.current.push({
step: step, step: step,
changeTime: Date.now(), changeTime: Date.now(),
}); });
@ -574,6 +584,19 @@ function InputRequiredHandler({
step: step, 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 ( return (
@ -592,15 +615,17 @@ function InputRequiredHandler({
> >
<Input <Input
key={"input-" + groupTaskParameter.parameterName} key={"input-" + groupTaskParameter.parameterName}
id={groupTaskParameter.parameterName} id={`${currentGroupTask.Id}-${step}-${groupTaskParameter.parameterName}`}
defaultValue={getDefaultValue(groupTaskParameter)} defaultValue={getDefaultValue(groupTaskParameter)}
disabled={taskLockedByUserId !== ""} disabled={taskLockedByUserId !== ""}
onChange={() => onChange={(e) =>
onInputChange( onInputChange(
e.target.value,
currentGroupTask.Id, currentGroupTask.Id,
groupTaskParameter.parameterName groupTaskParameter.parameterName
) )
} }
value={inputFields[groupTaskParameter.parameterName]}
/> />
</Form.Item> </Form.Item>
); );
@ -616,12 +641,14 @@ function InputRequiredHandler({
> >
<InputNumber <InputNumber
key={"fitem-" + groupTaskParameter.parameterName} key={"fitem-" + groupTaskParameter.parameterName}
id={groupTaskParameter.parameterName} id={`${currentGroupTask.Id}-${step}-${groupTaskParameter.parameterName}`}
style={{ width: "100%" }} style={{ width: "100%" }}
defaultValue={getDefaultValue(groupTaskParameter)} defaultValue={getDefaultValue(groupTaskParameter)}
disabled={taskLockedByUserId !== ""} disabled={taskLockedByUserId !== ""}
onChange={() => max={Number.MAX_SAFE_INTEGER}
onChange={(e) =>
onInputChange( onInputChange(
e,
currentGroupTask.Id, currentGroupTask.Id,
groupTaskParameter.parameterName groupTaskParameter.parameterName
) )

View File

@ -1,6 +1,6 @@
import { UserOutlined } from "@ant-design/icons"; import { UserOutlined } from "@ant-design/icons";
import { Avatar, Badge, Tooltip } from "antd"; 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 { useNavigate } from "react-router-dom";
import { Buffer } from "buffer"; import { Buffer } from "buffer";
@ -111,6 +111,7 @@ const ReceivedMessagesCommands = {
UpdateScannerLastUsed: 15, UpdateScannerLastUsed: 15,
TaskLocked: 16, TaskLocked: 16,
TaskUnlocked: 17, TaskUnlocked: 17,
UpdateGroupTaskStepUserInputValue: 18,
}; };
// commands sent to the backend server // commands sent to the backend server
@ -120,6 +121,7 @@ export const SentMessagesCommands = {
TaskContinueTaskStep: 3, TaskContinueTaskStep: 3,
ReloadGroupTasks: 4, ReloadGroupTasks: 4,
TaskLocking: 5, TaskLocking: 5,
UpdateGroupTaskStepUserInputValue: 6,
}; };
export function WebSocketProvider({ export function WebSocketProvider({
@ -146,21 +148,19 @@ export function WebSocketProvider({
const navigate = useNavigate(); const navigate = useNavigate();
const StartGroupTasksOpenModalRememberIdRef = useRef(null); const StartGroupTasksOpenModalRememberIdRef = useRef(null);
let socket = null; //let socket = null;
const ws = useRef(null); const ws = useRef(null);
const connect = () => { const connect = () => {
socket = new WebSocket(Constants.WS_ADDRESS + "?auth=" + userSession); ws.current = new WebSocket(Constants.WS_ADDRESS + "?auth=" + userSession);
ws.current = socket; ws.current.onopen = () => {
socket.onopen = () => {
console.log("connected"); console.log("connected");
setConnectionBadgeStatus("success"); setConnectionBadgeStatus("success");
setIsReady(true); setIsReady(true);
}; };
socket.onmessage = (event) => { ws.current.onmessage = (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
console.log("received message", data); console.log("received message", data);
@ -301,7 +301,6 @@ export function WebSocketProvider({
break; break;
case ReceivedMessagesCommands.UpdateScannerLastUsed: case ReceivedMessagesCommands.UpdateScannerLastUsed:
console.log("Received", body);
setScanners((arr) => { setScanners((arr) => {
const newArr = [...arr]; const newArr = [...arr];
@ -313,7 +312,6 @@ export function WebSocketProvider({
}); });
break; break;
case ReceivedMessagesCommands.TaskLocked: case ReceivedMessagesCommands.TaskLocked:
console.log("taskLocked", body);
setGroupTasksSteps((arr) => { setGroupTasksSteps((arr) => {
const newArr = [...arr]; const newArr = [...arr];
@ -343,13 +341,21 @@ export function WebSocketProvider({
return newArr; return newArr;
}); });
break; break;
case ReceivedMessagesCommands.UpdateGroupTaskStepUserInputValue:
const foundInput = document.getElementById(body.element);
if (foundInput) {
setNativeValue(foundInput, body.value);
}
break;
default: default:
console.error("unknown command", cmd); console.error("unknown command", cmd);
break; break;
} }
}; };
socket.onclose = (event) => { ws.current.onclose = (event) => {
setIsReady(false); setIsReady(false);
setConnectionBadgeStatus("error"); setConnectionBadgeStatus("error");
console.log("closed", event); console.log("closed", event);
@ -374,7 +380,8 @@ export function WebSocketProvider({
useEffect(() => { useEffect(() => {
connect(); connect();
return () => socket.close(); //return () => socket.close();
return () => ws.current.close();
}, []); }, []);
const SendSocketMessage = (cmd, body) => { const SendSocketMessage = (cmd, body) => {
@ -406,6 +413,33 @@ export function WebSocketProvider({
</WebSocketContext.Provider> </WebSocketContext.Provider>
); );
} }
/*
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) { export function FormatDatetime(datetime) {
if (datetime === "0001-01-01T00:00:00Z") { if (datetime === "0001-01-01T00:00:00Z") {