From 308aeabba098516bf8ff56e3347093b27da101a2 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 2 Jul 2023 01:59:40 +0200 Subject: [PATCH] GH-29: Add first steps for auto updating --- .github/workflows/build.yml | 1 + package.json | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 10 +++- src/components/ChatMessageContainer.tsx | 48 ++++++++++++----- src/components/CurrentUserInfo.tsx | 47 ++++++++++------- src/components/QuillEditor.tsx | 30 ++++++----- src/components/Sidebar.tsx | 18 +++---- src/routes/Chat.tsx | 69 ++++++++----------------- yarn.lock | 12 ++--- 10 files changed, 128 insertions(+), 111 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 24f5767..d5f873a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -175,6 +175,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: releaseId: ${{ needs.create-release.outputs.release_id }} + tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version - name: Add Windows Executeable continue-on-error: true diff --git a/package.json b/package.json index dcd8e6a..cad7ddc 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "lru-cache": "^10.0.0", "marked": "^5.0.2", "marked-highlight": "^2.0.0", - "quill": "1.3.6", + "quill": "^1.3.7", "react": "^18.2.0", "react-color": "^2.19.3", "react-dom": "^18.2.0", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index fbbaefc..d1d5389 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -22,7 +22,7 @@ patch = "0.7.0" [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.4.0", features = ["dialog-open", "global-shortcut-all", "shell-open", "window-close", "window-hide", "window-maximize", "window-minimize", "window-start-dragging", "window-unmaximize", "window-unminimize"] } +tauri = { version = "1.4.0", features = ["dialog-open", "global-shortcut-all", "shell-open", "updater", "window-close", "window-hide", "window-maximize", "window-minimize", "window-start-dragging", "window-unmaximize", "window-unminimize"] } futures = "0.3.4" tokio = { version = "1", features = ["full"] } tokio-native-tls = "0.3.1" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index fa3f339..792c6c7 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -95,7 +95,15 @@ "csp": null }, "updater": { - "active": false + "active": true, + "endpoints": [ + "https://github.com/Fancy-Mumble/FancyMumbleV2/releases/latest/download/latest.json" + ], + "dialog": true, + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDhGNTM4MDI3NDkwN0I1NQpSV1JWZTVCMEFqajFDQTdndmVuRDFmY1NNKy8vM1VWM3NESGNXSHM3MHFtZU45a212Qysvd0x0ZQo=", + "windows": { + "installMode": "passive" + } }, "windows": [ { diff --git a/src/components/ChatMessageContainer.tsx b/src/components/ChatMessageContainer.tsx index 44ada09..062d06c 100644 --- a/src/components/ChatMessageContainer.tsx +++ b/src/components/ChatMessageContainer.tsx @@ -28,23 +28,19 @@ const ChatMessageContainer = (props: ChatMessageContainerProps) => { const prevPropsRef = useRef(props); const scrollToBottom = () => { - //add some minor sleep to make sure the element is rendered - new Promise(r => setTimeout(r, 100)).then(() => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }); + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); } useEffect(() => { - if (chatContainer.current) { - let el = chatContainer.current; - if (el.scrollTop < el.scrollHeight - el.clientHeight * 2) { - console.log("User scrolled", el.scrollTop, el.scrollHeight - el.clientHeight); - setUserScrolled(true); - } else { - setUserScrolled(false); + let messages = props.messages; + if (messages.length > 0) { + const isScrolledToBottom = (chatContainer?.current?.scrollHeight || 0) - (chatContainer?.current?.scrollTop || 0) === chatContainer?.current?.clientHeight; + + if (isScrolledToBottom) { + messagesEndRef?.current?.scrollIntoView({ behavior: 'smooth' }); } } - }, [props, prevPropsRef]); // Depend on props + }, [props.messages]); useEffect(() => { if (!userScrolled && chatContainer?.current) { @@ -52,6 +48,34 @@ const ChatMessageContainer = (props: ChatMessageContainerProps) => { } }, [props, userScrolled]); // Depend on props and userScrolled + useEffect(() => { + const images = Array.from(document.getElementsByTagName('img')); + let loadedImagesCount = 0; + + const handleImageLoad = () => { + loadedImagesCount++; + + if (loadedImagesCount === images.length) { + scrollToBottom(); + console.log("All images loaded") + } + }; + + images.forEach((img) => { + if (img.complete) { + handleImageLoad(); + } else { + img.addEventListener('load', handleImageLoad); + } + }); + + return () => { + images.forEach((img) => { + img.removeEventListener('load', handleImageLoad); + }); + }; + }, [props.messages]); + const userIdToUserMap = useMemo(() => { if (!userList) return new Map(); diff --git a/src/components/CurrentUserInfo.tsx b/src/components/CurrentUserInfo.tsx index 24accf6..e055c3a 100644 --- a/src/components/CurrentUserInfo.tsx +++ b/src/components/CurrentUserInfo.tsx @@ -13,50 +13,59 @@ import { UpdateableUserState, UsersState } from "../store/features/users/userSli import "./styles/common.css" import { useCallback, useEffect, useMemo } from "react"; +const selectCurrentUser = (state: RootState) => state.reducer.userInfo.currentUser; -function CurrentUserInfo() { - const dispatch = useDispatch(); - const userInfo = useSelector((state: RootState) => state.reducer.userInfo); +const customEqual = (oldUser: UsersState | undefined, newUser: UsersState | undefined) => { + if (oldUser === newUser) return true; + if (oldUser === undefined || newUser === undefined) return false; - useEffect(() => { - console.log("CurrentUserInfo rendered"); - }); + return ( + oldUser.comment === newUser.comment && + oldUser.self_mute === newUser.self_mute && + oldUser.self_deaf === newUser.self_deaf && + oldUser.name === newUser.name && + oldUser.id === newUser.id + ); +}; - const userBackground = useMemo(() => getBackgroundFromComment(userInfo.currentUser), [userInfo?.currentUser?.comment]); +function CurrentUserInfo() { + const currentUser = useSelector(selectCurrentUser, customEqual); + + const userBackground = useMemo(() => getBackgroundFromComment(currentUser), [currentUser?.comment]); const updateUserValue = useCallback((update: (currentUser: UsersState, operator: UpdateableUserState) => void) => { - if (userInfo.currentUser) { - let currentUser = userInfo.currentUser; + if (currentUser) { let currentUserClone: UpdateableUserState = { id: currentUser.id }; update(currentUser, currentUserClone); invoke('change_user_state', { userState: currentUserClone }); } - }, [userInfo.currentUser]); + }, [currentUser]); const muteToggleUser = useCallback(() => { updateUserValue((currentUser, currentUserClone) => currentUserClone.self_mute = !currentUser.self_mute); - }, [userInfo?.currentUser?.self_mute]); + }, [updateUserValue, currentUser?.self_mute]); const deafToggleUser = useCallback(() => { updateUserValue((currentUser, currentUserClone) => currentUserClone.self_deaf = !currentUser.self_deaf); - }, [userInfo?.currentUser?.self_deaf]); + }, [updateUserValue, currentUser?.self_deaf]); + const MicrophoneState = useMemo(() => { - if (userInfo.currentUser?.self_mute) { + if (currentUser?.self_mute) { return () } else { return () } - }, [userInfo.currentUser?.self_mute]); + }, [currentUser?.self_mute]); const VolumeState = useMemo(() => { - if (userInfo.currentUser?.self_deaf) { + if (currentUser?.self_deaf) { return () } else { return () } - }, [userInfo.currentUser?.self_deaf]); + }, [currentUser?.self_deaf]); return ( - {userInfo.currentUser?.name ?? 'Unknown'} + {currentUser?.name ?? 'Unknown'} {MicrophoneState} diff --git a/src/components/QuillEditor.tsx b/src/components/QuillEditor.tsx index 7123c93..9b4fb29 100644 --- a/src/components/QuillEditor.tsx +++ b/src/components/QuillEditor.tsx @@ -1,7 +1,17 @@ import React, { useEffect, useRef, useState } from 'react'; -import Quill, { TextChangeHandler } from 'quill'; + +import Quill, { + QuillOptionsStatic, + RangeStatic, + BoundsStatic, + StringMap, + Sources, +} from 'quill'; + import 'quill/dist/quill.snow.css'; +Quill.register + interface QuillEditorProps { theme?: 'bubble' | 'snow' | string; placeholder?: string; @@ -26,7 +36,11 @@ export const QuillEditor: React.FC = ({ bounds = 'document.body', debug = 'warn', formats = [], - modules = {}, + modules = { + toolbar: { + toolbar: '#toolbar' + } + }, scrollingContainer = undefined, style = {}, onKeyDown, @@ -44,6 +58,7 @@ export const QuillEditor: React.FC = ({ // This runs once on component mount useEffect(() => { if (editorRef.current) { + console.log("editorRef.current", editorRef.current); quill = new Quill(editorRef.current, { theme, placeholder, @@ -101,17 +116,6 @@ export const QuillEditor: React.FC = ({ }; }, []); // Empty array means this effect runs once on mount and clean up on unmount - - useEffect(() => { - if (editorRef.current) { - - - /*if (quillRef) { - quillRef.current = quill; - }*/ - } - }, [theme, placeholder, readOnly, bounds, debug, formats, modules, scrollingContainer, quillRef]); - return
{toolbarVisible &&
/* Add toolbar contents here */
}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index f23cc7d..3e70e0c 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -3,34 +3,32 @@ import LogoutIcon from '@mui/icons-material/Logout'; import InfoIcon from '@mui/icons-material/Info'; import { invoke } from "@tauri-apps/api"; import { useNavigate } from "react-router-dom"; -import { useState } from "react"; +import { useCallback, useState } from "react"; import { LoadingButton } from "@mui/lab"; import './Sidebar.css' import ChannelViewer from "./ChannelViewer"; import CurrentUserInfo from "./CurrentUserInfo"; import SettingsIcon from '@mui/icons-material/Settings'; +import React from "react"; -interface SidebarProps { -} - -function Sidebar(props: SidebarProps) { +function Sidebar() { const navigate = useNavigate(); const [logoutInProgress, setLogoutInProgress] = useState(false); - function triggerLogout(event: React.MouseEvent) { + const triggerLogout = useCallback((event: React.MouseEvent) => { event.preventDefault(); setLogoutInProgress(true); invoke('logout').then(e => { setLogoutInProgress(false); navigate("/"); }) - } + }, [navigate]); // add dependencies here - function openSettings(event: React.MouseEvent): void { + const openSettings = useCallback((event: React.MouseEvent): void => { event.preventDefault(); navigate("/settings"); - } + }, [navigate]); // add dependencies here return ( @@ -53,4 +51,4 @@ function Sidebar(props: SidebarProps) { ) } -export default Sidebar +export default React.memo(Sidebar); diff --git a/src/routes/Chat.tsx b/src/routes/Chat.tsx index d3e1349..2bb9a93 100644 --- a/src/routes/Chat.tsx +++ b/src/routes/Chat.tsx @@ -1,6 +1,6 @@ import { Box, Divider, IconButton, InputBase, Paper, Tooltip } from '@mui/material'; import { invoke } from '@tauri-apps/api'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import SendIcon from '@mui/icons-material/Send'; import DeleteIcon from '@mui/icons-material/Delete'; import ChatMessageContainer from '../components/ChatMessageContainer'; @@ -14,6 +14,7 @@ import { formatBytes } from '../helper/Fomat'; import { UpdateableUserState, UsersState } from '../store/features/users/userSlice'; import { ChatMessageHandler } from '../helper/ChatMessage'; import { unregister } from '@tauri-apps/api/globalShortcut'; +import { QuillEditor } from '../components/QuillEditor'; function Chat() { const [chatMessage, setChatMessage] = useState(""); @@ -21,58 +22,30 @@ function Chat() { const [gifSearchAnchor, setGifSearchAnchor] = useState(); const dispatch = useDispatch(); - const userInfo = useSelector((state: RootState) => state.reducer.userInfo); + const currentUser = useSelector((state: RootState) => state.reducer.userInfo?.currentUser); const channelInfo = useSelector((state: RootState) => state.reducer.channel); const messageLog = useSelector((state: RootState) => state.reducer.chatMessage); - const currentChannel = channelInfo.find(e => e.channel_id === userInfo.currentUser?.channel_id)?.name; + const currentChannel = useMemo(() => channelInfo.find(e => e.channel_id === currentUser?.channel_id)?.name, [channelInfo, currentUser]); + const chatMessageHandler = useMemo(() => new ChatMessageHandler(dispatch, setChatMessage), [dispatch]); - const chatMessageHandler = new ChatMessageHandler(dispatch, setChatMessage); - - useEffect(() => { - unregister('F13'); - /*register('F13', () => { - console.log('F13 is pressed'); - if (userInfo.currentUser) - updateUserValue((currentUser, currentUserClone) => currentUserClone.self_mute = !currentUser.self_mute); - });*/ - }); - - - - async function updateUserValue(update: (currentUser: UsersState, operator: UpdateableUserState) => void) { - if (userInfo.currentUser) { - let currentUser = userInfo.currentUser; - let currentUserClone: UpdateableUserState = { id: currentUser.id }; - - update(currentUser, currentUserClone); - await invoke('change_user_state', { userState: currentUserClone }).catch(e => console.log(e)); - } - } - - function deleteMessages() { + const deleteMessages = useCallback(() => { chatMessageHandler.deleteMessages(); - } - - + }, [chatMessageHandler]); - function keyDownHandler(e: React.KeyboardEvent) { + const keyDownHandler = useCallback((e: React.KeyboardEvent) => { if (e && e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); - chatMessageHandler.sendChatMessage(chatMessage, userInfo.currentUser); + chatMessageHandler.sendChatMessage(chatMessage, currentUser); } - } + }, [chatMessage, chatMessageHandler, currentUser]); - function uploadFile(e: any) { - //TODO - } - - function showGifPreview(e: any) { - setShowGifSearch(!showGifSearch); + const showGifPreview = useCallback((e: any) => { + setShowGifSearch(prev => !prev); setGifSearchAnchor(e.currentTarget) - } + }, []); - function pasteEvent(event: any) { + const pasteEvent = useCallback((event: any) => { let items = event.clipboardData.items; for (const item of items) { if (item.type.indexOf('image') !== -1) { @@ -81,15 +54,15 @@ function Chat() { reader.readAsDataURL(file); reader.onload = function () { if (reader.result && (reader.result as string).length > 0x7fffff) { - chatMessageHandler.sendCustomChatMessage("[[ Image too large ( " + formatBytes((reader.result as string).length) + " out of " + formatBytes(0x7fffff) + ") ]]", userInfo.currentUser); + chatMessageHandler.sendCustomChatMessage("[[ Image too large ( " + formatBytes((reader.result as string).length) + " out of " + formatBytes(0x7fffff) + ") ]]", currentUser); return; } let img = ''; - chatMessageHandler.sendCustomChatMessage(img, userInfo.currentUser); + chatMessageHandler.sendCustomChatMessage(img, currentUser); }; } } - } + }, [chatMessageHandler, currentUser]); return ( @@ -103,9 +76,9 @@ function Chat() { sx={{ p: '2px 4px', display: 'flex', alignItems: 'center', width: 400, flexGrow: 1 }} > - - - + + + - chatMessageHandler.sendChatMessage(chatMessage, userInfo.currentUser)}> + chatMessageHandler.sendChatMessage(chatMessage, currentUser)}> diff --git a/yarn.lock b/yarn.lock index aac7dbc..b7de6ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1016,7 +1016,7 @@ eventemitter3@^2.0.3: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" integrity sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg== -extend@^3.0.1, extend@^3.0.2: +extend@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -1484,15 +1484,15 @@ quill-delta@^4.0.1: lodash.clonedeep "^4.5.0" lodash.isequal "^4.5.0" -quill@1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.6.tgz#99f4de1fee85925a0d7d4163b6d8328f23317a4d" - integrity sha512-K0mvhimWZN6s+9OQ249CH2IEPZ9JmkFuCQeHAOQax3EZ2nDJ3wfGh59mnlQaZV2i7u8eFarx6wAtvQKgShojug== +quill@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8" + integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g== dependencies: clone "^2.1.1" deep-equal "^1.0.1" eventemitter3 "^2.0.3" - extend "^3.0.1" + extend "^3.0.2" parchment "^1.1.4" quill-delta "^3.6.2"