-
Notifications
You must be signed in to change notification settings - Fork 102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: abnormal-width on text select #658
Conversation
Hi @stealth-bombeer, thank you for raising this PR. I will review it later when I have time so just take a break 😃. Would you mind changing the "keyword" in the PR description? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Nice work! @stealth-bombeer
Is this also fix #620 or not? I could not reproduce the issue. So, we might need Lam to give it a test. @tyn1998
No still #620 persists,once these changes are reviewed as they seems to be related I can start working on it 💯 |
That would be great. Appreciate it! Meanwhile, i am not sure if the |
Thanks @wxharry ,I think this would be a great start and will surely look after it 😄 |
That is not a big deal, don't worry :) BTW, as far as I know, you cannot change the PR branch name. That automatically closes a PR. And I'm reviewing your code, I'll give you feedback soon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @stealth-bombeer, thanks for your PR. It works good, yet can still be improved :)
I commented serveral lines of code. Besides, I'm wondering if it is necessary to put the code in a MutationObserver callback?
@@ -35,6 +35,8 @@ const View = ({ theme, currentRepo, currentDocsName }: Props): JSX.Element => { | |||
const [inited, setInited] = useState(false); | |||
const [settings, setSettings] = useState(new Settings()); | |||
const [history, setHistory] = useState<[string, string]>(['', '']); | |||
// Toggle to allow resizing of the widget | |||
const [resizable, setResizable] = useState(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resizable
doesn't need to be a React state. setResizable
is never used.
Actually we can make the chat-widget always resizable so do not need an option to toggle it. Just make it as default, no if else
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason why this needs to be placed in mutation callback is we watch for the changes in rcw-widget-container as this should be fired only when the container is active
let isDragging = false; | ||
let initialMouseX: number; | ||
let initialWidth: number; | ||
if (conversationResizer.length) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use exists
helper function to check if an element exsists instead.
|
||
if ($('.rcw-conversation-resizer')) { | ||
$('.rcw-conversation-resizer').off('mousedown'); | ||
$(document).off('mousemove mouseup'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will .off
remove all listeners (including GitHub's own listeners) for mousemove and mouseup?
Maybe you can move the listeners more accurately.
hi @tyn1998 ,i've made all the changes as suggested by you the recent commit ensures that the exact same functions that were added as event listeners are removed rather than relying jquery's default behaviour and created a helper function to check validity of selector |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @stealth-bombeer, I tried a cleaner way to implement our own resizer. What do you think?
import React, { useState, useEffect, useRef } from 'react';
import $ from 'jquery';
import {
Widget,
addResponseMessage,
deleteMessages,
toggleMsgLoader,
toggleInputDisabled,
} from 'react-chat-widget';
import { getAnswer } from './service';
import './rcw.scss';
import exists from '../../../../helpers/exists';
import { getMessageByLocale } from '../../../../utils/utils';
import Settings, { loadSettings } from '../../../../utils/settings';
interface Props {
theme: 'light' | 'dark';
currentRepo: string;
currentDocsName: string | null;
}
const displayWelcome = (repoName: string, locale: string) => {
addResponseMessage(
getMessageByLocale('OSS_GPT_welcome', locale).replace('%v', repoName)
);
};
const displayNotAvailable = (repoName: string, locale: string) => {
addResponseMessage(
getMessageByLocale('OSS_GPT_notAvailable', locale).replace('%v', repoName)
);
};
const View = ({ theme, currentRepo, currentDocsName }: Props): JSX.Element => {
const [inited, setInited] = useState(false);
const [settings, setSettings] = useState(new Settings());
const [history, setHistory] = useState<[string, string]>(['', '']);
const mouseDownX = useRef(0); // X position when mouse down
const rcwWidth = useRef(0); // rcw width when mouse down
useEffect(() => {
const initSettings = async () => {
const temp = await loadSettings();
setSettings(temp);
setInited(true);
};
if (!inited) {
initSettings();
}
}, [inited, settings]);
const subtitle = currentDocsName
? getMessageByLocale('OSS_GPT_subtitle', settings.locale).replace(
'%v',
currentRepo
)
: getMessageByLocale(
'OSS_GPT_subtitle_notAvailable',
settings.locale
).replace('%v', currentRepo);
const handleNewUserMessage = async (newMessage: string) => {
toggleMsgLoader();
toggleInputDisabled();
if (currentDocsName) {
const answer = await getAnswer(currentDocsName, newMessage, history);
addResponseMessage(answer);
setHistory([newMessage, answer]); // update history
} else {
displayNotAvailable(currentRepo, settings.locale);
}
toggleMsgLoader();
toggleInputDisabled();
};
useEffect(() => {
// when repo changes
deleteMessages(Infinity); // delete all messages after repo switching
setHistory(['', '']); // clear history
if (currentDocsName) {
// if docs for current repo is available
displayWelcome(currentRepo, settings.locale);
} else {
displayNotAvailable(currentRepo, settings.locale);
}
}, [settings, currentRepo, currentDocsName]);
const handleMouseDown = (event: JQuery.MouseDownEvent) => {
mouseDownX.current = event.clientX;
rcwWidth.current = parseInt(
$('#rcw-conversation-container').css('width'),
10
);
$(document).on('mousemove', handleMouseMove);
$(document).on('mouseup', handleMouseUp);
};
const handleMouseMove = (event: JQuery.MouseMoveEvent) => {
$('#rcw-conversation-container').css(
'width',
`${rcwWidth.current - (event.clientX - mouseDownX.current)}px`
);
};
const handleMouseUp = (event: JQuery.MouseUpEvent) => {
$(document).off('mousemove', handleMouseMove);
$(document).off('mouseup', handleMouseUp);
};
// we cannot change emoji-mart theme with an option, so we have to use MutationObserver and jquery to change the css
useEffect(() => {
// Select the node that will be observed for mutations
const targetNode = $('div.rcw-widget-container')[0]!;
// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: true, subtree: true };
// Callback function to execute when mutations are observed
const callback: MutationCallback = (mutationList, observer) => {
// hacking code for resizer
if (
!exists('.rcw-conversation-resizer') &&
exists('.rcw-conversation-container')
) {
//we only add a resizer when there is no resizer and there is an active conversation container
const resizerDiv = $('<div>');
resizerDiv.attr('class', 'rcw-conversation-resizer');
resizerDiv.on('mousedown', handleMouseDown);
$('#rcw-conversation-container').prepend(resizerDiv);
}
// hacking code for emoji-mart
if (exists('section.emoji-mart')) {
$('section.emoji-mart').addClass(`emoji-mart emoji-mart-${theme}`);
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
return () => {
// Stop observing
observer.disconnect();
};
}, []);
return (
<div className={theme}>
<Widget
title="OSS-GPT"
subtitle={subtitle}
emojis={true} // will be enabled after style is fine tuned for two themes
handleNewUserMessage={handleNewUserMessage}
showBadge={false}
profileAvatar={chrome.runtime.getURL('main.png')}
/>
</div>
);
};
export default View;
handleMouseMove = (event: JQuery.MouseMoveEvent) => { | ||
if (isDragging) { | ||
console.log(document); | ||
const mouseX = event.clientX; | ||
const widthDiff = mouseX - initialMouseX; | ||
conversationContainer.css( | ||
'width', | ||
`${initialWidth - widthDiff}px` | ||
); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried adding console.log(anything)
in handleMouseMove
, and I found the times of this function being called is doubled every time after I resizing rcw. That slows the page noticeably.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is because a new listener is added every time the useEffect that contains the callback function is triggered.
function exists(selector: JQuery<HTMLElement>) { | ||
return $(selector).length > 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have an exists
in codebase already :)
const conversationResizer = $('.rcw-conversation-resizer'); | ||
if (exists(conversationResizer)) { | ||
const handleMouseDown = conversationResizer.data('handleMouseDown'); | ||
const handleMouseMove = conversationResizer.data('handleMouseMove'); | ||
const handleMouseUp = conversationResizer.data('handleMouseUp'); | ||
conversationResizer.off('mousedown', handleMouseDown); | ||
$(document).off('mousemove', handleMouseMove); | ||
$(document).off('mouseup', handleMouseUp); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, jQuery element.data() won't work as you expected. only undefined
you will get.
This is great and works completely fine and I think it resolves above issues @tyn1998 and I think my code is quite repetitive at certain instances :( |
@stealth-bombeer your code taught me how to implement "drag to resize". That is of great value! If my implementation is good to you, you may push another commit to this branch and I'll merge this PR :) |
Thanks, I'll commit making the final changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thank you so much @stealth-bombeer !
Brief Information
This pull request is in the type of (more info about types):
Related issues (all available keywords):
Details
The react-chat-widget has bug in itself for it's resizing props, may be because it had event listener all over the container, so we don't use the inbuilt property of resizing instead we use jquery to select the container and allow it resize only when the left border of the container is dragged we watch for mutations in the useeffect when the conversation container becomes active
Checklist
Others
N.A