{
tooltip: {
sx: {
color: theme.palette.text.primary,
- backgroundColor: theme.palette.background.default,
+ backgroundColor: theme.palette.background.paper,
},
},
arrow: {
@@ -87,7 +87,11 @@ export const QMailStatus = () => {
},
}}
>
-
+
);
diff --git a/src/components/RegisterName.tsx b/src/components/RegisterName.tsx
index 35af458..4cee6ec 100644
--- a/src/components/RegisterName.tsx
+++ b/src/components/RegisterName.tsx
@@ -1,28 +1,29 @@
-import React, { useCallback, useContext, useEffect, useState } from 'react'
+import React, { useCallback, useContext, useEffect, useState } from 'react';
import {
- Avatar,
- Box,
- Button,
- ButtonBase,
- Collapse,
- Dialog,
- DialogActions,
- DialogContent,
- DialogContentText,
- DialogTitle,
- Input,
- ListItem,
- ListItemAvatar,
- ListItemButton,
- ListItemIcon,
- ListItemText,
- List,
- MenuItem,
- Popover,
- Select,
- TextField,
- Typography,
- } from "@mui/material";
+ Avatar,
+ Box,
+ Button,
+ ButtonBase,
+ Collapse,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogContentText,
+ DialogTitle,
+ Input,
+ ListItem,
+ ListItemAvatar,
+ ListItemButton,
+ ListItemIcon,
+ ListItemText,
+ List,
+ MenuItem,
+ Popover,
+ Select,
+ TextField,
+ Typography,
+ useTheme,
+} from '@mui/material';
import { Label } from './Group/AddGroup';
import { Spacer } from '../common/Spacer';
import { LoadingButton } from '@mui/lab';
@@ -35,278 +36,313 @@ import CheckIcon from '@mui/icons-material/Check';
import ErrorIcon from '@mui/icons-material/Error';
enum Availability {
- NULL = 'null',
- LOADING = 'loading',
- AVAILABLE = 'available',
- NOT_AVAILABLE = 'not-available'
+ NULL = 'null',
+ LOADING = 'loading',
+ AVAILABLE = 'available',
+ NOT_AVAILABLE = 'not-available',
}
-export const RegisterName = ({setOpenSnack, setInfoSnack, userInfo, show, setTxList, balance}) => {
- const [isOpen, setIsOpen] = useState(false)
- const [registerNameValue, setRegisterNameValue] = useState('')
- const [isLoadingRegisterName, setIsLoadingRegisterName] = useState(false)
- const [isNameAvailable, setIsNameAvailable] = useState
(Availability.NULL)
- const [nameFee, setNameFee] = useState(null)
+export const RegisterName = ({
+ setOpenSnack,
+ setInfoSnack,
+ userInfo,
+ show,
+ setTxList,
+ balance,
+}) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [registerNameValue, setRegisterNameValue] = useState('');
+ const [isLoadingRegisterName, setIsLoadingRegisterName] = useState(false);
+ const [isNameAvailable, setIsNameAvailable] = useState(
+ Availability.NULL
+ );
+ const [nameFee, setNameFee] = useState(null);
+ const theme = useTheme();
+ const checkIfNameExisits = async (name) => {
+ if (!name?.trim()) {
+ setIsNameAvailable(Availability.NULL);
- const checkIfNameExisits = async (name)=> {
- if(!name?.trim()){
- setIsNameAvailable(Availability.NULL)
-
- return
+ return;
}
- setIsNameAvailable(Availability.LOADING)
+ setIsNameAvailable(Availability.LOADING);
try {
- const res = await fetch(`${getBaseApiReact()}/names/` + name);
- const data = await res.json()
- if(data?.message === 'name unknown'){
- setIsNameAvailable(Availability.AVAILABLE)
- } else {
- setIsNameAvailable(Availability.NOT_AVAILABLE)
- }
+ const res = await fetch(`${getBaseApiReact()}/names/` + name);
+ const data = await res.json();
+ if (data?.message === 'name unknown') {
+ setIsNameAvailable(Availability.AVAILABLE);
+ } else {
+ setIsNameAvailable(Availability.NOT_AVAILABLE);
+ }
} catch (error) {
- console.error(error)
+ console.error(error);
} finally {
}
- }
- // Debounce logic
- useEffect(() => {
- const handler = setTimeout(() => {
- checkIfNameExisits(registerNameValue);
- }, 500);
-
- // Cleanup timeout if searchValue changes before the timeout completes
- return () => {
- clearTimeout(handler);
- };
- }, [registerNameValue]);
+ };
+ // Debounce logic
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ checkIfNameExisits(registerNameValue);
+ }, 500);
- const openRegisterNameFunc = useCallback((e) => {
- setIsOpen(true)
+ // Cleanup timeout if searchValue changes before the timeout completes
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [registerNameValue]);
- }, [ setIsOpen]);
-
- useEffect(() => {
- subscribeToEvent("openRegisterName", openRegisterNameFunc);
-
- return () => {
- unsubscribeFromEvent("openRegisterName", openRegisterNameFunc);
- };
- }, [openRegisterNameFunc]);
+ const openRegisterNameFunc = useCallback(
+ (e) => {
+ setIsOpen(true);
+ },
+ [setIsOpen]
+ );
- useEffect(()=> {
- const nameRegistrationFee = async ()=> {
- try {
- const fee = await getFee("REGISTER_NAME");
- setNameFee(fee?.fee)
- } catch (error) {
- console.error(error)
- }
- }
- nameRegistrationFee()
- }, [])
+ useEffect(() => {
+ subscribeToEvent('openRegisterName', openRegisterNameFunc);
- const registerName = async () => {
+ return () => {
+ unsubscribeFromEvent('openRegisterName', openRegisterNameFunc);
+ };
+ }, [openRegisterNameFunc]);
+
+ useEffect(() => {
+ const nameRegistrationFee = async () => {
try {
- if (!userInfo?.address) throw new Error("Your address was not found");
- if(!registerNameValue) throw new Error('Enter a name')
-
- const fee = await getFee("REGISTER_NAME");
- await show({
- message: "Would you like to register this name?",
- publishFee: fee.fee + " QORT",
- });
- setIsLoadingRegisterName(true);
- new Promise((res, rej) => {
- window
- .sendMessage("registerName", {
- name: registerNameValue,
- })
- .then((response) => {
- if (!response?.error) {
- res(response);
- setIsLoadingRegisterName(false);
- setInfoSnack({
- type: "success",
- message:
- "Successfully registered. It may take a couple of minutes for the changes to propagate",
- });
- setIsOpen(false);
- setRegisterNameValue("");
- setOpenSnack(true);
- setTxList((prev) => [
- {
- ...response,
- type: "register-name",
- label: `Registered name: awaiting confirmation. This may take a couple minutes.`,
- labelDone: `Registered name: success!`,
- done: false,
- },
- ...prev.filter((item) => !item.done),
- ]);
- return;
- }
- setInfoSnack({
- type: "error",
- message: response?.error,
- });
- setOpenSnack(true);
- rej(response.error);
- })
- .catch((error) => {
- setInfoSnack({
- type: "error",
- message: error.message || "An error occurred",
- });
- setOpenSnack(true);
- rej(error);
- });
- });
+ const fee = await getFee('REGISTER_NAME');
+ setNameFee(fee?.fee);
} catch (error) {
- if (error?.message) {
- setOpenSnack(true)
- setInfoSnack({
- type: "error",
- message: error?.message,
- });
- }
- } finally {
- setIsLoadingRegisterName(false);
+ console.error(error);
}
};
+ nameRegistrationFee();
+ }, []);
+
+ const registerName = async () => {
+ try {
+ if (!userInfo?.address) throw new Error('Your address was not found');
+ if (!registerNameValue) throw new Error('Enter a name');
+
+ const fee = await getFee('REGISTER_NAME');
+ await show({
+ message: 'Would you like to register this name?',
+ publishFee: fee.fee + ' QORT',
+ });
+ setIsLoadingRegisterName(true);
+ new Promise((res, rej) => {
+ window
+ .sendMessage('registerName', {
+ name: registerNameValue,
+ })
+ .then((response) => {
+ if (!response?.error) {
+ res(response);
+ setIsLoadingRegisterName(false);
+ setInfoSnack({
+ type: 'success',
+ message:
+ 'Successfully registered. It may take a couple of minutes for the changes to propagate',
+ });
+ setIsOpen(false);
+ setRegisterNameValue('');
+ setOpenSnack(true);
+ setTxList((prev) => [
+ {
+ ...response,
+ type: 'register-name',
+ label: `Registered name: awaiting confirmation. This may take a couple minutes.`,
+ labelDone: `Registered name: success!`,
+ done: false,
+ },
+ ...prev.filter((item) => !item.done),
+ ]);
+ return;
+ }
+ setInfoSnack({
+ type: 'error',
+ message: response?.error,
+ });
+ setOpenSnack(true);
+ rej(response.error);
+ })
+ .catch((error) => {
+ setInfoSnack({
+ type: 'error',
+ message: error.message || 'An error occurred',
+ });
+ setOpenSnack(true);
+ rej(error);
+ });
+ });
+ } catch (error) {
+ if (error?.message) {
+ setOpenSnack(true);
+ setInfoSnack({
+ type: 'error',
+ message: error?.message,
+ });
+ }
+ } finally {
+ setIsLoadingRegisterName(false);
+ }
+ };
return (
- )
-}
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx
index 413b9a5..a19efd2 100644
--- a/src/components/Save/Save.tsx
+++ b/src/components/Save/Save.tsx
@@ -321,12 +321,12 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
}}
variant="contained"
sx={{
- backgroundColor: 'var(--danger)',
+ backgroundColor: theme.palette.other.danger,
color: 'black',
fontWeight: 'bold',
opacity: 0.7,
'&:hover': {
- backgroundColor: 'var(--danger)',
+ backgroundColor: theme.palette.other.danger,
color: 'black',
opacity: 1,
},
@@ -393,12 +393,12 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
{
onClick={revertChanges}
variant="contained"
sx={{
- backgroundColor: 'var(--danger)',
+ backgroundColor: theme.palette.other.danger,
color: 'black',
fontWeight: 'bold',
opacity: 0.7,
'&:hover': {
- backgroundColor: 'var(--danger)',
+ backgroundColor: theme.palette.other.danger,
color: 'black',
opacity: 1,
},
@@ -503,12 +503,12 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
onClick={saveToQdn}
variant="contained"
sx={{
- backgroundColor: 'var(--danger)',
+ backgroundColor: theme.palette.other.danger,
color: 'black',
fontWeight: 'bold',
opacity: 0.7,
'&:hover': {
- backgroundColor: 'var(--danger)',
+ backgroundColor: theme.palette.other.danger,
color: 'black',
opacity: 1,
},
diff --git a/src/components/Theme/ThemeContext.tsx b/src/components/Theme/ThemeContext.tsx
index d2e7048..0f24b4a 100644
--- a/src/components/Theme/ThemeContext.tsx
+++ b/src/components/Theme/ThemeContext.tsx
@@ -6,57 +6,129 @@ import {
useEffect,
useCallback,
} from 'react';
-import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles';
-import { darkTheme } from '../../styles/theme-dark';
-import { lightTheme } from '../../styles/theme-light';
+import {
+ ThemeProvider as MuiThemeProvider,
+ createTheme,
+} from '@mui/material/styles';
+import { lightThemeOptions } from '../../styles/theme-light';
+import { darkThemeOptions } from '../../styles/theme-dark';
+
+const defaultTheme = {
+ id: 'default',
+ name: 'Default Theme',
+ light: lightThemeOptions.palette,
+ dark: darkThemeOptions.palette,
+};
const ThemeContext = createContext({
themeMode: 'light',
toggleTheme: () => {},
+ userThemes: [defaultTheme],
+ addUserTheme: (themes) => {},
+ setUserTheme: (theme) => {},
+ currentThemeId: 'default',
});
-export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
+export const ThemeProvider = ({ children }) => {
const [themeMode, setThemeMode] = useState('light');
+ const [userThemes, setUserThemes] = useState([defaultTheme]);
+ const [currentThemeId, setCurrentThemeId] = useState('default');
- const theme = useMemo(
- () => (themeMode === 'light' ? lightTheme : darkTheme),
- [themeMode]
- );
+ const currentTheme =
+ userThemes.find((theme) => theme.id === currentThemeId) || defaultTheme;
+
+ const muiTheme = useMemo(() => {
+ if (themeMode === 'light') {
+ return createTheme({
+ ...lightThemeOptions,
+ palette: {
+ ...currentTheme.light,
+ },
+ });
+ } else {
+ return createTheme({
+ ...lightThemeOptions,
+ palette: {
+ ...currentTheme.dark,
+ },
+ });
+ }
+ }, [themeMode, currentTheme]);
+
+ const saveSettings = (
+ themes = userThemes,
+ mode = themeMode,
+ themeId = currentThemeId
+ ) => {
+ localStorage.setItem(
+ 'saved_ui_theme',
+ JSON.stringify({
+ mode,
+ userThemes: themes,
+ currentThemeId: themeId,
+ })
+ );
+ };
const toggleTheme = () => {
- setThemeMode((prevMode) => {
- const newMode = prevMode === 'light' ? 'dark' : 'light';
-
- const themeProperties = {
- mode: newMode,
- };
-
- localStorage.setItem('saved_ui_theme', JSON.stringify(themeProperties));
-
+ setThemeMode((prev) => {
+ const newMode = prev === 'light' ? 'dark' : 'light';
+ saveSettings(userThemes, newMode, currentThemeId);
return newMode;
});
};
- const getSavedTheme = useCallback(async () => {
- try {
- const themeProperties = JSON.parse(
- localStorage.getItem(`saved_ui_theme`) || '{}'
- );
+ const addUserTheme = (themes) => {
+ setUserThemes(themes);
+ saveSettings(themes);
+ };
- const theme = themeProperties?.mode || 'light';
- setThemeMode(theme);
- } catch (error) {
- console.log('error', error);
+ const setUserTheme = (theme) => {
+ if (theme.id === 'default') {
+ setCurrentThemeId('default');
+ saveSettings(userThemes, themeMode, 'default');
+ } else {
+ setCurrentThemeId(theme.id);
+ saveSettings(userThemes, themeMode, theme.id);
+ }
+ };
+
+ const loadSettings = useCallback(() => {
+ const saved = localStorage.getItem('saved_ui_theme');
+ if (saved) {
+ try {
+ const parsed = JSON.parse(saved);
+ if (parsed.mode === 'light' || parsed.mode === 'dark')
+ setThemeMode(parsed.mode);
+ if (Array.isArray(parsed.userThemes)) {
+ const filteredThemes = parsed.userThemes.filter(
+ (theme) => theme.id !== 'default'
+ );
+ setUserThemes([defaultTheme, ...filteredThemes]);
+ }
+ if (parsed.currentThemeId) setCurrentThemeId(parsed.currentThemeId);
+ } catch (error) {
+ console.error('Failed to parse saved_ui_theme:', error);
+ }
}
}, []);
useEffect(() => {
- getSavedTheme();
- }, [getSavedTheme]);
+ loadSettings();
+ }, [loadSettings]);
return (
-
- {children}
+
+ {children}
);
};
diff --git a/src/components/Theme/ThemeManager.tsx b/src/components/Theme/ThemeManager.tsx
new file mode 100644
index 0000000..eca0e9f
--- /dev/null
+++ b/src/components/Theme/ThemeManager.tsx
@@ -0,0 +1,403 @@
+import React, { useState, useRef, useEffect } from 'react';
+import {
+ Box,
+ Button,
+ IconButton,
+ Typography,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ List,
+ ListItemText,
+ ListItemSecondaryAction,
+ TextField,
+ Tabs,
+ Tab,
+ ListItemButton,
+} from '@mui/material';
+import { Sketch } from '@uiw/react-color';
+import DeleteIcon from '@mui/icons-material/Delete';
+import EditIcon from '@mui/icons-material/Edit';
+import AddIcon from '@mui/icons-material/Add';
+import CheckIcon from '@mui/icons-material/Check';
+import { useThemeContext } from './ThemeContext';
+import { darkThemeOptions } from '../../styles/theme-dark';
+import { lightThemeOptions } from '../../styles/theme-light';
+import ShortUniqueId from 'short-unique-id';
+import { rgbStringToHsva, rgbaStringToHsva } from '@uiw/color-convert';
+import FileDownloadIcon from '@mui/icons-material/FileDownload';
+import { saveFileToDiskGeneric } from '../../utils/generateWallet/generateWallet';
+import { handleImportClick } from '../../utils/fileReading';
+const uid = new ShortUniqueId({ length: 8 });
+
+function detectColorFormat(color) {
+ if (typeof color !== 'string') return null;
+ if (color.startsWith('rgba')) return 'rgba';
+ if (color.startsWith('rgb')) return 'rgb';
+ return null;
+}
+
+const validateTheme = (theme) => {
+ if (typeof theme !== 'object' || !theme) return false;
+ if (typeof theme.name !== 'string') return false;
+ if (!theme.light || typeof theme.light !== 'object') return false;
+ if (!theme.dark || typeof theme.dark !== 'object') return false;
+
+ // Optional: deeper checks on structure
+ const requiredKeys = [
+ 'primary',
+ 'secondary',
+ 'background',
+ 'text',
+ 'border',
+ 'other',
+ ];
+
+ for (const mode of ['light', 'dark']) {
+ const modeTheme = theme[mode];
+ if (modeTheme.mode !== mode) return false;
+
+ for (const key of requiredKeys) {
+ if (!modeTheme[key] || typeof modeTheme[key] !== 'object') {
+ return false;
+ }
+ }
+ }
+
+ return true;
+};
+
+export default function ThemeManager() {
+ const { userThemes, addUserTheme, setUserTheme, currentThemeId } =
+ useThemeContext();
+ const [openEditor, setOpenEditor] = useState(false);
+ const [themeDraft, setThemeDraft] = useState({
+ id: '',
+ name: '',
+ light: {},
+ dark: {},
+ });
+ const [currentTab, setCurrentTab] = useState('light');
+ const nameInputRef = useRef(null);
+
+ useEffect(() => {
+ if (openEditor && nameInputRef.current) {
+ nameInputRef.current.focus();
+ }
+ }, [openEditor]);
+
+ const handleAddTheme = () => {
+ setThemeDraft({
+ id: '',
+ name: '',
+ light: structuredClone(lightThemeOptions.palette),
+ dark: structuredClone(darkThemeOptions.palette),
+ });
+ setOpenEditor(true);
+ };
+
+ const handleEditTheme = (themeId) => {
+ const themeToEdit = userThemes.find((theme) => theme.id === themeId);
+ if (themeToEdit) {
+ setThemeDraft({ ...themeToEdit });
+ setOpenEditor(true);
+ }
+ };
+
+ const handleSaveTheme = () => {
+ if (themeDraft.id) {
+ const updatedThemes = [...userThemes];
+ const index = updatedThemes.findIndex(
+ (theme) => theme.id === themeDraft.id
+ );
+ if (index !== -1) {
+ updatedThemes[index] = themeDraft;
+ addUserTheme(updatedThemes);
+ }
+ } else {
+ const newTheme = { ...themeDraft, id: uid.rnd() };
+ const updatedThemes = [...userThemes, newTheme];
+ addUserTheme(updatedThemes);
+ setUserTheme(newTheme);
+ }
+ setOpenEditor(false);
+ };
+
+ const handleDeleteTheme = (id) => {
+ const updatedThemes = userThemes.filter((theme) => theme.id !== id);
+ addUserTheme(updatedThemes);
+
+ if (id === currentThemeId) {
+ // Find the default theme object in the list
+ const defaultTheme = updatedThemes.find(
+ (theme) => theme.id === 'default'
+ );
+
+ if (defaultTheme) {
+ setUserTheme(defaultTheme);
+ } else {
+ // Emergency fallback
+ setUserTheme({
+ light: lightThemeOptions,
+ dark: darkThemeOptions,
+ });
+ }
+ }
+ };
+
+ const handleApplyTheme = (theme) => {
+ setUserTheme(theme);
+ };
+
+ const handleColorChange = (mode, fieldPath, color) => {
+ setThemeDraft((prev) => {
+ const updated = { ...prev };
+ const paths = fieldPath.split('.');
+ updated[mode][paths[0]][paths[1]] = color.hex;
+ return updated;
+ });
+ };
+
+ const renderColorPicker = (mode, label, fieldPath, currentValue) => {
+ let color = currentValue || '#ffffff';
+ const format = detectColorFormat(currentValue);
+ if (format === 'rgba') {
+ color = rgbaStringToHsva(currentValue);
+ } else if (format === 'rgb') {
+ color = rgbStringToHsva(currentValue);
+ }
+ return (
+
+
+ {label}
+
+ handleColorChange(mode, fieldPath, color)}
+ />
+
+ );
+ };
+
+ const exportTheme = async (theme) => {
+ try {
+ const copyTheme = structuredClone(theme);
+ delete copyTheme.id;
+ const fileName = `ui_theme_${theme.name}.json`;
+
+ const blob = new Blob([JSON.stringify(copyTheme, null, 2)], {
+ type: 'application/json',
+ });
+
+ await saveFileToDiskGeneric(blob, fileName);
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ const importTheme = async (theme) => {
+ try {
+ const fileContent = await handleImportClick('.json');
+ const importedTheme = JSON.parse(fileContent);
+ if (!validateTheme(importedTheme)) {
+ throw new Error('Invalid theme format');
+ }
+ const newTheme = { ...importedTheme, id: uid.rnd() };
+ const updatedThemes = [...userThemes, newTheme];
+ addUserTheme(updatedThemes);
+ setUserTheme(newTheme);
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ return (
+
+
+ Theme Manager
+
+
+ }
+ onClick={handleAddTheme}
+ >
+ Add Theme
+
+ }
+ onClick={importTheme}
+ >
+ Import theme
+
+
+ {userThemes?.map((theme, index) => (
+
+
+
+ {theme.id !== 'default' && (
+ <>
+ exportTheme(theme)}>
+
+
+ handleEditTheme(theme.id)}>
+
+
+ handleDeleteTheme(theme.id)}>
+
+
+ >
+ )}
+ handleApplyTheme(theme)}>
+
+
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/components/Theme/themeManager.css b/src/components/Theme/themeManager.css
new file mode 100644
index 0000000..85a4538
--- /dev/null
+++ b/src/components/Theme/themeManager.css
@@ -0,0 +1,39 @@
+[data-color-mode*='dark'] .w-color-sketch {
+ --sketch-background: #323232 !important;
+}
+
+[data-color-mode*='dark'] .w-color-swatch {
+ --sketch-swatch-border-top: 1px solid #525252 !important;
+}
+
+[data-color-mode*='dark'] .w-color-block {
+ --block-background-color: #323232 !important;
+ --block-box-shadow: rgb(0 0 0 / 10%) 0 1px !important;
+}
+
+[data-color-mode*='dark'] .w-color-editable-input {
+ --editable-input-label-color: #757575 !important;
+ --editable-input-box-shadow: #616161 0px 0px 0px 1px inset !important;
+ --editable-input-color: #bbb !important;
+}
+
+[data-color-mode*='dark'] .w-color-github {
+ --github-border: 1px solid rgba(0, 0, 0, 0.2) !important;
+ --github-background-color: #323232 !important;
+ --github-box-shadow: rgb(0 0 0 / 15%) 0px 3px 12px !important;
+ --github-arrow-border-color: rgba(0, 0, 0, 0.15) !important;
+}
+
+[data-color-mode*='dark'] .w-color-compact {
+ --compact-background-color: #323232 !important;
+}
+
+[data-color-mode*='dark'] .w-color-material {
+ --material-background-color: #323232 !important;
+ --material-border-bottom-color: #707070 !important;
+}
+
+[data-color-mode*='dark'] .w-color-alpha {
+ --alpha-pointer-background-color: #6a6a6a !important;
+ --alpha-pointer-box-shadow: rgb(0 0 0 / 37%) 0px 1px 4px 0px !important;
+}
diff --git a/src/styles/App-styles.ts b/src/styles/App-styles.ts
index 980657b..af273e5 100644
--- a/src/styles/App-styles.ts
+++ b/src/styles/App-styles.ts
@@ -54,7 +54,6 @@ export const AuthenticatedContainerInnerTop = styled(Box)(({ theme }) => ({
}));
export const TextP = styled(Typography)(({ theme }) => ({
- backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
fontFamily: 'Inter',
fontSize: '13px',
@@ -62,7 +61,6 @@ export const TextP = styled(Typography)(({ theme }) => ({
}));
export const TextItalic = styled('span')(({ theme }) => ({
- backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
fontFamily: 'Inter',
fontSize: '13px',
@@ -71,7 +69,6 @@ export const TextItalic = styled('span')(({ theme }) => ({
}));
export const TextSpan = styled('span')(({ theme }) => ({
- backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
fontFamily: 'Inter',
fontSize: '13px',
@@ -131,9 +128,8 @@ export const CustomButton = styled(Box)(({ theme }) => ({
width: 'fit-content',
'&:hover': {
backgroundColor: theme.palette.background.paper,
- color: theme.palette.text.secondary,
'svg path': {
- fill: theme.palette.background.paper,
+ fill: theme.palette.background.secondary,
},
},
}));
diff --git a/src/styles/theme-dark.ts b/src/styles/theme-dark.ts
index 9c9e610..8929561 100644
--- a/src/styles/theme-dark.ts
+++ b/src/styles/theme-dark.ts
@@ -1,26 +1,36 @@
import { createTheme, ThemeOptions } from '@mui/material/styles';
import { commonThemeOptions } from './theme-common';
-const darkThemeOptions: ThemeOptions = {
+export const darkThemeOptions: ThemeOptions = {
...commonThemeOptions,
palette: {
mode: 'dark',
primary: {
- main: 'rgb(46, 61, 96)',
- dark: 'rgb(5, 20, 53)',
- light: 'rgb(45, 92, 201)',
+ main: 'rgb(100, 155, 240)',
+ dark: 'rgb(45, 92, 201)',
+ light: 'rgb(130, 185, 255)',
},
secondary: {
main: 'rgb(69, 173, 255)',
},
background: {
default: 'rgb(49, 51, 56)',
- paper: 'rgb(96, 96, 97)',
+ paper: 'rgb(62, 64, 68)',
+ surface: 'rgb(58, 60, 65)',
},
text: {
primary: 'rgb(255, 255, 255)',
secondary: 'rgb(179, 179, 179)',
},
+ border: {
+ main: 'rgba(255, 255, 255, 0.12)',
+ subtle: 'rgba(255, 255, 255, 0.08)',
+ },
+ other: {
+ positive: 'rgb(94, 176, 73)',
+ danger: 'rgb(177, 70, 70)',
+ unread: 'rgb(66, 151, 226)',
+ },
},
components: {
MuiCard: {
@@ -76,6 +86,20 @@ const darkThemeOptions: ThemeOptions = {
},
},
},
+ MuiDialog: {
+ styleOverrides: {
+ paper: {
+ backgroundImage: 'none',
+ },
+ },
+ },
+ MuiPopover: {
+ styleOverrides: {
+ paper: {
+ backgroundImage: 'none',
+ },
+ },
+ },
},
};
diff --git a/src/styles/theme-light.ts b/src/styles/theme-light.ts
index 1bd114d..15b1f8b 100644
--- a/src/styles/theme-light.ts
+++ b/src/styles/theme-light.ts
@@ -1,25 +1,35 @@
import { createTheme, ThemeOptions } from '@mui/material/styles';
import { commonThemeOptions } from './theme-common';
-const lightThemeOptions: ThemeOptions = {
+export const lightThemeOptions: ThemeOptions = {
...commonThemeOptions,
palette: {
mode: 'light',
primary: {
- main: 'rgba(244, 244, 251, 1)',
+ main: 'rgb(162, 162, 221)', // old light becomes main
dark: 'rgb(113, 198, 212)',
- light: 'rgb(162, 162, 221)',
+ light: 'rgba(244, 244, 251, 1)', // former main becomes light
},
secondary: {
main: 'rgba(194, 222, 236, 1)',
},
background: {
default: 'rgba(250, 250, 250, 1)',
- paper: 'rgb(228, 228, 228)',
+ paper: 'rgb(220, 220, 220)', // darker card background
+ surface: 'rgb(240, 240, 240)', // optional middle gray for replies, side panels
},
text: {
- primary: 'rgba(0, 0, 0, 1)',
- secondary: 'rgba(82, 82, 82, 1)',
+ primary: 'rgba(0, 0, 0, 0.87)', // 87% black (slightly softened)
+ secondary: 'rgba(0, 0, 0, 0.6)', // 60% black
+ },
+ border: {
+ main: 'rgba(0, 0, 0, 0.12)',
+ subtle: 'rgba(0, 0, 0, 0.08)',
+ },
+ other: {
+ positive: 'rgb(94, 176, 73)',
+ danger: 'rgb(177, 70, 70)',
+ unread: 'rgb(66, 151, 226)',
},
},
components: {
@@ -77,6 +87,20 @@ const lightThemeOptions: ThemeOptions = {
},
},
},
+ MuiDialog: {
+ styleOverrides: {
+ paper: {
+ backgroundImage: 'none',
+ },
+ },
+ },
+ MuiPopover: {
+ styleOverrides: {
+ paper: {
+ backgroundImage: 'none',
+ },
+ },
+ },
},
};
diff --git a/src/styles/theme.d.ts b/src/styles/theme.d.ts
new file mode 100644
index 0000000..e10c5c1
--- /dev/null
+++ b/src/styles/theme.d.ts
@@ -0,0 +1,29 @@
+import '@mui/material/styles';
+
+declare module '@mui/material/styles' {
+ interface TypeBackground {
+ surface: string;
+ }
+ interface Palette {
+ border: {
+ main: string;
+ subtle: string;
+ };
+ other: {
+ positive: string;
+ danger: string;
+ unread: string;
+ };
+ }
+ interface PaletteOptions {
+ border?: {
+ main?: string;
+ subtle?: string;
+ };
+ other?: {
+ positive?: string;
+ danger?: string;
+ unread?: string;
+ };
+ }
+}
diff --git a/src/utils/fileReading/index.ts b/src/utils/fileReading/index.ts
index a6295c8..a04435c 100644
--- a/src/utils/fileReading/index.ts
+++ b/src/utils/fileReading/index.ts
@@ -1,63 +1,93 @@
// @ts-nocheck
class Semaphore {
- constructor(count) {
- this.count = count
- this.waiting = []
- }
- acquire() {
- return new Promise(resolve => {
- if (this.count > 0) {
- this.count--
- resolve()
- } else {
- this.waiting.push(resolve)
- }
- })
- }
- release() {
- if (this.waiting.length > 0) {
- const resolve = this.waiting.shift()
- resolve()
- } else {
- this.count++
- }
- }
+ constructor(count) {
+ this.count = count;
+ this.waiting = [];
+ }
+ acquire() {
+ return new Promise((resolve) => {
+ if (this.count > 0) {
+ this.count--;
+ resolve();
+ } else {
+ this.waiting.push(resolve);
+ }
+ });
+ }
+ release() {
+ if (this.waiting.length > 0) {
+ const resolve = this.waiting.shift();
+ resolve();
+ } else {
+ this.count++;
+ }
+ }
}
-let semaphore = new Semaphore(1)
-let reader = new FileReader()
+let semaphore = new Semaphore(1);
+let reader = new FileReader();
-export const fileToBase64 = (file) => new Promise(async (resolve, reject) => {
- const reader = new FileReader(); // Create a new instance
- await semaphore.acquire();
- reader.readAsDataURL(file);
- reader.onload = () => {
- const dataUrl = reader.result;
- semaphore.release();
- if (typeof dataUrl === 'string') {
- resolve(dataUrl.split(',')[1]);
- } else {
- reject(new Error('Invalid data URL'));
- }
- reader.onload = null; // Clear the handler
- reader.onerror = null; // Clear the handle
- };
- reader.onerror = (error) => {
- semaphore.release();
- reject(error);
- reader.onload = null; // Clear the handler
- reader.onerror = null; // Clear the handle
- };
+export const fileToBase64 = (file) =>
+ new Promise(async (resolve, reject) => {
+ const reader = new FileReader(); // Create a new instance
+ await semaphore.acquire();
+ reader.readAsDataURL(file);
+ reader.onload = () => {
+ const dataUrl = reader.result;
+ semaphore.release();
+ if (typeof dataUrl === 'string') {
+ resolve(dataUrl.split(',')[1]);
+ } else {
+ reject(new Error('Invalid data URL'));
+ }
+ reader.onload = null; // Clear the handler
+ reader.onerror = null; // Clear the handle
+ };
+ reader.onerror = (error) => {
+ semaphore.release();
+ reject(error);
+ reader.onload = null; // Clear the handler
+ reader.onerror = null; // Clear the handle
+ };
});
-
-export const base64ToBlobUrl = (base64, mimeType = "image/png") => {
- const binary = atob(base64);
- const array = [];
- for (let i = 0; i < binary.length; i++) {
- array.push(binary.charCodeAt(i));
- }
- const blob = new Blob([new Uint8Array(array)], { type: mimeType });
- return URL.createObjectURL(blob);
- };
\ No newline at end of file
+export const base64ToBlobUrl = (base64, mimeType = 'image/png') => {
+ const binary = atob(base64);
+ const array = [];
+ for (let i = 0; i < binary.length; i++) {
+ array.push(binary.charCodeAt(i));
+ }
+ const blob = new Blob([new Uint8Array(array)], { type: mimeType });
+ return URL.createObjectURL(blob);
+};
+
+export const handleImportClick = async (fileTypes) => {
+ const fileInput = document.createElement('input');
+ fileInput.type = 'file';
+ fileInput.accept = fileTypes;
+
+ // Create a promise to handle file selection and reading synchronously
+ return await new Promise((resolve, reject) => {
+ fileInput.onchange = () => {
+ const file = fileInput.files[0];
+ if (!file) {
+ reject(new Error('No file selected'));
+ return;
+ }
+
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ resolve(e.target.result); // Resolve with the file content
+ };
+ reader.onerror = () => {
+ reject(new Error('Error reading file'));
+ };
+
+ reader.readAsText(file); // Read the file as text (Base64 string)
+ };
+
+ // Trigger the file input dialog
+ fileInput.click();
+ });
+};