Skip to content

Commit

Permalink
GH-84: Improve Storing and selection of multiple languages
Browse files Browse the repository at this point in the history
  • Loading branch information
SetZero committed Feb 18, 2024
1 parent 2e71e00 commit a2ca4fe
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 44 deletions.
15 changes: 13 additions & 2 deletions src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ use crate::{
protocol::message_transmitter::MessageTransmitter,
utils::{audio::device_manager::AudioDeviceManager, constants::get_project_dirs},
};
use tauri::State;
use tauri::{AppHandle, State};
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
use tokio::sync::{
broadcast::{self, Receiver, Sender},
Mutex,
};
use tracing::{error, info, trace};

use self::utils::settings::{
AudioOptions, AudioOutputSettings, AudioPreviewContainer, AudioUserState, Coordinates, GlobalSettings
AudioOptions, AudioOutputSettings, AudioPreviewContainer, AudioUserState, Coordinates,
GlobalSettings,
};
use image::{
imageops::{self, FilterType},
Expand Down Expand Up @@ -351,3 +353,12 @@ pub async fn enable_audio_info(state: State<'_, ConnectionState>) -> Result<(),
});
Ok(())
}

#[allow(clippy::needless_pass_by_value)] // tauri command
#[tauri::command]
pub fn close_app(app: AppHandle) {
if let Err(e) = app.save_window_state(StateFlags::all()) {
error!("Failed to save window state: {:?}", e);
}
app.exit(0);
}
9 changes: 3 additions & 6 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ mod tests;
use std::{collections::HashMap, sync::Arc};

use commands::{web_cmd::CrawlerState, ConnectionState};
use tauri_plugin_window_state::{StateFlags, WindowExt};
use tokio::sync::Mutex;

use tauri::Manager;
Expand All @@ -28,7 +27,7 @@ use tracing_subscriber::{
};

