[Update] Main Page: Add feature that the sent message be editable and resend-able from logs.
This commit is contained in:
@@ -1,9 +1,42 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import styles from "./MessageContainer.module.scss";
|
import styles from "./MessageContainer.module.scss";
|
||||||
|
import { MessageSubMenuContainer } from "./message_sub_menu_container/MessageSubMenuContainer";
|
||||||
|
import { useMessage } from "@logics_common";
|
||||||
export const MessageContainer = ({ messages, status, category, created_at }) => {
|
export const MessageContainer = ({ messages, status, category, created_at }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const {
|
||||||
|
sendMessage,
|
||||||
|
updateMessageInputValue,
|
||||||
|
} = useMessage();
|
||||||
|
|
||||||
|
const [is_hovered, setIsHovered] = useState(false);
|
||||||
|
const [is_locked, setIsLocked] = useState(false);
|
||||||
|
|
||||||
|
const resendFunction = () => {
|
||||||
|
sendMessage(messages.original);
|
||||||
|
};
|
||||||
|
const editFunction = () => {
|
||||||
|
updateMessageInputValue(messages.original);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
if (!is_locked) {
|
||||||
|
setIsHovered(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
setIsHovered(false);
|
||||||
|
setIsLocked(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const lockHoverState = () => {
|
||||||
|
setIsHovered(false);
|
||||||
|
setIsLocked(true);
|
||||||
|
};
|
||||||
|
|
||||||
const is_translated_exist = messages.translated.length >= 1;
|
const is_translated_exist = messages.translated.length >= 1;
|
||||||
const is_pending = status === "pending";
|
const is_pending = status === "pending";
|
||||||
@@ -16,18 +49,31 @@ export const MessageContainer = ({ messages, status, category, created_at }) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(styles.container, message_type_class_name)}>
|
<div
|
||||||
<div className={clsx(styles.info_box, message_type_class_name)}>
|
className={clsx(styles.container, message_type_class_name)}
|
||||||
<p className={styles.time}>{created_at}</p>
|
onMouseEnter={handleMouseEnter}
|
||||||
<p className={clsx(styles.category, message_type_class_name)}>{category_text}</p>
|
onMouseLeave={handleMouseLeave}
|
||||||
{is_sent_message && is_pending && <span className={styles.loader}></span>}
|
>
|
||||||
</div>
|
<div className={clsx(styles.message_wrapper, message_type_class_name)}>
|
||||||
<div className={clsx(styles.message_box, message_type_class_name)}>
|
<div className={clsx(styles.info_box, message_type_class_name)}>
|
||||||
{is_translated_exist
|
<p className={styles.time}>{created_at}</p>
|
||||||
? <WithTranslatedMessages messages={messages} />
|
<p className={clsx(styles.category, message_type_class_name)}>{category_text}</p>
|
||||||
: <p className={styles.message_main}>{messages.original}</p>
|
{is_sent_message && is_pending && <span className={styles.loader}></span>}
|
||||||
}
|
</div>
|
||||||
|
<div className={clsx(styles.message_box, message_type_class_name)}>
|
||||||
|
{is_translated_exist
|
||||||
|
? <WithTranslatedMessages messages={messages} />
|
||||||
|
: <p className={styles.message_main}>{messages.original}</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{is_sent_message && is_hovered ? (
|
||||||
|
<MessageSubMenuContainer
|
||||||
|
setIsHovered={lockHoverState}
|
||||||
|
resendFunction={resendFunction}
|
||||||
|
editFunction={editFunction}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
@import "@scss_mixins";
|
@import "@scss_mixins";
|
||||||
|
|
||||||
|
// ******************* *******************
|
||||||
|
// ******************* Express in "em" not "rem" *******************
|
||||||
|
// ******************* *******************
|
||||||
.container {
|
.container {
|
||||||
margin-bottom: 1em;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
user-select: text;
|
||||||
|
gap: 0.6rem;
|
||||||
|
&.sent_message:hover {
|
||||||
|
background-color: var(--dark_950_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_wrapper {
|
||||||
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: end;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
|
padding: 0.6em 0;
|
||||||
&.sent_message {
|
&.sent_message {
|
||||||
align-items: end;
|
align-items: end;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import React, { useState, useRef } from "react";
|
||||||
|
import Tooltip, { tooltipClasses } from '@mui/material/Tooltip';
|
||||||
|
import styles from "./MessageSubMenuContainer.module.scss";
|
||||||
|
import SendMessageSvg from "@images/send_message.svg?react";
|
||||||
|
import RefreshSvg from "@images/refresh_2.svg?react";
|
||||||
|
|
||||||
|
export const MessageSubMenuContainer = (props) => {
|
||||||
|
const [is_holding, setIsHolding] = useState(false);
|
||||||
|
const progressRef = useRef(null);
|
||||||
|
const holdTimeout = useRef(null);
|
||||||
|
|
||||||
|
const startHold = () => {
|
||||||
|
setIsHolding(true);
|
||||||
|
if (progressRef.current) {
|
||||||
|
progressRef.current.style.transition = "width 500ms linear";
|
||||||
|
progressRef.current.style.width = "100%";
|
||||||
|
}
|
||||||
|
holdTimeout.current = setTimeout(() => {
|
||||||
|
props.resendFunction();
|
||||||
|
props.setIsHovered(false);
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelHold = () => {
|
||||||
|
setIsHolding(false);
|
||||||
|
if (progressRef.current) {
|
||||||
|
progressRef.current.style.transition = "none";
|
||||||
|
progressRef.current.style.width = "0%";
|
||||||
|
}
|
||||||
|
clearTimeout(holdTimeout.current);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickFunction = () => {
|
||||||
|
props.editFunction();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const offset = {
|
||||||
|
popper: {
|
||||||
|
sx: {
|
||||||
|
[`&.${tooltipClasses.popper}[data-popper-placement*="top"] .${tooltipClasses.tooltip}`]: { marginBottom: "0.2em" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Tooltip
|
||||||
|
title={<Title_p />}
|
||||||
|
placement="top"
|
||||||
|
slotProps={offset}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className={styles.resend_button}
|
||||||
|
onMouseDown={startHold}
|
||||||
|
onMouseUp={cancelHold}
|
||||||
|
onMouseLeave={cancelHold}
|
||||||
|
onClick={onClickFunction}
|
||||||
|
>
|
||||||
|
<SendMessageSvg className={styles.send_message_svg} />
|
||||||
|
<RefreshSvg className={styles.refresh_svg} />
|
||||||
|
<div ref={progressRef} className={styles.hold_progress_bar}></div>
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Title_p = () => {
|
||||||
|
return <p className={styles.tooltip_title}>Press and hold to send</p>;
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// ******************* *******************
|
||||||
|
// ******************* Express in "em" not "rem" *******************
|
||||||
|
// ******************* *******************
|
||||||
|
.container {
|
||||||
|
}
|
||||||
|
.resend_button {
|
||||||
|
background-color: var(--dark_825_color);
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 3.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send_message_svg {
|
||||||
|
position: absolute;
|
||||||
|
top: 58%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 2.2em;
|
||||||
|
color: var(--dark_400_color);
|
||||||
|
}
|
||||||
|
.refresh_svg {
|
||||||
|
position: absolute;
|
||||||
|
top: 36%;
|
||||||
|
left: 42%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 1.8em;
|
||||||
|
color: var(--sent_400_color);
|
||||||
|
filter: drop-shadow(0.2em 0.2em 0 var(--dark_825_color));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip_title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--dark_basic_text_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hold_progress_bar {
|
||||||
|
position: absolute;
|
||||||
|
top: 10%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 0%;
|
||||||
|
height: 0.4em;
|
||||||
|
background-color: var(--sent_400_color);
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
@@ -7,10 +7,14 @@ import { store } from "@store";
|
|||||||
import { scrollToBottom } from "@utils";
|
import { scrollToBottom } from "@utils";
|
||||||
|
|
||||||
export const MessageInputBox = () => {
|
export const MessageInputBox = () => {
|
||||||
const [input_value, setInputValue] = useState("");
|
|
||||||
const [message_history, setMessageHistory] = useState([]);
|
const [message_history, setMessageHistory] = useState([]);
|
||||||
const [history_index, setHistoryIndex] = useState(-1);
|
const [history_index, setHistoryIndex] = useState(-1);
|
||||||
const { sendMessage, currentMessageLogs } = useMessage();
|
const {
|
||||||
|
sendMessage,
|
||||||
|
currentMessageLogs,
|
||||||
|
currentMessageInputValue,
|
||||||
|
updateMessageInputValue,
|
||||||
|
} = useMessage();
|
||||||
|
|
||||||
const { currentEnableAutoClearMessageInputBox } = useEnableAutoClearMessageInputBox();
|
const { currentEnableAutoClearMessageInputBox } = useEnableAutoClearMessageInputBox();
|
||||||
const { currentSendMessageButtonType } = useSendMessageButtonType();
|
const { currentSendMessageButtonType } = useSendMessageButtonType();
|
||||||
@@ -27,11 +31,11 @@ export const MessageInputBox = () => {
|
|||||||
const onSubmitFunction = (e) => {
|
const onSubmitFunction = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!input_value.trim()) return setInputValue("");
|
if (!currentMessageInputValue.data.trim()) return updateMessageInputValue("");
|
||||||
|
|
||||||
sendMessage(input_value);
|
sendMessage(currentMessageInputValue.data);
|
||||||
|
|
||||||
if (currentEnableAutoClearMessageInputBox.data) setInputValue("");
|
if (currentEnableAutoClearMessageInputBox.data) updateMessageInputValue("");
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
scrollToBottom(store.log_box_ref);
|
scrollToBottom(store.log_box_ref);
|
||||||
@@ -41,7 +45,7 @@ export const MessageInputBox = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onChangeFunction = (e) => {
|
const onChangeFunction = (e) => {
|
||||||
setInputValue(e.currentTarget.value);
|
updateMessageInputValue(e.currentTarget.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyDownFunction = (e) => {
|
const onKeyDownFunction = (e) => {
|
||||||
@@ -57,7 +61,7 @@ export const MessageInputBox = () => {
|
|||||||
if (history_index + 1 < message_history.length) {
|
if (history_index + 1 < message_history.length) {
|
||||||
const new_index = history_index + 1;
|
const new_index = history_index + 1;
|
||||||
setHistoryIndex(new_index);
|
setHistoryIndex(new_index);
|
||||||
setInputValue(message_history[message_history.length - 1 - new_index]);
|
updateMessageInputValue(message_history[message_history.length - 1 - new_index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +87,7 @@ export const MessageInputBox = () => {
|
|||||||
className={styles.message_box_input_area}
|
className={styles.message_box_input_area}
|
||||||
onChange={onChangeFunction}
|
onChange={onChangeFunction}
|
||||||
placeholder="Input Textfield"
|
placeholder="Input Textfield"
|
||||||
value={input_value}
|
value={currentMessageInputValue.data}
|
||||||
onKeyDown={onKeyDownFunction}
|
onKeyDown={onKeyDownFunction}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
1
src-ui/assets/refresh_2.svg
Normal file
1
src-ui/assets/refresh_2.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9 12l-4.463 4.969-4.537-4.969h3c0-4.97 4.03-9 9-9 2.395 0 4.565.942 6.179 2.468l-2.004 2.231c-1.081-1.05-2.553-1.699-4.175-1.699-3.309 0-6 2.691-6 6h3zm10.463-4.969l-4.463 4.969h3c0 3.309-2.691 6-6 6-1.623 0-3.094-.65-4.175-1.699l-2.004 2.231c1.613 1.526 3.784 2.468 6.179 2.468 4.97 0 9-4.03 9-9h3l-4.537-4.969z"/></svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
@@ -1,11 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
useStore_MessageLogs,
|
useStore_MessageLogs,
|
||||||
|
useStore_MessageInputValue,
|
||||||
} from "@store";
|
} from "@store";
|
||||||
|
|
||||||
import { useStdoutToPython } from "@logics/useStdoutToPython";
|
import { useStdoutToPython } from "@logics/useStdoutToPython";
|
||||||
|
|
||||||
export const useMessage = () => {
|
export const useMessage = () => {
|
||||||
const { currentMessageLogs, addMessageLogs, updateMessageLogs } = useStore_MessageLogs();
|
const { currentMessageLogs, addMessageLogs, updateMessageLogs } = useStore_MessageLogs();
|
||||||
|
const { currentMessageInputValue, updateMessageInputValue } = useStore_MessageInputValue();
|
||||||
const { asyncStdoutToPython } = useStdoutToPython();
|
const { asyncStdoutToPython } = useStdoutToPython();
|
||||||
|
|
||||||
const sendMessage = (message) => {
|
const sendMessage = (message) => {
|
||||||
@@ -46,6 +48,9 @@ export const useMessage = () => {
|
|||||||
updateSentMessageLogById,
|
updateSentMessageLogById,
|
||||||
addSentMessageLog,
|
addSentMessageLog,
|
||||||
addReceivedMessageLog,
|
addReceivedMessageLog,
|
||||||
|
|
||||||
|
currentMessageInputValue,
|
||||||
|
updateMessageInputValue,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export const { atomInstance: Atom_TranscriptionReceiveStatus, useHook: useStore_
|
|||||||
export const { atomInstance: Atom_ForegroundStatus, useHook: useStore_ForegroundStatus } = createAtomWithHook(false, "ForegroundStatus", {is_state_ok: true});
|
export const { atomInstance: Atom_ForegroundStatus, useHook: useStore_ForegroundStatus } = createAtomWithHook(false, "ForegroundStatus", {is_state_ok: true});
|
||||||
|
|
||||||
export const { atomInstance: Atom_MessageLogs, useHook: useStore_MessageLogs } = createAtomWithHook(generateTestData(20), "MessageLogs");
|
export const { atomInstance: Atom_MessageLogs, useHook: useStore_MessageLogs } = createAtomWithHook(generateTestData(20), "MessageLogs");
|
||||||
|
export const { atomInstance: Atom_MessageInputValue, useHook: useStore_MessageInputValue } = createAtomWithHook("", "MessageInputValue");
|
||||||
|
|
||||||
export const { atomInstance: Atom_SelectableLanguageList, useHook: useStore_SelectableLanguageList } = createAtomWithHook([], "SelectableLanguageList");
|
export const { atomInstance: Atom_SelectableLanguageList, useHook: useStore_SelectableLanguageList } = createAtomWithHook([], "SelectableLanguageList");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user