diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 8ac06a1..74aa66b 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -2,30 +2,22 @@ #![allow(clippy::used_underscore_binding)] mod helper; +pub mod settings_cmd; +pub mod web_cmd; +pub mod zip_cmd; -use std::{ - borrow::BorrowMut, - collections::HashMap, - io::{Seek, SeekFrom, Write}, -}; +use std::{borrow::BorrowMut, collections::HashMap}; use crate::{ connection::{traits::Shutdown, Connection}, errors::string_convertion::ErrorString, manager::user::UpdateableUserState, protocol::message_transmitter::MessageTransmitter, - utils::{ - audio::device_manager::AudioDeviceManager, constants::get_project_dirs, server::Server, - }, + utils::audio::device_manager::AudioDeviceManager, }; -use base64::{engine::general_purpose, Engine}; -use serde_json::json; use tauri::State; use tokio::sync::Mutex; use tracing::{error, info, trace}; -use webbrowser::{Browser, BrowserOptions}; - -use self::helper::OpenGraphCrawler; pub struct ConnectionState { pub connection: Mutex>, @@ -82,91 +74,6 @@ pub async fn connect_to_server( Ok(()) } -#[tauri::command] -pub fn save_server( - description: &str, - server_host: &str, - server_port: u16, - username: &str, -) -> Result<(), String> { - info!("Saving server: {server_host}:{server_port}"); - let project_dirs = get_project_dirs().ok_or("Unable to load project dir")?; - - let data_dir = project_dirs.config_dir(); - - // create config dir if it doesn't exist - std::fs::create_dir_all(data_dir).map_err(|e| format!("{e:?}"))?; - - // open server.json or create it if it doesn't exist - let mut server_file = std::fs::OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(data_dir.join("server.json")) - .map_err(|e| format!("Error opening file: {e:?}"))?; - - // read the json content using serde and append the new server - let mut server_list = - serde_json::from_reader::<&std::fs::File, Vec>(&server_file).unwrap_or_default(); - - // check if the server is already in the list - for server in &server_list { - if server.host == server_host && server.port == server_port { - return Err("Server already exists".to_string()); - } - } - - server_list.push(Server { - description: description.to_string(), - host: server_host.to_string(), - port: server_port, - username: username.to_string(), - }); - - trace!("Server list: {:#?}", server_list); - - // write the new json content - server_file - .seek(SeekFrom::Start(0)) - .map_err(|e| format!("{e:?}"))?; - server_file - .write_all( - serde_json::to_string_pretty(&server_list) - .map_err(|e| format!("{e:?}"))? - .as_bytes(), - ) - .map_err(|e| format!("{e:?}"))?; - - Ok(()) -} - -#[tauri::command] -pub fn get_server_list() -> Result, String> { - info!("Getting server list"); - let project_dirs = get_project_dirs().ok_or("Unable to load project dir")?; - - let data_dir = project_dirs.config_dir(); - - // create config dir if it doesn't exist - std::fs::create_dir_all(data_dir).map_err(|e| format!("{e:?}"))?; - - // open server.json or create it if it doesn't exist - let server_file = std::fs::OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(data_dir.join("server.json")) - .map_err(|e| format!("Error opening file: {e:?}"))?; - - // read the json content using serde - let server_list = - serde_json::from_reader::<&std::fs::File, Vec>(&server_file).unwrap_or_default(); - - trace!("Server list: {:#?}", server_list); - - Ok(server_list) -} - #[tauri::command] pub async fn send_message( chat_message: String, @@ -289,77 +196,3 @@ pub async fn get_audio_devices( Err(ErrorString("Failed to get audio devices".to_string())) } - -#[tauri::command] -pub fn zip_data_to_utf8(data: &str, quality: u32) -> Result { - trace!("zipping data {:?}", data); - - let mut buffer = Vec::new(); - let lg_windows_size = 22; - - { - let cursor = std::io::Cursor::new(&mut buffer); - let mut writer = brotli::CompressorWriter::new(cursor, 4096, quality, lg_windows_size); - writer - .write_all(data.as_bytes()) - .map_err(|e| e.to_string())?; - writer.flush().map_err(|e| e.to_string())?; - } - - let encoded = general_purpose::STANDARD.encode(buffer); - Ok(encoded) -} - -#[tauri::command] -pub fn unzip_data_from_utf8(data: &str) -> Result { - let decoded_data = general_purpose::STANDARD - .decode(data) - .map_err(|e| e.to_string())?; - let mut writer = brotli::DecompressorWriter::new(Vec::new(), 4096); - writer.write_all(&decoded_data).map_err(|e| e.to_string())?; - let output = writer.into_inner().map_err(|_| "Decompress Error")?; - - let result = String::from_utf8(output).map_err(|e| e.to_string())?; - Ok(result) -} - -#[tauri::command] -pub fn open_browser(url: &str) -> Result<(), String> { - if let Err(e) = webbrowser::open_browser_with_options( - Browser::Default, - url, - BrowserOptions::new().with_suppress_output(false), - ) { - return Err(format!("{e:?}")); - } - - Ok(()) -} - -pub struct CrawlerState { - pub crawler: Mutex>, -} - -#[tauri::command] -pub async fn get_open_graph_data_from_website( - state: State<'_, CrawlerState>, - url: &str, -) -> Result { - // setup crawler if not already done - let result = { - let mut client = state.crawler.lock().await; - if client.is_none() { - *client = OpenGraphCrawler::try_new(); - } - - client - .as_ref() - .ok_or_else(|| "Failed to read website body".to_string())? - .crawl(url) - .await - }; - - let result = json!(result); - - Ok(result.to_string()) -} diff --git a/src-tauri/src/commands/settings_cmd.rs b/src-tauri/src/commands/settings_cmd.rs new file mode 100644 index 0000000..59728d6 --- /dev/null +++ b/src-tauri/src/commands/settings_cmd.rs @@ -0,0 +1,138 @@ +use std::io::{Read, Seek, SeekFrom, Write}; + +use tracing::{info, trace}; + +use crate::utils::{constants::get_project_dirs, server::Server}; + +const SERVER_SETTINS_FILE: &str = "server.json"; +const FRONTEND_SETTINS_FILE: &str = "frontend_settings.json"; + +pub fn get_settings_file(file_name: &str) -> Result { + let project_dirs = get_project_dirs().ok_or("Unable to load project dir")?; + let data_dir = project_dirs.config_dir(); + std::fs::create_dir_all(data_dir).map_err(|e| format!("{e:?}"))?; + let settings_file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(data_dir.join(file_name)) + .map_err(|e| format!("Error opening file: {e:?}"))?; + Ok(settings_file) +} + +#[tauri::command] +pub fn save_server( + description: &str, + server_host: &str, + server_port: u16, + username: &str, +) -> Result<(), String> { + info!("Saving server: {server_host}:{server_port}"); + let mut server_file = get_settings_file(SERVER_SETTINS_FILE)?; + + // read the json content using serde and append the new server + let mut server_list = + serde_json::from_reader::<&std::fs::File, Vec>(&server_file).unwrap_or_default(); + + // check if the server is already in the list + for server in &server_list { + if server.host == server_host && server.port == server_port { + return Err("Server already exists".to_string()); + } + } + + server_list.push(Server { + description: description.to_string(), + host: server_host.to_string(), + port: server_port, + username: username.to_string(), + }); + + trace!("Server list: {:#?}", server_list); + + // write the new json content + server_file + .seek(SeekFrom::Start(0)) + .map_err(|e| format!("{e:?}"))?; + server_file + .write_all( + serde_json::to_string_pretty(&server_list) + .map_err(|e| format!("{e:?}"))? + .as_bytes(), + ) + .map_err(|e| format!("{e:?}"))?; + + Ok(()) +} + +#[tauri::command] +pub fn get_server_list() -> Result, String> { + info!("Getting server list"); + let project_dirs = get_project_dirs().ok_or("Unable to load project dir")?; + + let data_dir = project_dirs.config_dir(); + + // create config dir if it doesn't exist + std::fs::create_dir_all(data_dir).map_err(|e| format!("{e:?}"))?; + + // open server.json or create it if it doesn't exist + let server_file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(data_dir.join(SERVER_SETTINS_FILE)) + .map_err(|e| format!("Error opening file: {e:?}"))?; + + // read the json content using serde + let server_list = + serde_json::from_reader::<&std::fs::File, Vec>(&server_file).unwrap_or_default(); + + trace!("Server list: {:#?}", server_list); + + Ok(server_list) +} + +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct LinkPreview { + enabled: bool, + allow_all: bool, + urls: Vec, +} + +#[allow(clippy::needless_pass_by_value)] // LinkPreview needs to be deserialized +#[tauri::command] +pub fn save_frontend_settings(settings_name: &str, data: LinkPreview) -> Result<(), String> { + info!("Saving frontend settings: {settings_name}"); + let mut settings_file = get_settings_file(&format!("{settings_name}_{FRONTEND_SETTINS_FILE}"))?; + + trace!("Settings data: {:#?}", data); + + // write the new json content + settings_file + .seek(SeekFrom::Start(0)) + .map_err(|e| format!("{e:?}"))?; + settings_file + .write_all( + serde_json::to_string_pretty(&data) + .map_err(|e| format!("{e:?}"))? + .as_bytes(), + ) + .map_err(|e| format!("{e:?}"))?; + + Ok(()) +} + +#[tauri::command] +pub fn get_frontend_settings(settings_name: &str) -> Result { + info!("Getting frontend settings: {settings_name}"); + let mut settings_file = get_settings_file(&format!("{settings_name}_{FRONTEND_SETTINS_FILE}"))?; + + let mut settings_data = String::new(); + settings_file + .read_to_string(&mut settings_data) + .map_err(|e| format!("{e:?}"))?; + + trace!("Settings data: {:#?}", settings_data); + + Ok(settings_data) +} diff --git a/src-tauri/src/commands/web_cmd.rs b/src-tauri/src/commands/web_cmd.rs new file mode 100644 index 0000000..add8bbf --- /dev/null +++ b/src-tauri/src/commands/web_cmd.rs @@ -0,0 +1,47 @@ +use serde_json::json; +use tauri::State; +use tokio::sync::Mutex; +use webbrowser::{Browser, BrowserOptions}; + +use super::helper::OpenGraphCrawler; + +#[tauri::command] +pub fn open_browser(url: &str) -> Result<(), String> { + if let Err(e) = webbrowser::open_browser_with_options( + Browser::Default, + url, + BrowserOptions::new().with_suppress_output(false), + ) { + return Err(format!("{e:?}")); + } + + Ok(()) +} + +pub struct CrawlerState { + pub crawler: Mutex>, +} + +#[tauri::command] +pub async fn get_open_graph_data_from_website( + state: State<'_, CrawlerState>, + url: &str, +) -> Result { + // setup crawler if not already done + let result = { + let mut client = state.crawler.lock().await; + if client.is_none() { + *client = OpenGraphCrawler::try_new(); + } + + client + .as_ref() + .ok_or_else(|| "Failed to read website body".to_string())? + .crawl(url) + .await + }; + + let result = json!(result); + + Ok(result.to_string()) +} diff --git a/src-tauri/src/commands/zip_cmd.rs b/src-tauri/src/commands/zip_cmd.rs new file mode 100644 index 0000000..81919d9 --- /dev/null +++ b/src-tauri/src/commands/zip_cmd.rs @@ -0,0 +1,37 @@ +use std::io::Write; + +use base64::{engine::general_purpose, Engine}; +use tracing::trace; + +#[tauri::command] +pub fn zip_data_to_utf8(data: &str, quality: u32) -> Result { + trace!("zipping data {:?}", data); + + let mut buffer = Vec::new(); + let lg_windows_size = 22; + + { + let cursor = std::io::Cursor::new(&mut buffer); + let mut writer = brotli::CompressorWriter::new(cursor, 4096, quality, lg_windows_size); + writer + .write_all(data.as_bytes()) + .map_err(|e| e.to_string())?; + writer.flush().map_err(|e| e.to_string())?; + } + + let encoded = general_purpose::STANDARD.encode(buffer); + Ok(encoded) +} + +#[tauri::command] +pub fn unzip_data_from_utf8(data: &str) -> Result { + let decoded_data = general_purpose::STANDARD + .decode(data) + .map_err(|e| e.to_string())?; + let mut writer = brotli::DecompressorWriter::new(Vec::new(), 4096); + writer.write_all(&decoded_data).map_err(|e| e.to_string())?; + let output = writer.into_inner().map_err(|_| "Decompress Error")?; + + let result = String::from_utf8(output).map_err(|e| e.to_string())?; + Ok(result) +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index aed454f..597fd70 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -16,7 +16,7 @@ mod tests; use std::collections::HashMap; -use commands::{ConnectionState, CrawlerState}; +use commands::{web_cmd::CrawlerState, ConnectionState}; use tokio::sync::Mutex; use tauri::Manager; @@ -27,9 +27,11 @@ use tracing_subscriber::{ }; use crate::commands::{ - change_user_state, connect_to_server, get_audio_devices, get_open_graph_data_from_website, - get_server_list, like_message, logout, open_browser, save_server, send_message, set_user_image, - unzip_data_from_utf8, zip_data_to_utf8, + change_user_state, connect_to_server, get_audio_devices, like_message, logout, send_message, + set_user_image, + settings_cmd::{get_frontend_settings, get_server_list, save_frontend_settings, save_server}, + web_cmd::{get_open_graph_data_from_website, open_browser}, + zip_cmd::{unzip_data_from_utf8, zip_data_to_utf8}, }; fn init_logging() { @@ -79,7 +81,9 @@ fn main() { zip_data_to_utf8, unzip_data_from_utf8, open_browser, - get_open_graph_data_from_website + get_open_graph_data_from_website, + save_frontend_settings, + get_frontend_settings ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/components/UrlPreview.tsx b/src/components/UrlPreview.tsx index 2e9bbd2..83cb094 100644 --- a/src/components/UrlPreview.tsx +++ b/src/components/UrlPreview.tsx @@ -4,6 +4,9 @@ import "./styles/common.css" import { useEffect, useMemo, useState } from "react"; import { invoke } from "@tauri-apps/api"; import { s } from "@tauri-apps/api/app-373d24a3"; +import { openInBrowser } from "../helper/BrowserUtils"; +import { useSelector } from "react-redux"; +import { RootState } from "../store/store"; interface UrlPreviewProps { href: string; @@ -20,13 +23,21 @@ interface UrlPreviewData { function UrlPreview(props: UrlPreviewProps) { let [urlPreviewData, setUrlPreviewData] = useState(undefined); + const linkPreview = useSelector((state: RootState) => state.reducer.frontendSettings).linkPreview; const random_rgba = () => { var o = Math.round, r = Math.random, s = 255; - return 'rgba(' + o(r()*s) + ',' + o(r()*s) + ',' + o(r()*s) + ',' + r().toFixed(1) + ')'; + return 'rgba(' + o(r() * s) + ',' + o(r() * s) + ',' + o(r() * s) + ',' + r().toFixed(1) + ')'; } useEffect(() => { + if (!linkPreview.enabled) return; + if (!linkPreview.allow_all && !linkPreview.urls.some(v => { + let url = new URL(props.href); + return url.hostname.endsWith(v); + })) return; + console.log("fetching url preview data for: ", props.href) + invoke('get_open_graph_data_from_website', { url: props.href }).then((data) => { setUrlPreviewData(JSON.parse(data)); props.onLoaded?.(); @@ -37,14 +48,14 @@ function UrlPreview(props: UrlPreviewProps) { if (urlPreviewData?.image) { return ( + component="img" + height="140" + image={urlPreviewData.image} + alt={urlPreviewData.title} + /> ) } else { - return (); + return (); } }, [urlPreviewData?.image]); @@ -53,7 +64,10 @@ function UrlPreview(props: UrlPreviewProps) { if (urlPreviewData && (urlPreviewData.title || urlPreviewData.description)) { return ( - window.open(props.href, '_blank')}> + { + e.preventDefault(); + openInBrowser(props.href); + }}> {cardMedia} @@ -65,7 +79,7 @@ function UrlPreview(props: UrlPreviewProps) { - diff --git a/src/components/settings/AdditionalFeatures.tsx b/src/components/settings/AdditionalFeatures.tsx index db711b8..1b1a0cd 100644 --- a/src/components/settings/AdditionalFeatures.tsx +++ b/src/components/settings/AdditionalFeatures.tsx @@ -1,12 +1,18 @@ -import { Alert, Box, Container, Divider, Grid, Switch, Typography } from '@mui/material'; +import { Alert, Box, Container, Divider, Grid, Switch, TextField, Typography } from '@mui/material'; import { G } from '@tauri-apps/api/path-e12e0e34'; import React, { useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../store/store'; +import { LinkPreviewSettings, updateLinkPreview } from '../../store/features/users/frontendSettings'; +import { invoke } from '@tauri-apps/api'; interface AdditionalFeaturesProps { } const AdditionalFeatures: React.FC = React.memo(({ }) => { + const linkPreview = useSelector((state: RootState) => state.reducer.frontendSettings).linkPreview; + const dispatch = useDispatch(); let [errorMessage, setErrorMessage] = useState(''); const label = { inputProps: { 'aria-label': 'Link Preview' } }; @@ -17,6 +23,76 @@ const AdditionalFeatures: React.FC = React.memo(({ }) = } } + function updateEnabled() { + updateLinkPreviewState((state) => { + let newState = !linkPreview.enabled; + state.enabled = newState; + + return state; + }); + } + + function updateAllowAll() { + updateLinkPreviewState((state) => { + let newState = !linkPreview.allow_all; + state.allow_all = newState; + + return state; + }); + } + + function updateAllowedUrls(urls: string[]) { + updateLinkPreviewState((state) => { + state.urls = urls; + return state; + }); + } + + function updateLinkPreviewState(stateChangeFunction: (newState: LinkPreviewSettings) => LinkPreviewSettings) { + let stateClone = JSON.parse(JSON.stringify(linkPreview)); + stateClone = stateChangeFunction(stateClone); + + console.log(stateClone); + invoke('save_frontend_settings', { settingsName: 'link_preview', data: stateClone }).then(() => { + console.log('Saved settings.'); + }).catch(e => { + console.log(e); + setErrorMessage('Failed to save settings.'); + }); + + dispatch(updateLinkPreview(stateClone)); + } + + function alloAllSetting() { + if (!linkPreview.enabled) return (); + + return ( + + + Allow URLs from all sources + + + updateAllowAll()} /> + + + ) + } + + function linkPreviewSettings() { + if (!linkPreview.enabled || linkPreview.allow_all) return (); + + return ( + + + Link Preview Urls + + + updateAllowedUrls(text.target.value.split(/\r?\n/))} maxRows={10} /> + + + ) + } + return ( @@ -28,9 +104,11 @@ const AdditionalFeatures: React.FC = React.memo(({ }) = Enable Link Preview - + updateEnabled()} /> + {alloAllSetting()} + {linkPreviewSettings()} ) diff --git a/src/routes/Chat.tsx b/src/routes/Chat.tsx index 1fd04cc..6e2e715 100644 --- a/src/routes/Chat.tsx +++ b/src/routes/Chat.tsx @@ -4,18 +4,30 @@ import ChatMessageContainer from '../components/ChatMessageContainer'; import Sidebar from '../components/Sidebar'; import { RootState } from '../store/store'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import ChatInput from '../components/ChatInput'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import ChatInfoBar from '../components/ChatInfoBar'; import EventLog from '../components/EventLog'; +import { invoke } from '@tauri-apps/api'; +import { updateLinkPreview } from '../store/features/users/frontendSettings'; function Chat() { const [showLog, setShowLog] = useState(false); const messageLog = useSelector((state: RootState) => state.reducer.chatMessage); + const dispatch = useDispatch(); + + useEffect(() => { + invoke('get_frontend_settings', { settingsName: 'link_preview' }).then((result) => { + console.log("settings: ", result); + dispatch(updateLinkPreview(JSON.parse(result))); + }).catch(e => { + console.log(e); + }); + }, []); return ( @@ -32,4 +44,4 @@ function Chat() { ) } -export default Chat; \ No newline at end of file +export default Chat; diff --git a/src/store/features/users/eventLogReducer.ts b/src/store/features/users/eventLogReducer.ts index b4cf257..287ed4d 100644 --- a/src/store/features/users/eventLogReducer.ts +++ b/src/store/features/users/eventLogReducer.ts @@ -8,54 +8,77 @@ interface EventLogState { logMessage: string } +interface CheckData { + condition: (userInfo: UsersState | null, payload: UsersState) => boolean; + message: (userInfo: UsersState | null, payload: UsersState, storeAPI?: any) => string; + stopAfter?: (userInfo: UsersState | null) => boolean; +} + +const checks: CheckData[] = [ + { + condition: (userInfo, payload) => !userInfo, + message: (userInfo, payload) => `${payload.name} joined the server`, + stopAfter: (userInfo) => !userInfo + }, + { + condition: (userInfo, payload) => userInfo?.self_deaf !== payload.self_deaf && userInfo?.self_mute !== payload.self_mute, + message: (userInfo, payload) => `${userInfo?.name} ${payload.self_deaf ? "deafened" : "undeafened"} and ${payload.self_mute ? "muted" : "unmuted"}` + }, + { + condition: (userInfo, payload) => userInfo?.self_deaf !== payload.self_deaf, + message: (userInfo, payload) => `${userInfo?.name} ${payload.self_deaf ? "deafened" : "undeafened"}` + }, + { + condition: (userInfo, payload) => userInfo?.self_mute !== payload.self_mute, + message: (userInfo, payload) => `${userInfo?.name} ${payload.self_mute ? "muted" : "unmuted"}` + }, + { + condition: (userInfo, payload) => userInfo?.mute !== payload.mute, + message: (userInfo, payload) => `${userInfo?.name} ${payload.mute ? "was muted" : "was unmuted"} by unknown` + }, + { + condition: (userInfo, payload) => userInfo?.deaf !== payload.deaf, + message: (userInfo, payload) => `${userInfo?.name} ${payload.deaf ? "was deafened" : "was undeafened"} by unknown` + }, + { + condition: (userInfo, payload) => userInfo?.channel_id !== payload.channel_id, + message: (userInfo, payload, storeAPI: any) => { + const channelInfo = findChannelById(payload.channel_id, storeAPI); + const oldChannel = findChannelById(userInfo?.channel_id || -1, storeAPI); + return `${userInfo?.name} joined ${channelInfo?.name} from ${oldChannel?.name}`; + } + }, + { + condition: (userInfo, payload) => userInfo?.priority_speaker !== payload.priority_speaker, + message: (userInfo, payload) => `${userInfo?.name} ${payload.priority_speaker ? "is now a priority speaker" : "is no longer a priority speaker"}` + }, + { + condition: (userInfo, payload) => userInfo?.suppress !== payload.suppress, + message: (userInfo, payload) => `${userInfo?.name} ${payload.suppress ? "is now suppressed" : "is no longer suppressed"}` + }, + { + condition: (userInfo, payload) => userInfo?.name !== payload.name, + message: (userInfo, payload) => `${userInfo?.name} changed their name to ${payload.name}` + }, + { + condition: (userInfo, payload) => userInfo?.recording !== payload.recording, + message: (userInfo, payload) => `${userInfo?.name} ${payload.recording ? "started recording" : "stopped recording"}` + } +]; + // Handle function for updateUser.fulfilled function handleUpdateUser(action: { payload: UsersState }, storeAPI: any) { const userState: UserInfoState = storeAPI.getState().reducer.userInfo; const userId = action.payload.id; const userInfo = getUserInfo(userState, userId); - if (!userInfo) { - storeAPI.dispatch(eventLogSlice.actions.dispatchEventLog({ message: `${action.payload.name} joined the server` })); - return - }; - - if(userInfo.self_deaf !== action.payload.self_deaf && userInfo.self_mute !== action.payload.self_mute) { - storeAPI.dispatch(eventLogSlice.actions.dispatchEventLog({ message: `${userInfo.name} ${action.payload.self_deaf ? "deafened" : "undeafened"} and ${action.payload.self_mute ? "muted" : "unmuted"}` })); - } else if (userInfo.self_deaf !== action.payload.self_deaf) { - storeAPI.dispatch(eventLogSlice.actions.dispatchEventLog({ message: `${userInfo.name} ${action.payload.self_deaf ? "deafened" : "undeafened"}` })); - } else if (userInfo.self_mute !== action.payload.self_mute) { - storeAPI.dispatch(eventLogSlice.actions.dispatchEventLog({ message: `${userInfo.name} ${action.payload.self_mute ? "muted" : "unmuted"}` })); - } - - if (userInfo.mute !== action.payload.mute) { - storeAPI.dispatch(eventLogSlice.actions.dispatchEventLog({ message: `${userInfo.name} ${action.payload.mute ? "was muted" : "was unmuted"} by unkonwn` })); - } - - if (userInfo.deaf !== action.payload.deaf) { - storeAPI.dispatch(eventLogSlice.actions.dispatchEventLog({ message: `${userInfo.name} ${action.payload.deaf ? "was deafened" : "was undeafened"} by unkonwn` })); - } - - if (userInfo.channel_id !== action.payload.channel_id) { - const channelInfo = findChannelById(action.payload.channel_id, storeAPI); - const oldChannel = findChannelById(userInfo.channel_id, storeAPI); - storeAPI.dispatch(eventLogSlice.actions.dispatchEventLog({ message: `${userInfo.name} joined ${channelInfo?.name} from ${oldChannel?.name}` })); - } - - if(userInfo.priority_speaker !== action.payload.priority_speaker) { - storeAPI.dispatch(eventLogSlice.actions.dispatchEventLog({ message: `${userInfo.name} ${action.payload.priority_speaker ? "is now a priority speaker" : "is no longer a priority speaker"}` })); - } - - if(userInfo.suppress !== action.payload.suppress) { - storeAPI.dispatch(eventLogSlice.actions.dispatchEventLog({ message: `${userInfo.name} ${action.payload.suppress ? "is now suppressed" : "is no longer suppressed"}` })); - } - - if(userInfo.name !== action.payload.name) { - storeAPI.dispatch(eventLogSlice.actions.dispatchEventLog({ message: `${userInfo.name} changed their name to ${action.payload.name}` })); - } - - if(userInfo.recording !== action.payload.recording) { - storeAPI.dispatch(eventLogSlice.actions.dispatchEventLog({ message: `${userInfo.name} ${action.payload.recording ? "started recording" : "stopped recording"}` })); - } + checks.every(({condition, message, stopAfter}) => { + if(condition(userInfo, action.payload)) { + storeAPI.dispatch(eventLogSlice.actions.dispatchEventLog({ message: message(userInfo, action.payload, storeAPI) })); + } + if(stopAfter && stopAfter(userInfo)) return false; + return true; + }); } // Handle function for deleteUser diff --git a/src/store/features/users/frontendSettings.ts b/src/store/features/users/frontendSettings.ts new file mode 100644 index 0000000..1246ea6 --- /dev/null +++ b/src/store/features/users/frontendSettings.ts @@ -0,0 +1,35 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { invoke } from "@tauri-apps/api"; + +export interface LinkPreviewSettings { + enabled: boolean, + allow_all: boolean, + urls: string[] +} + +export interface FrontendSettings { + linkPreview: LinkPreviewSettings +} + + +const initialState: FrontendSettings = { + linkPreview: { + enabled: true, + allow_all: false, + urls: [] + } +}; + +export const frontendSettings = createSlice({ + name: 'channel', + initialState, + reducers: { + updateLinkPreview: (state, action) => { + state.linkPreview = action.payload; + } + }, +}) + +export const { updateLinkPreview } = frontendSettings.actions + +export default frontendSettings.reducer \ No newline at end of file diff --git a/src/store/features/users/userSettings.ts b/src/store/features/users/userSettings.ts deleted file mode 100644 index 0d0f32c..0000000 --- a/src/store/features/users/userSettings.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; - - -export interface UserSettings { -} - - -const initialState: UserSettings[] = []; - -export const userSettings = createSlice({ - name: 'channel', - initialState, - reducers: { - - }, -}) - -export const { } = userSettings.actions - -export default userSettings.reducer \ No newline at end of file diff --git a/src/store/store.ts b/src/store/store.ts index 2f9e279..76c9658 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -3,11 +3,13 @@ import userReducer from './features/users/userSlice'; import channelReducer from './features/users/channelSlice'; import chatMessageReducer from './features/users/chatMessageSlice'; import eventLogReducer, { checkStatusChangedMiddleware } from './features/users/eventLogReducer'; +import frontendSettingsReducer from './features/users/frontendSettings'; const combinedReducer = combineReducers({ channel: channelReducer, userInfo: userReducer, - chatMessage: chatMessageReducer + chatMessage: chatMessageReducer, + frontendSettings: frontendSettingsReducer }) const rootReducer = (state: any, action: any) => {