use crate::commands::{
change_user_state, connect_to_server, crop_and_store_image, disable_audio_info,
change_user_state, close_app, connect_to_server, crop_and_store_image, disable_audio_info,
enable_audio_info, get_audio_devices, like_message, logout, send_message,
set_audio_input_setting, set_audio_output_setting, set_audio_user_state, set_user_image,
settings_cmd::{get_identity_certs, get_server_list, save_server},
Expand Down Expand Up @@ -76,9 +75,6 @@ async fn main() {
app.manage(CrawlerState {
crawler: Mutex::new(None),
});
if let Some(window) = app.get_window("main") {
window.restore_state(StateFlags::all())?;
}

Ok(())
})
Expand Down Expand Up @@ -106,7 +102,8 @@ async fn main() {
get_tenor_search_results,
get_tenor_trending_results,
convert_url_to_base64,
set_audio_user_state
set_audio_user_state,
close_app
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down
31 changes: 20 additions & 11 deletions src/components/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ const parseUI = (message: string | undefined, onLoaded: () => void) => {
if (message && message.includes('<')) {
let messageParser = new MessageUIHelper(message, () => onLoaded());

return messageParser.build();
return { standalone: true, element: messageParser.build() };
}

return message;
return { standalone: false, element: message };
}

const generateDate = (timestamp: number) => {
let day = dayjs(timestamp).locale('en-US');
const generateDate = (timestamp: number, locale = 'en') => {
let day = dayjs(timestamp).locale(locale);
if (day.isToday()) {
return day.format('HH:mm');
} else if (day.isYesterday()) {
Expand All @@ -65,6 +65,7 @@ const generateDate = (timestamp: number) => {

const ChatMessage: React.FC<ChatMessageProps> = React.memo(({ message, messageId, onLoaded }) => {
const userList = useSelector((state: RootState) => state.reducer.userInfo);
const locale = useSelector((state: RootState) => state.reducer.frontendSettings.language?.language);
const dispatch = useDispatch();
const { t } = useTranslation();

Expand All @@ -86,7 +87,7 @@ const ChatMessage: React.FC<ChatMessageProps> = React.memo(({ message, messageId
e.addEventListener('mouseleave', (e) => {
e.preventDefault();
e.stopPropagation();
if(e.target) {
if (e.target) {
const videoElement = e.target as HTMLVideoElement;
videoElement.pause();
videoElement.loop = false;
Expand All @@ -101,7 +102,7 @@ const ChatMessage: React.FC<ChatMessageProps> = React.memo(({ message, messageId
, [userList, message.sender.user_id]);

const parsedMessage = React.useMemo(() => parseUI(parseMessage(message.message), onLoaded), [message.message]);
const date = React.useMemo(() => generateDate(message.timestamp), [message.timestamp]);
const date = React.useMemo(() => generateDate(message.timestamp, locale), [message.timestamp]);

const deleteMessageEvent = React.useCallback(() => {
dispatch(deleteChatMessage(messageId));
Expand All @@ -111,13 +112,21 @@ const ChatMessage: React.FC<ChatMessageProps> = React.memo(({ message, messageId
invoke('like_message', { messageId: messageId, reciever: userList.users.map(e => e.id) });
}, []);

const messageElement = React.useMemo(() => {
if (parsedMessage.standalone) {
return (<Grid item className="message-container-inner">{parsedMessage.element}</Grid>);
}

return (<Grid item className="message-container-inner">
<Box className={`message ${false ? "sender" : "receiver"}`}>
{parsedMessage.element}
</Box>
</Grid>);
}, [parsedMessage]);

return (
<Grid item xs={10} className="message-container">
<Grid item className="message-container-inner">
<Box className={`message ${false ? "sender" : "receiver"}`}>
{parsedMessage}
</Box>
</Grid>
{messageElement}
<Grid item className="message-metadata">
<Typography variant="subtitle2" className="metadata">
<Link className="user-info" href="#">{message.sender.user_name}</Link> - {date}
Expand Down
19 changes: 12 additions & 7 deletions src/components/ChatMessageContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const ChatMessageContainer = (props: ChatMessageContainerProps) => {
const messagesEndRef: React.RefObject<HTMLDivElement> = React.createRef();
const [userInfoAnchor, setUserInfoAnchor] = React.useState<HTMLElement | null>(null);
const [currentPopoverUserId, setCurrentPopoverUserId]: any = useState(null);
const [processedMessages, setProcessedMessages] = React.useState<TextMessage[]>([]);

const scrollToBottom = () => {
if (advancedSettings?.disableAutoscroll) {
Expand All @@ -43,6 +44,10 @@ const ChatMessageContainer = (props: ChatMessageContainerProps) => {
});
}

useEffect(() => {
setProcessedMessages(props.messages);
}, [props.messages]);

useEffect(() => {
let messages = props.messages;
if (messages.length > 0) {
Expand All @@ -55,7 +60,7 @@ const ChatMessageContainer = (props: ChatMessageContainerProps) => {
scrollToBottom();
}
}
}, [props.messages]);
}, [processedMessages]);

useEffect(() => {
if (advancedSettings?.alwaysScrollDown) {
Expand Down Expand Up @@ -89,7 +94,7 @@ const ChatMessageContainer = (props: ChatMessageContainerProps) => {
img.removeEventListener('load', handleImageLoad);
});
};
}, [props.messages]);
}, [processedMessages]);

const userIdToUserMap = useMemo(() => {
if (!userList) return new Map<number, UsersState>();
Expand All @@ -105,7 +110,7 @@ const ChatMessageContainer = (props: ChatMessageContainerProps) => {
let groupedMessages: Array<GroupedMessages> = [];
let prevUser: UsersState | undefined = undefined;

props.messages.forEach((el) => {
processedMessages.forEach((el) => {
let currentUser = userIdToUserMap.get(el.sender.user_id);
if (currentUser?.id !== prevUser?.id || groupedMessages.length === 0) {
groupedMessages.push({ user: currentUser, messages: [] });
Expand All @@ -124,7 +129,7 @@ const ChatMessageContainer = (props: ChatMessageContainerProps) => {
});

return groupedMessages;
}, [props.messages]);
}, [processedMessages]);

const userIdToPopoverMap = useMemo(() => {
const popoverMap = new Map<number, ReactElement>();
Expand All @@ -143,7 +148,7 @@ const ChatMessageContainer = (props: ChatMessageContainerProps) => {
}, [userIdToUserMap, userInfoAnchor]);

const emptyChatMessageContainer = useMemo(() => {
if (props.messages.length === 0) {
if (processedMessages.length === 0) {
return (
<Grid container sx={{ height: '100%', width: '100%', userSelect: 'none' }} justifyContent="center" alignItems="center">
<Grid item>
Expand All @@ -155,10 +160,10 @@ const ChatMessageContainer = (props: ChatMessageContainerProps) => {
);
}
return null;
}, [props.messages]);
}, [processedMessages]);

const chatElements = useMemo(() => {
if (!memoizedMessages || props.messages.length === 0) return null;
if (!memoizedMessages || processedMessages.length === 0) return null;

return (<List sx={{ width: '100%', maxWidth: '100%' }}>
{memoizedMessages.map((group, index) => (
Expand Down
42 changes: 38 additions & 4 deletions src/components/LightBoxImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { UsersState } from "../store/features/users/userSlice";
import "./styles/UserInfo.css";
import "./styles/common.css"
import UserInfo from "./UserInfo";
import React, { useState } from "react";
import React, { useEffect, useMemo, useState } from "react";
import { openInBrowser } from "../helper/BrowserUtils";
import { useTranslation } from "react-i18next";

Expand All @@ -12,16 +12,50 @@ interface LightBoxImageProps {
}

function LightBoxImage(props: LightBoxImageProps) {
const minWidth = 400;
const minHeight = 400;
const lightboxRef = React.createRef<HTMLDivElement>();
const imgRef = React.createRef<HTMLImageElement>();
const { t, i18n } = useTranslation();
const [open, setOpen]: any = useState(false);
const [backgroundImage, setBackgroundImage] = useState<React.CSSProperties>({});

const handleClose = () => {
setOpen(false);
};

const imageWidth = useMemo(() => {
if (!imgRef.current) return 0;

return imgRef.current.offsetWidth;
}, [imgRef]);

const imageHeight = useMemo(() => {
if (!imgRef.current) return 0;

return imgRef.current.offsetHeight;
}, [imgRef]);

useEffect(() => {
if (!lightboxRef.current) return;

if (lightboxRef.current.offsetWidth <= minWidth || lightboxRef.current.offsetHeight <= minHeight) {
let additionalStyles = {};
if (imageWidth <= imageHeight) {
additionalStyles = { minWidth: minWidth, height: '100%' };
} else {
additionalStyles = { width: '100%', minHeight: minHeight, height: '100%' };
}

setBackgroundImage({ backgroundImage: `url(${props.src})`, backgroundColor: '#000000', backgroundSize: 'cover', ...additionalStyles });
}
}, [lightboxRef.current?.offsetWidth, lightboxRef.current?.offsetHeight, imageHeight, imageWidth]);

return (
<Box>
<img src={props.src} onClick={() => setOpen(true)} style={{ maxWidth: '100%', maxHeight: '600px', cursor: 'pointer' }} />
<Box sx={{ borderRadius: '10px', display: 'flex', cursor: 'pointer', ...backgroundImage }} ref={lightboxRef}>
<Box onClick={() => setOpen(true)} sx={{ width: '100%', height: '100%', borderRadius: '10px', backdropFilter: 'blur(20px)', display: 'flex', justifyContent: 'center', alignContent: 'center', alignItems: 'center', flexWrap: 'nowrap', flexDirection: 'column' }}>
<img ref={imgRef} src={props.src} style={{ maxWidth: '100%', maxHeight: minHeight, borderRadius: '10px' }} alt="" />
</Box>
<Backdrop
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1, backdropFilter: 'blur(5px)', padding: '50px 10px 10px 10px' }}
open={open}
Expand All @@ -31,7 +65,7 @@ function LightBoxImage(props: LightBoxImageProps) {
<Box sx={{ flexShrink: 0, display: 'contents' }}>
<img src={props.src} style={{ height: 'auto', width: 'auto', maxWidth: '100%', maxHeight: 'calc(100% - 2em)', objectFit: 'contain' }} />
</Box>
<Box sx={{ flexShrink: 1, textAlign: 'center'}}>
<Box sx={{ flexShrink: 1, textAlign: 'center' }}>
<Link href="#" color="inherit" underline="hover" onClick={() => openInBrowser(props.src)}>{t('Open In Browser')}</Link>
</Box>
</Box>
Expand Down
11 changes: 7 additions & 4 deletions src/components/Titlebar.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { appWindow } from "@tauri-apps/api/window";
import CloseIcon from '@mui/icons-material/Close';
import MaximizeIcon from '@mui/icons-material/Maximize';
import MinimizeIcon from '@mui/icons-material/Minimize';
import FilterNoneIcon from '@mui/icons-material/FilterNone';
import { Box, IconButton, Paper } from "@mui/material";
import { IconButton, Paper } from "@mui/material";
import { invoke } from "@tauri-apps/api";
import './styles/Titlebar.css';

function Titlebar() {
const closeApp = () => {
invoke('close_app');
}

return (
<Paper data-tauri-drag-region sx={{
width: '100%',
Expand All @@ -21,7 +24,7 @@ function Titlebar() {
<IconButton size="small" onClick={(e) => appWindow.toggleMaximize()} className="titlebar-button">
<FilterNoneIcon sx={{ fontSize: 18 }} />
</IconButton >
<IconButton size="small" onClick={(e) => appWindow.close()} className="titlebar-button" color="error">
<IconButton size="small" onClick={(e) => closeApp()} className="titlebar-button" color="error">
<CloseIcon sx={{ fontSize: 18 }} />
</IconButton >
</Paper>
Expand Down
7 changes: 4 additions & 3 deletions src/components/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ function UserInfo(props: UserInfoProps) {
const [voiceAdjustment, setVoiceAdjustment] = useState(0.0);
const { t, i18n } = useTranslation();
const userInfo = useSelector((state: RootState) => state.reducer.userInfo).currentUser;
const locale = useSelector((state: RootState) => state.reducer.frontendSettings.language?.language) ?? 'en';

const dispatch = useDispatch();
const chatMessageHandler = new ChatMessageHandler(dispatch, setChatMessage);

let mutedText = props.userInfo?.mutedSince ? dayjs(props.userInfo?.mutedSince).fromNow() : '';
let deafenedText = props.userInfo?.deafenedSince ? dayjs(props.userInfo?.deafenedSince).fromNow() : '';
let joinedText = props.userInfo?.joinedSince ? dayjs(props.userInfo?.joinedSince).fromNow() : '';
let mutedText = props.userInfo?.mutedSince ? dayjs(props.userInfo?.mutedSince).locale(locale).fromNow() : '';
let deafenedText = props.userInfo?.deafenedSince ? dayjs(props.userInfo?.deafenedSince).locale(locale).fromNow() : '';
let joinedText = props.userInfo?.joinedSince ? dayjs(props.userInfo?.joinedSince).locale(locale).fromNow() : '';

function generateCardMedia() {
if (background) {
Expand Down
7 changes: 5 additions & 2 deletions src/components/settings/LanguageSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ function LanguageSettings() {
let sortedLanguages = [...i18n.languages].sort((a, b) => a.localeCompare(b));

function updateLanguageSettings(language: string) {
let newLanguageSettings = { language: language };
let newFrontendSettings = { ...frontendSettings, language: newLanguageSettings };

i18n.changeLanguage(language);
dispatch(setLanguage({ language: language }));
persistFrontendSettings(frontendSettings);
dispatch(setLanguage(newLanguageSettings));
persistFrontendSettings(newFrontendSettings);
}

return (
Expand Down
1 change: 0 additions & 1 deletion src/components/styles/ChatMessage.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
}

.message-container-inner {
flex-direction: column;
display: flex;
align-items: flex-start
}
Expand Down
6 changes: 3 additions & 3 deletions src/helper/MessageUIHelper.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Container } from '@mui/material';
import { Box, Container } from '@mui/material';
import parse, { DOMNode, HTMLReactParserOptions, domToReact } from 'html-react-parser';
import LightBoxImage from '../components/LightBoxImage';
import UrlPreview from '../components/UrlPreview';
Expand All @@ -17,9 +17,9 @@ export default class MessageUIHelper {
replace: ({ name, attribs, children }: any) => {
switch (name) {
case 'img':
return (<Container >
return (<Box>
<LightBoxImage src={attribs.src} />
</Container>);
</Box>);
case 'a':
return (<Container>
<UrlPreview href={attribs.href} onLoaded={this.loaded} />
Expand Down
2 changes: 1 addition & 1 deletion src/i18n/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ i18n
lng: "en", // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
// you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
// if you're using a language detector, do not define the lng option
fallbackLng: ["en", "de", "fr", "es", "dev"],
fallbackLng: ["de", "en", "fr", "es", "zh", "dev"],
ns: ['common', 'appearance', 'audio', 'language', 'notifications', 'privacy', 'time', 'user_interaction'],
defaultNS: 'common',
interpolation: {
Expand Down
5 changes: 5 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import hljs from 'highlight.js';
import { marked } from 'marked';
import { markedHighlight } from 'marked-highlight';
import './i18n/i18n';
import 'dayjs/locale/de';
import 'dayjs/locale/en';
import 'dayjs/locale/zh';
import 'dayjs/locale/fr';
import 'dayjs/locale/es';

import 'highlight.js/styles/base16/equilibrium-gray-dark.css';

Expand Down

0 comments on commit a2ca4fe

Please sign in to comment.