feat: dark mode

This commit is contained in:
Ibrahima G. Coulibaly
2025-03-31 01:27:44 +00:00
parent e53642db31
commit d676383d22
26 changed files with 297 additions and 551 deletions

213
.idea/workspace.xml generated
View File

@@ -4,11 +4,33 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="refactor: time between dates"> <list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: compress video icon">
<change afterPath="$PROJECT_DIR$/src/components/BackButton.tsx" afterDir="false" /> <change afterPath="$PROJECT_DIR$/@types/theme.d.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/public/assets/background-dark.png" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/App.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/App.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/Hero.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Hero.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/Navbar/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Navbar/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/ToolHeader.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/ToolHeader.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/ToolLayout.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/ToolLayout.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/allTools/ToolCard.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/allTools/ToolCard.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/examples/ExampleCard.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/examples/ExampleCard.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/input/BaseFileInput.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/input/BaseFileInput.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/input/ToolFileInput.tsx" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/input/ToolTextInput.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/input/ToolTextInput.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/options/ColorSelector.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/ColorSelector.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/options/TextFieldWithDesc.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/TextFieldWithDesc.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/options/ToolOptions.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/ToolOptions.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/result/ToolFileResult.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/result/ToolFileResult.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/result/ToolTextResult.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/result/ToolTextResult.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/config/muiConfig.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/config/muiConfig.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/home/Categories.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/home/Categories.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/home/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/home/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools-by-category/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools-by-category/index.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools-by-category/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools-by-category/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/rotate/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/rotate/index.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/split-pdf/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/split-pdf/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/compress/service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/compress/service.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/gif/change-speed/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/gif/change-speed/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tsconfig.json" beforeDir="false" afterPath="$PROJECT_DIR$/tsconfig.json" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -25,7 +47,7 @@
<option name="PUSH_AUTO_UPDATE" value="true" /> <option name="PUSH_AUTO_UPDATE" value="true" />
<option name="RECENT_BRANCH_BY_REPOSITORY"> <option name="RECENT_BRANCH_BY_REPOSITORY">
<map> <map>
<entry key="$PROJECT_DIR$" value="fork/lfsjesus/feature/rotate-video" /> <entry key="$PROJECT_DIR$" value="main" />
</map> </map>
</option> </option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
@@ -108,6 +130,20 @@
"number": 73 "number": 73
}, },
"lastSeen": 1743265865001 "lastSeen": 1743265865001
},
{
"id": {
"id": "PR_kwDOMJIfts6Qp5nI",
"number": 72
},
"lastSeen": 1743338472110
},
{
"id": {
"id": "PR_kwDOMJIfts6QsjlS",
"number": 76
},
"lastSeen": 1743352150953
} }
] ]
}]]></component> }]]></component>
@@ -139,56 +175,56 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent">{ <component name="PropertiesComponent"><![CDATA[{
&quot;keyToString&quot;: { "keyToString": {
&quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;, "ASKED_ADD_EXTERNAL_FILES": "true",
&quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;, "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
&quot;Docker.Dockerfile build.executor&quot;: &quot;Run&quot;, "Docker.Dockerfile build.executor": "Run",
&quot;Docker.Dockerfile.executor&quot;: &quot;Run&quot;, "Docker.Dockerfile.executor": "Run",
&quot;Playwright.Create transparent PNG.should make png color transparent.executor&quot;: &quot;Run&quot;, "Playwright.Create transparent PNG.should make png color transparent.executor": "Run",
&quot;Playwright.JoinText Component.executor&quot;: &quot;Run&quot;, "Playwright.JoinText Component.executor": "Run",
&quot;Playwright.JoinText Component.should merge text pieces with specified join character.executor&quot;: &quot;Run&quot;, "Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;, "RunOnceActivity.OpenProjectViewOnStart": "true",
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, "RunOnceActivity.ShowReadmeOnStart": "true",
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;, "RunOnceActivity.git.unshallow": "true",
&quot;Vitest.compute function (1).executor&quot;: &quot;Run&quot;, "Vitest.compute function (1).executor": "Run",
&quot;Vitest.compute function.executor&quot;: &quot;Run&quot;, "Vitest.compute function.executor": "Run",
&quot;Vitest.mergeText.executor&quot;: &quot;Run&quot;, "Vitest.mergeText.executor": "Run",
&quot;Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor&quot;: &quot;Run&quot;, "Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run",
&quot;Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor&quot;: &quot;Run&quot;, "Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run",
&quot;Vitest.parsePageRanges.executor&quot;: &quot;Run&quot;, "Vitest.parsePageRanges.executor": "Run",
&quot;Vitest.removeDuplicateLines function.executor&quot;: &quot;Run&quot;, "Vitest.removeDuplicateLines function.executor": "Run",
&quot;Vitest.removeDuplicateLines function.newlines option.executor&quot;: &quot;Run&quot;, "Vitest.removeDuplicateLines function.newlines option.executor": "Run",
&quot;Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor&quot;: &quot;Run&quot;, "Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run",
&quot;Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor&quot;: &quot;Run&quot;, "Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
&quot;Vitest.replaceText function.executor&quot;: &quot;Run&quot;, "Vitest.replaceText function.executor": "Run",
&quot;Vitest.timeBetweenDates.executor&quot;: &quot;Run&quot;, "Vitest.timeBetweenDates.executor": "Run",
&quot;git-widget-placeholder&quot;: &quot;main&quot;, "git-widget-placeholder": "dark-mode",
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;, "ignore.virus.scanning.warn.message": "true",
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;, "kotlin-language-version-configured": "true",
&quot;last_opened_file_path&quot;: &quot;C:/Users/Ibrahima/IdeaProjects/omni-tools/src/components/input&quot;, "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/@types",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "node.js.detected.package.eslint": "true",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "node.js.detected.package.tslint": "true",
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.eslint": "(autodetect)",
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.tslint": "(autodetect)",
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, "nodejs_package_manager_path": "npm",
&quot;npm.build.executor&quot;: &quot;Run&quot;, "npm.build.executor": "Run",
&quot;npm.dev.executor&quot;: &quot;Run&quot;, "npm.dev.executor": "Run",
&quot;npm.lint.executor&quot;: &quot;Run&quot;, "npm.lint.executor": "Run",
&quot;npm.prebuild.executor&quot;: &quot;Run&quot;, "npm.prebuild.executor": "Run",
&quot;npm.script:create:tool.executor&quot;: &quot;Run&quot;, "npm.script:create:tool.executor": "Run",
&quot;npm.test.executor&quot;: &quot;Run&quot;, "npm.test.executor": "Run",
&quot;npm.test:e2e.executor&quot;: &quot;Run&quot;, "npm.test:e2e.executor": "Run",
&quot;npm.test:e2e:run.executor&quot;: &quot;Run&quot;, "npm.test:e2e:run.executor": "Run",
&quot;prettierjs.PrettierConfiguration.Package&quot;: &quot;C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier&quot;, "prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier",
&quot;project.structure.last.edited&quot;: &quot;Problems&quot;, "project.structure.last.edited": "Problems",
&quot;project.structure.proportion&quot;: &quot;0.0&quot;, "project.structure.proportion": "0.0",
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;, "project.structure.side.proportion": "0.2",
&quot;settings.editor.selected.configurable&quot;: &quot;refactai_advanced_settings&quot;, "settings.editor.selected.configurable": "refactai_advanced_settings",
&quot;ts.external.directory.path&quot;: &quot;C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib&quot;, "ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; "vue.rearranger.settings.migration": "true"
} }
}</component> }]]></component>
<component name="ReactDesignerToolWindowState"> <component name="ReactDesignerToolWindowState">
<option name="myId2Visible"> <option name="myId2Visible">
<map> <map>
@@ -200,21 +236,21 @@
</component> </component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\@types" />
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" />
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\components\input" /> <recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\components\input" />
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\.husky" /> <recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\.husky" />
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" />
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\assets" /> <recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\assets" />
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\.github" />
</key> </key>
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\@types" />
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" /> <recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" />
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools" /> <recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools" />
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\categories" /> <recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\categories" />
<recent name="C:\Users\HP\IdeaProjects\omni-tools\src\components" /> <recent name="C:\Users\HP\IdeaProjects\omni-tools\src\components" />
<recent name="C:\Users\HP\IdeaProjects\omni-tools\src\components\options" />
</key> </key>
</component> </component>
<component name="RunManager" selected="Vitest.calculateTimeBetweenDates"> <component name="RunManager" selected="npm.dev">
<configuration name="calculateTimeBetweenDates" type="JavaScriptTestRunnerVitest" temporary="true" nameIsGenerated="true"> <configuration name="calculateTimeBetweenDates" type="JavaScriptTestRunnerVitest" temporary="true" nameIsGenerated="true">
<node-interpreter value="project" /> <node-interpreter value="project" />
<vitest-package value="$PROJECT_DIR$/node_modules/vitest" /> <vitest-package value="$PROJECT_DIR$/node_modules/vitest" />
@@ -293,11 +329,11 @@
</list> </list>
<recent_temporary> <recent_temporary>
<list> <list>
<item itemvalue="Vitest.calculateTimeBetweenDates" />
<item itemvalue="Vitest.timeBetweenDates" />
<item itemvalue="npm.dev" /> <item itemvalue="npm.dev" />
<item itemvalue="Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp" /> <item itemvalue="Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp" />
<item itemvalue="Vitest.parsePageRanges" /> <item itemvalue="Vitest.parsePageRanges" />
<item itemvalue="Vitest.timeBetweenDates" />
<item itemvalue="Vitest.calculateTimeBetweenDates" />
</list> </list>
</recent_temporary> </recent_temporary>
</component> </component>
@@ -384,30 +420,7 @@
<workItem from="1743018497879" duration="3895000" /> <workItem from="1743018497879" duration="3895000" />
<workItem from="1743047367993" duration="986000" /> <workItem from="1743047367993" duration="986000" />
<workItem from="1743103182313" duration="4264000" /> <workItem from="1743103182313" duration="4264000" />
</task> <workItem from="1743348610793" duration="21855000" />
<task id="LOCAL-00124" summary="docs: readme">
<option name="closed" value="true" />
<created>1740614012237</created>
<option name="number" value="00124" />
<option name="presentableId" value="LOCAL-00124" />
<option name="project" value="LOCAL" />
<updated>1740614012237</updated>
</task>
<task id="LOCAL-00125" summary="docs: readme">
<option name="closed" value="true" />
<created>1740614185980</created>
<option name="number" value="00125" />
<option name="presentableId" value="LOCAL-00125" />
<option name="project" value="LOCAL" />
<updated>1740614185980</updated>
</task>
<task id="LOCAL-00126" summary="chore: handle enter press on search">
<option name="closed" value="true" />
<created>1740614957672</created>
<option name="number" value="00126" />
<option name="presentableId" value="LOCAL-00126" />
<option name="project" value="LOCAL" />
<updated>1740614957672</updated>
</task> </task>
<task id="LOCAL-00127" summary="chore: show tooloptions in example"> <task id="LOCAL-00127" summary="chore: show tooloptions in example">
<option name="closed" value="true" /> <option name="closed" value="true" />
@@ -777,7 +790,31 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1743106796406</updated> <updated>1743106796406</updated>
</task> </task>
<option name="localTasksCounter" value="173" /> <task id="LOCAL-00173" summary="fix: typos">
<option name="closed" value="true" />
<created>1743349732644</created>
<option name="number" value="00173" />
<option name="presentableId" value="LOCAL-00173" />
<option name="project" value="LOCAL" />
<updated>1743349732644</updated>
</task>
<task id="LOCAL-00174" summary="feat: compress video">
<option name="closed" value="true" />
<created>1743355099396</created>
<option name="number" value="00174" />
<option name="presentableId" value="LOCAL-00174" />
<option name="project" value="LOCAL" />
<updated>1743355099396</updated>
</task>
<task id="LOCAL-00175" summary="chore: compress video icon">
<option name="closed" value="true" />
<created>1743355166425</created>
<option name="number" value="00175" />
<option name="presentableId" value="LOCAL-00175" />
<option name="project" value="LOCAL" />
<updated>1743355166426</updated>
</task>
<option name="localTasksCounter" value="176" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -824,9 +861,6 @@
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" /> <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<option name="CHECK_NEW_TODO" value="false" /> <option name="CHECK_NEW_TODO" value="false" />
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" /> <option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
<MESSAGE value="feat: minify json" />
<MESSAGE value="feat: stringify json" />
<MESSAGE value="feat: arithmetic sequence" />
<MESSAGE value="style: tools height" /> <MESSAGE value="style: tools height" />
<MESSAGE value="chore: update meta" /> <MESSAGE value="chore: update meta" />
<MESSAGE value="feat: change pgn opacity" /> <MESSAGE value="feat: change pgn opacity" />
@@ -849,7 +883,10 @@
<MESSAGE value="chore: show new tools in landing" /> <MESSAGE value="chore: show new tools in landing" />
<MESSAGE value="chore: zoom on hover" /> <MESSAGE value="chore: zoom on hover" />
<MESSAGE value="refactor: time between dates" /> <MESSAGE value="refactor: time between dates" />
<option name="LAST_COMMIT_MESSAGE" value="refactor: time between dates" /> <MESSAGE value="fix: typos" />
<MESSAGE value="feat: compress video" />
<MESSAGE value="chore: compress video icon" />
<option name="LAST_COMMIT_MESSAGE" value="chore: compress video icon" />
</component> </component>
<component name="XSLT-Support.FileAssociations.UIState"> <component name="XSLT-Support.FileAssociations.UIState">
<expand /> <expand />

9
@types/theme.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
import '@mui/material/styles';
declare module '@mui/material/styles' {
interface TypeBackground {
hover?: string;
lightSecondary?: string;
darkSecondary?: string;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

View File

@@ -1,14 +1,14 @@
import { BrowserRouter, useRoutes } from 'react-router-dom'; import { BrowserRouter, useRoutes } from 'react-router-dom';
import routesConfig from '../config/routesConfig'; import routesConfig from '../config/routesConfig';
import Navbar from './Navbar'; import Navbar from './Navbar';
import { Suspense } from 'react'; import { Suspense, useMemo, useState } from 'react';
import Loading from './Loading'; import Loading from './Loading';
import { ThemeProvider } from '@mui/material'; import { CssBaseline, ThemeProvider } from '@mui/material';
import theme from '../config/muiConfig';
import { CustomSnackBarProvider } from '../contexts/CustomSnackBarContext'; import { CustomSnackBarProvider } from '../contexts/CustomSnackBarContext';
import { SnackbarProvider } from 'notistack'; import { SnackbarProvider } from 'notistack';
import { tools } from '../tools'; import { tools } from '../tools';
import './index.css'; import './index.css';
import { darkTheme, lightTheme } from '../config/muiConfig';
const AppRoutes = () => { const AppRoutes = () => {
const updatedRoutesConfig = [...routesConfig]; const updatedRoutesConfig = [...routesConfig];
@@ -19,21 +19,29 @@ const AppRoutes = () => {
}; };
function App() { function App() {
const [darkMode, setDarkMode] = useState<boolean>(() => {
return localStorage.getItem('theme') === 'dark';
});
const theme = useMemo(() => (darkMode ? darkTheme : lightTheme), [darkMode]);
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<CssBaseline />
<SnackbarProvider <SnackbarProvider
maxSnack={5} maxSnack={5}
anchorOrigin={{ anchorOrigin={{
vertical: 'bottom', vertical: 'bottom',
horizontal: 'right' horizontal: 'right'
}} }}
classes={{
containerRoot: 'bottom-0 right-0 mb-52 md:mb-68 mr-8 lg:mr-80 z-99'
}}
> >
<CustomSnackBarProvider> <CustomSnackBarProvider>
<BrowserRouter> <BrowserRouter>
<Navbar /> <Navbar
onSwitchTheme={() => {
setDarkMode((prevState) => !prevState);
localStorage.setItem('theme', darkMode ? 'light' : 'dark');
}}
/>
<Suspense fallback={<Loading />}> <Suspense fallback={<Loading />}>
<AppRoutes /> <AppRoutes />
</Suspense> </Suspense>

View File

@@ -1,4 +1,4 @@
import { Autocomplete, Box, Stack, TextField } from '@mui/material'; import { Autocomplete, Box, Stack, TextField, useTheme } from '@mui/material';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import Grid from '@mui/material/Grid'; import Grid from '@mui/material/Grid';
@@ -25,6 +25,7 @@ const exampleTools: { label: string; url: string }[] = [
]; ];
export default function Hero() { export default function Hero() {
const [inputValue, setInputValue] = useState<string>(''); const [inputValue, setInputValue] = useState<string>('');
const theme = useTheme();
const [filteredTools, setFilteredTools] = useState<DefinedTool[]>( const [filteredTools, setFilteredTools] = useState<DefinedTool[]>(
_.shuffle(tools) _.shuffle(tools)
); );
@@ -78,7 +79,7 @@ export default function Hero() {
endAdornment: <SearchIcon />, endAdornment: <SearchIcon />,
sx: { sx: {
borderRadius: 4, borderRadius: 4,
backgroundColor: 'white' backgroundColor: 'background.paper'
} }
}} }}
onChange={(event) => handleInputChange(event, event.target.value)} onChange={(event) => handleInputChange(event, event.target.value)}
@@ -125,11 +126,13 @@ export default function Hero() {
borderWidth: 1, borderWidth: 1,
padding: 1, padding: 1,
borderRadius: 3, borderRadius: 3,
borderColor: 'grey', borderColor: theme.palette.mode === 'dark' ? '#363b41' : 'grey',
borderStyle: 'solid', borderStyle: 'solid',
backgroundColor: 'white', backgroundColor: 'background.paper',
cursor: 'pointer', cursor: 'pointer',
'&:hover': { backgroundColor: '#FAFAFD' } '&:hover': {
backgroundColor: 'background.hover'
}
}} }}
> >
<Typography>{tool.label}</Typography> <Typography>{tool.label}</Typography>

View File

@@ -17,8 +17,13 @@ import {
import useMediaQuery from '@mui/material/useMediaQuery'; import useMediaQuery from '@mui/material/useMediaQuery';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import DarkModeIcon from '@mui/icons-material/DarkMode';
const Navbar: React.FC = () => { interface NavbarProps {
onSwitchTheme: () => void;
}
const Navbar: React.FC<NavbarProps> = ({ onSwitchTheme }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md')); const isMobile = useMediaQuery(theme.breakpoints.down('md'));
@@ -30,7 +35,9 @@ const Navbar: React.FC = () => {
// { label: 'Features', path: '/features' } // { label: 'Features', path: '/features' }
// { label: 'About Us', path: '/about-us' } // { label: 'About Us', path: '/about-us' }
]; ];
const buttons: ReactNode[] = [ const buttons: ReactNode[] = [
<DarkModeIcon onClick={onSwitchTheme} style={{ cursor: 'pointer' }} />,
<Icon <Icon
onClick={() => window.open('https://discord.gg/SDbbn3hT4b', '_blank')} onClick={() => window.open('https://discord.gg/SDbbn3hT4b', '_blank')}
style={{ cursor: 'pointer' }} style={{ cursor: 'pointer' }}
@@ -81,9 +88,10 @@ const Navbar: React.FC = () => {
return ( return (
<AppBar <AppBar
position="static" position="static"
style={{ sx={{
backgroundColor: '#F5F5FA', background: 'transparent',
color: 'black' boxShadow: 'none',
color: 'text.primary'
}} }}
> >
<Toolbar <Toolbar

View File

@@ -31,7 +31,7 @@ function ToolLinks() {
<Grid container spacing={2} mt={1}> <Grid container spacing={2} mt={1}>
<Grid item md={12} lg={6}> <Grid item md={12} lg={6}>
<StyledButton <StyledButton
sx={{ backgroundColor: 'white' }} sx={{ backgroundColor: 'background.paper' }}
fullWidth fullWidth
variant="outlined" variant="outlined"
onClick={() => scrollToElement('tool')} onClick={() => scrollToElement('tool')}
@@ -43,6 +43,7 @@ function ToolLinks() {
<StyledButton <StyledButton
fullWidth fullWidth
variant="outlined" variant="outlined"
sx={{ backgroundColor: 'background.paper' }}
onClick={() => scrollToElement('examples')} onClick={() => scrollToElement('examples')}
> >
See Examples See Examples

View File

@@ -38,7 +38,7 @@ export default function ToolLayout({
display={'flex'} display={'flex'}
flexDirection={'column'} flexDirection={'column'}
alignItems={'center'} alignItems={'center'}
sx={{ backgroundColor: '#F5F5FA' }} sx={{ backgroundColor: 'background.default' }}
> >
<Helmet> <Helmet>
<title>{`${title} - Omni Tools`}</title> <title>{`${title} - Omni Tools`}</title>

View File

@@ -1,4 +1,12 @@
import { Box, Card, CardContent, Link, Stack, Typography } from '@mui/material'; import {
Box,
Card,
CardContent,
Link,
Stack,
Typography,
useTheme
} from '@mui/material';
import { ToolCardProps } from './AllTools'; import { ToolCardProps } from './AllTools';
import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@@ -10,6 +18,7 @@ export default function ToolCard({
link, link,
icon icon
}: ToolCardProps) { }: ToolCardProps) {
const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<Card <Card
@@ -17,10 +26,13 @@ export default function ToolCard({
raised raised
sx={{ sx={{
borderRadius: 2, borderRadius: 2,
bgcolor: '#5581b5', bgcolor: 'background.darkSecondary',
borderColor: '#5581b5', borderColor: 'background.darkSecondary',
color: '#fff', color: '#fff',
boxShadow: '6px 6px 12px #b8b9be, -6px -6px 12px #fff', boxShadow:
theme.palette.mode === 'dark'
? null
: '6px 6px 12px #b8b9be, -6px -6px 12px #fff',
cursor: 'pointer', cursor: 'pointer',
height: '100%', height: '100%',
'&:hover': { '&:hover': {

View File

@@ -38,14 +38,16 @@ export default function ExampleCard<T>({
changeInputResult(sampleText, sampleOptions); changeInputResult(sampleText, sampleOptions);
}} }}
sx={{ sx={{
bgcolor: theme.palette.background.default, bgcolor: 'background.lightSecondary',
height: '100%', height: '100%',
overflow: 'hidden', overflow: 'hidden',
borderRadius: 2, borderRadius: 2,
transition: 'background-color 0.3s ease', transition: 'background-color 0.3s ease',
cursor: 'pointer', cursor: 'pointer',
'&:hover': { '&:hover': {
boxShadow: '12px 9px 11px 2px #b8b9be, -6px -6px 12px #fff' boxShadow: `12px 9px 11px 2px ${
theme.palette.mode === 'dark' ? theme.palette.grey[900] : '#b8b9be'
}, -6px -6px 12px ${theme.palette.mode === 'dark' ? 'black' : '#fff'}`
} }
}} }}
> >

View File

@@ -94,7 +94,7 @@ export default function BaseFileInput({
border: preview ? 0 : 1, border: preview ? 0 : 1,
borderRadius: 2, borderRadius: 2,
boxShadow: '5', boxShadow: '5',
bgcolor: 'white', bgcolor: 'background.paper',
position: 'relative' position: 'relative'
}} }}
> >
@@ -106,7 +106,8 @@ export default function BaseFileInput({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
backgroundImage: `url(${greyPattern})`, backgroundImage:
theme.palette.mode === 'dark' ? null : `url(${greyPattern})`,
position: 'relative', position: 'relative',
overflow: 'hidden' overflow: 'hidden'
}} }}
@@ -126,7 +127,13 @@ export default function BaseFileInput({
cursor: 'pointer' cursor: 'pointer'
}} }}
> >
<Typography color={theme.palette.grey['600']}> <Typography
color={
theme.palette.mode === 'dark'
? theme.palette.grey['300']
: theme.palette.grey['600']
}
>
Click here to select a {type} from your device, press Ctrl+V to Click here to select a {type} from your device, press Ctrl+V to
use a {type} from your clipboard, drag and drop a file from use a {type} from your clipboard, drag and drop a file from
desktop desktop

View File

@@ -1,377 +0,0 @@
import { Box, useTheme } from '@mui/material';
import Typography from '@mui/material/Typography';
import React, { useContext, useEffect, useRef, useState } from 'react';
import ReactCrop, { Crop, PixelCrop } from 'react-image-crop';
import 'react-image-crop/dist/ReactCrop.css';
import InputHeader from '../InputHeader';
import InputFooter from './InputFooter';
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
import greyPattern from '@assets/grey-pattern.png';
import { globalInputHeight } from '../../config/uiConfig';
import Slider from 'rc-slider';
import 'rc-slider/assets/index.css';
interface ToolFileInputProps {
value: File | null;
onChange: (file: File) => void;
accept: string[];
title?: string;
showCropOverlay?: boolean;
cropShape?: 'rectangular' | 'circular';
cropPosition?: { x: number; y: number };
cropSize?: { width: number; height: number };
onCropChange?: (
position: { x: number; y: number },
size: { width: number; height: number }
) => void;
type?: 'image' | 'video' | 'audio';
// Video specific props
showTrimControls?: boolean;
onTrimChange?: (trimStart: number, trimEnd: number) => void;
trimStart?: number;
trimEnd?: number;
}
export default function ToolFileInput({
value,
onChange,
accept,
title = 'File',
showCropOverlay = false,
cropShape = 'rectangular',
cropPosition = { x: 0, y: 0 },
cropSize = { width: 100, height: 100 },
onCropChange,
type = 'image',
showTrimControls = false,
onTrimChange,
trimStart = 0,
trimEnd = 100
}: ToolFileInputProps) {
const [preview, setPreview] = useState<string | null>(null);
const theme = useTheme();
const { showSnackBar } = useContext(CustomSnackBarContext);
const fileInputRef = useRef<HTMLInputElement>(null);
const imageRef = useRef<HTMLImageElement>(null);
const videoRef = useRef<HTMLVideoElement>(null);
const [imgWidth, setImgWidth] = useState(0);
const [imgHeight, setImgHeight] = useState(0);
const [videoDuration, setVideoDuration] = useState(0);
// Convert position and size to crop format used by ReactCrop
const [crop, setCrop] = useState<Crop>({
unit: 'px',
x: 0,
y: 0,
width: 0,
height: 0
});
const RATIO = imageRef.current ? imgWidth / imageRef.current.width : 1;
useEffect(() => {
if (imgWidth && imgHeight) {
setCrop({
unit: 'px',
x: cropPosition.x / RATIO,
y: cropPosition.y / RATIO,
width: cropSize.width / RATIO,
height: cropSize.height / RATIO
});
}
}, [cropPosition, cropSize, imgWidth, imgHeight]);
const handleCopy = () => {
if (value) {
const blob = new Blob([value], { type: value.type });
const clipboardItem = new ClipboardItem({ [value.type]: blob });
navigator.clipboard
.write([clipboardItem])
.then(() => showSnackBar('File copied', 'success'))
.catch((err) => {
showSnackBar('Failed to copy: ' + err, 'error');
});
}
};
useEffect(() => {
if (value) {
const objectUrl = URL.createObjectURL(value);
setPreview(objectUrl);
// Clean up memory when the component is unmounted or the file changes
return () => URL.revokeObjectURL(objectUrl);
} else {
setPreview(null);
setImgWidth(0);
setImgHeight(0);
}
}, [value]);
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) onChange(file);
};
const handleImportClick = () => {
fileInputRef.current?.click();
};
// Handle image load to set dimensions
const onImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {
const { naturalWidth: width, naturalHeight: height } = e.currentTarget;
setImgWidth(width);
setImgHeight(height);
// Initialize crop with a centered default crop if needed
if (!crop.width && !crop.height && onCropChange) {
const initialCrop: Crop = {
unit: 'px',
x: Math.floor(width / 4),
y: Math.floor(height / 4),
width: Math.floor(width / 2),
height: Math.floor(height / 2)
};
setCrop(initialCrop);
// Notify parent component of initial crop
onCropChange(
{ x: initialCrop.x, y: initialCrop.y },
{ width: initialCrop.width, height: initialCrop.height }
);
}
};
// Handle video load to set duration
const onVideoLoad = (e: React.SyntheticEvent<HTMLVideoElement>) => {
const duration = e.currentTarget.duration;
setVideoDuration(duration);
// Initialize trim with full duration if needed
if (onTrimChange && trimStart === 0 && trimEnd === 100) {
onTrimChange(0, duration);
}
};
const handleCropChange = (newCrop: Crop) => {
setCrop(newCrop);
};
const handleCropComplete = (crop: PixelCrop) => {
if (onCropChange) {
onCropChange(
{ x: Math.round(crop.x * RATIO), y: Math.round(crop.y * RATIO) },
{
width: Math.round(crop.width * RATIO),
height: Math.round(crop.height * RATIO)
}
);
}
};
const handleTrimChange = (start: number, end: number) => {
if (onTrimChange) {
onTrimChange(start, end);
}
};
useEffect(() => {
const handlePaste = (event: ClipboardEvent) => {
const clipboardItems = event.clipboardData?.items ?? [];
const item = clipboardItems[0];
if (
item &&
(item.type.includes('image') || item.type.includes('video'))
) {
const file = item.getAsFile();
if (file) onChange(file);
}
};
window.addEventListener('paste', handlePaste);
return () => {
window.removeEventListener('paste', handlePaste);
};
}, [onChange]);
// Format seconds to MM:SS format
const formatTime = (seconds: number) => {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds
.toString()
.padStart(2, '0')}`;
};
return (
<Box>
<InputHeader title={title} />
<Box
sx={{
width: '100%',
height: globalInputHeight,
border: preview ? 0 : 1,
borderRadius: 2,
boxShadow: '5',
bgcolor: 'white',
position: 'relative'
}}
>
{preview ? (
<Box
width="100%"
height="100%"
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundImage: `url(${greyPattern})`,
position: 'relative',
overflow: 'hidden'
}}
>
{type === 'image' &&
(showCropOverlay ? (
<ReactCrop
crop={crop}
onChange={handleCropChange}
onComplete={handleCropComplete}
circularCrop={cropShape === 'circular'}
style={{ maxWidth: '100%', maxHeight: globalInputHeight }}
>
<img
ref={imageRef}
src={preview}
alt="Preview"
style={{ maxWidth: '100%', maxHeight: globalInputHeight }}
onLoad={onImageLoad}
/>
</ReactCrop>
) : (
<img
ref={imageRef}
src={preview}
alt="Preview"
style={{ maxWidth: '100%', maxHeight: globalInputHeight }}
onLoad={onImageLoad}
/>
))}
{type === 'video' && (
<Box
sx={{
position: 'relative',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
}}
>
<video
ref={videoRef}
src={preview}
style={{
maxWidth: '100%',
maxHeight: showTrimControls ? 'calc(100% - 50px)' : '100%'
}}
onLoadedMetadata={onVideoLoad}
controls={!showTrimControls}
/>
{showTrimControls && videoDuration > 0 && (
<Box
sx={{
width: '100%',
padding: '10px 20px',
position: 'absolute',
bottom: 0,
left: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
color: 'white',
display: 'flex',
flexDirection: 'column',
gap: 1
}}
>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}
>
<Typography variant="caption">
Start: {formatTime(trimStart || 0)}
</Typography>
<Typography variant="caption">
End: {formatTime(trimEnd || videoDuration)}
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<div
className="range-slider-container"
style={{ margin: '20px 0', width: '100%' }}
>
<Slider
range
min={0}
max={videoDuration}
step={0.1}
value={[trimStart || 0, trimEnd || videoDuration]}
onChange={(values) => {
if (Array.isArray(values)) {
handleTrimChange(values[0], values[1]);
}
}}
allowCross={false}
pushable={0.1} // Minimum distance between handles
/>
</div>
</Box>
</Box>
)}
</Box>
)}
{type === 'audio' && (
<audio
src={preview}
controls
style={{ width: '100%', maxWidth: '500px' }}
/>
)}
</Box>
) : (
<Box
onClick={handleImportClick}
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: 5,
height: '100%',
cursor: 'pointer'
}}
>
<Typography color={theme.palette.grey['600']}>
Click here to select a {type} from your device, press Ctrl+V to
use a {type} from your clipboard, drag and drop a file from
desktop
</Typography>
</Box>
)}
</Box>
<InputFooter handleCopy={handleCopy} handleImport={handleImportClick} />
<input
ref={fileInputRef}
style={{ display: 'none' }}
type="file"
accept={accept.join(',')}
onChange={handleFileChange}
/>
</Box>
);
}

View File

@@ -52,7 +52,7 @@ export default function ToolTextInput({
rows={10} rows={10}
sx={{ sx={{
'&.MuiTextField-root': { '&.MuiTextField-root': {
backgroundColor: 'white' backgroundColor: 'background.paper'
} }
}} }}
inputProps={{ inputProps={{

View File

@@ -30,7 +30,7 @@ const ColorSelector: React.FC<ColorSelectorProps & TextFieldProps> = ({
<Box mb={1}> <Box mb={1}>
<Stack direction={'row'}> <Stack direction={'row'}>
<TextField <TextField
sx={{ backgroundColor: 'white' }} sx={{ backgroundColor: 'background.paper' }}
value={color} value={color}
onChange={handleColorChange} onChange={handleColorChange}
{...props} {...props}

View File

@@ -19,7 +19,7 @@ const TextFieldWithDesc = ({
<Box> <Box>
<TextField <TextField
placeholder={placeholder} placeholder={placeholder}
sx={{ backgroundColor: 'white' }} sx={{ backgroundColor: 'background.paper' }}
value={value} value={value}
onChange={(event) => onOwnChange(event.target.value)} onChange={(event) => onOwnChange(event.target.value)}
{...props} {...props}

View File

@@ -36,7 +36,7 @@ export default function ToolOptions<T extends FormikValues>({
mb: 2, mb: 2,
borderRadius: 2, borderRadius: 2,
padding: 2, padding: 2,
backgroundColor: theme.palette.background.default, backgroundColor: 'background.lightSecondary',
boxShadow: '2' boxShadow: '2'
}} }}
mt={2} mt={2}

View File

@@ -1,4 +1,4 @@
import { Box, CircularProgress, Typography } from '@mui/material'; import { Box, CircularProgress, Typography, useTheme } from '@mui/material';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import InputHeader from '../InputHeader'; import InputHeader from '../InputHeader';
import greyPattern from '@assets/grey-pattern.png'; import greyPattern from '@assets/grey-pattern.png';
@@ -21,6 +21,7 @@ export default function ToolFileResult({
}) { }) {
const [preview, setPreview] = React.useState<string | null>(null); const [preview, setPreview] = React.useState<string | null>(null);
const { showSnackBar } = useContext(CustomSnackBarContext); const { showSnackBar } = useContext(CustomSnackBarContext);
const theme = useTheme();
React.useEffect(() => { React.useEffect(() => {
if (value) { if (value) {
@@ -87,7 +88,7 @@ export default function ToolFileResult({
border: preview ? 0 : 1, border: preview ? 0 : 1,
borderRadius: 2, borderRadius: 2,
boxShadow: '5', boxShadow: '5',
bgcolor: 'white' bgcolor: 'background.paper'
}} }}
> >
{loading ? ( {loading ? (
@@ -114,7 +115,8 @@ export default function ToolFileResult({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
backgroundImage: `url(${greyPattern})` backgroundImage:
theme.palette.mode === 'dark' ? null : `url(${greyPattern})`
}} }}
> >
{fileType === 'image' && ( {fileType === 'image' && (

View File

@@ -52,7 +52,7 @@ export default function ToolTextResult({
multiline multiline
sx={{ sx={{
'&.MuiTextField-root': { '&.MuiTextField-root': {
backgroundColor: 'white' backgroundColor: 'background.paper'
} }
}} }}
rows={10} rows={10}

View File

@@ -1,13 +1,50 @@
import { createTheme } from '@mui/material'; import { createTheme, ThemeOptions } from '@mui/material';
const theme = createTheme({ const sharedThemeOptions: ThemeOptions = {
typography: { typography: {
button: { button: {
textTransform: 'none' textTransform: 'none'
} }
}, },
palette: { background: { default: '#ebf5ff' } },
zIndex: { snackbar: 100000 } zIndex: { snackbar: 100000 }
};
export const lightTheme = createTheme({
...sharedThemeOptions,
palette: {
background: {
default: '#F5F5FA',
hover: '#FAFAFD',
lightSecondary: '#EBF5FF',
darkSecondary: '#5581b5'
}
},
components: {
MuiButton: {
styleOverrides: {
contained: { color: '#ffffff', backgroundColor: '#1976d2' }
}
}
}
}); });
export default theme; export const darkTheme = createTheme({
...sharedThemeOptions,
palette: {
mode: 'dark',
background: {
default: '#1C1F20',
paper: '#181a1b',
hover: '#1a1c1d',
lightSecondary: '#1E2021',
darkSecondary: '#3C5F8A'
},
text: { primary: '#ffffff' }
},
components: {
MuiButton: {
styleOverrides: {
contained: { color: '#ffffff', backgroundColor: '#145ea8' }
}
}
}
});

View File

@@ -1,6 +1,6 @@
import { getToolsByCategory } from '@tools/index'; import { getToolsByCategory } from '@tools/index';
import Grid from '@mui/material/Grid'; import Grid from '@mui/material/Grid';
import { Box, Card, CardContent, Stack } from '@mui/material'; import { Box, Card, CardContent, Stack, useTheme } from '@mui/material';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
@@ -19,6 +19,7 @@ const SingleCategory = function ({
index: number; index: number;
}) { }) {
const navigate = useNavigate(); const navigate = useNavigate();
const theme = useTheme();
const [hovered, setHovered] = useState<boolean>(false); const [hovered, setHovered] = useState<boolean>(false);
const toggleHover = () => setHovered((prevState) => !prevState); const toggleHover = () => setHovered((prevState) => !prevState);
return ( return (
@@ -32,7 +33,7 @@ const SingleCategory = function ({
<Card <Card
sx={{ sx={{
height: '100%', height: '100%',
backgroundColor: hovered ? '#FAFAFD' : 'white' backgroundColor: hovered ? 'background.hover' : 'background.paper'
}} }}
> >
<CardContent sx={{ height: '100%' }}> <CardContent sx={{ height: '100%' }}>
@@ -52,7 +53,11 @@ const SingleCategory = function ({
color={categoriesColors[index % categoriesColors.length]} color={categoriesColors[index % categoriesColors.length]}
/> />
<Link <Link
style={{ fontSize: 20, fontWeight: 700, color: 'black' }} style={{
fontSize: 20,
fontWeight: 700,
color: theme.palette.mode === 'dark' ? 'white' : 'black'
}}
to={'/categories/' + category.type} to={'/categories/' + category.type}
> >
{category.title} {category.title}
@@ -70,7 +75,7 @@ const SingleCategory = function ({
</Grid> </Grid>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Button <Button
sx={{ backgroundColor: 'white' }} sx={{ backgroundColor: 'background.default' }}
fullWidth fullWidth
onClick={() => navigate(category.example.path)} onClick={() => navigate(category.example.path)}
variant={'outlined'} variant={'outlined'}

View File

@@ -1,16 +1,23 @@
import { Box } from '@mui/material'; import { Box, useTheme } from '@mui/material';
import Hero from 'components/Hero'; import Hero from 'components/Hero';
import Categories from './Categories'; import Categories from './Categories';
export default function Home() { export default function Home() {
const theme = useTheme();
return ( return (
<Box <Box
padding={{ padding={{
xs: 1, xs: 1,
md: 3, md: 3,
lg: 5, lg: 5
background: `url(/assets/background.svg)`, }}
backgroundColor: '#F5F5FA' sx={{
background: `url(/assets/${
theme.palette.mode === 'dark'
? 'background-dark.png'
: 'background.svg'
})`,
backgroundColor: 'background.default'
}} }}
display={'flex'} display={'flex'}
flexDirection={'column'} flexDirection={'column'}

View File

@@ -26,7 +26,7 @@ export default function Home() {
}, []); }, []);
return ( return (
<Box sx={{ backgroundColor: '#F5F5FA' }}> <Box sx={{ backgroundColor: 'background.default' }}>
<Box <Box
padding={{ xs: 1, md: 3, lg: 5 }} padding={{ xs: 1, md: 3, lg: 5 }}
display={'flex'} display={'flex'}
@@ -55,12 +55,14 @@ export default function Home() {
<Grid item xs={12} md={6} lg={4} key={tool.path}> <Grid item xs={12} md={6} lg={4} key={tool.path}>
<Stack <Stack
sx={{ sx={{
backgroundColor: 'white', backgroundColor: 'background.paper',
boxShadow: '5px 4px 2px #E9E9ED', boxShadow: `5px 4px 2px ${
theme.palette.mode === 'dark' ? 'black' : '#E9E9ED'
}`,
cursor: 'pointer', cursor: 'pointer',
height: '100%', height: '100%',
'&:hover': { '&:hover': {
backgroundColor: theme.palette.background.default // Change this to your desired hover color backgroundColor: theme.palette.background.hover
} }
}} }}
onClick={() => navigate('/' + tool.path)} onClick={() => navigate('/' + tool.path)}
@@ -77,7 +79,12 @@ export default function Home() {
color={categoriesColors[index % categoriesColors.length]} color={categoriesColors[index % categoriesColors.length]}
/> />
<Box> <Box>
<Link style={{ fontSize: 20 }} to={'/' + tool.path}> <Link
style={{
fontSize: 20
}}
to={'/' + tool.path}
>
{tool.name} {tool.name}
</Link> </Link>
<Typography sx={{ mt: 2 }}> <Typography sx={{ mt: 2 }}>

View File

@@ -1,6 +1,5 @@
import { Box, Typography } from '@mui/material'; import { Box, Typography } from '@mui/material';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useState } from 'react';
import ToolFileInput from '@components/input/ToolFileInput';
import ToolFileResult from '@components/result/ToolFileResult'; import ToolFileResult from '@components/result/ToolFileResult';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import ToolContent from '@components/ToolContent'; import ToolContent from '@components/ToolContent';
@@ -8,7 +7,6 @@ import { ToolComponentProps } from '@tools/defineTool';
import { parsePageRanges, splitPdf } from './service'; import { parsePageRanges, splitPdf } from './service';
import { CardExampleType } from '@components/examples/ToolExamples'; import { CardExampleType } from '@components/examples/ToolExamples';
import { PDFDocument } from 'pdf-lib'; import { PDFDocument } from 'pdf-lib';
import { FormikProps } from 'formik';
import ToolPdfInput from '@components/input/ToolPdfInput'; import ToolPdfInput from '@components/input/ToolPdfInput';
type InitialValuesType = { type InitialValuesType = {

View File

@@ -15,7 +15,6 @@ export async function compressVideo(
input: File, input: File,
options: CompressVideoOptions options: CompressVideoOptions
): Promise<File> { ): Promise<File> {
console.log('Compressing video...', options);
if (!ffmpeg.loaded) { if (!ffmpeg.loaded) {
await ffmpeg.load({ await ffmpeg.load({
wasmURL: wasmURL:

View File

@@ -1,7 +1,6 @@
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import React, { useState } from 'react'; import React, { useState } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import ToolFileInput from '@components/input/ToolFileInput';
import ToolFileResult from '@components/result/ToolFileResult'; import ToolFileResult from '@components/result/ToolFileResult';
import TextFieldWithDesc from 'components/options/TextFieldWithDesc'; import TextFieldWithDesc from 'components/options/TextFieldWithDesc';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
@@ -9,6 +8,8 @@ import { FrameOptions, GifReader, GifWriter } from 'omggif';
import { gifBinaryToFile } from '@utils/gif'; import { gifBinaryToFile } from '@utils/gif';
import ToolContent from '@components/ToolContent'; import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool'; import { ToolComponentProps } from '@tools/defineTool';
import ToolVideoInput from '@components/input/ToolVideoInput';
import ToolImageInput from '@components/input/ToolImageInput';
const initialValues = { const initialValues = {
newSpeed: 200 newSpeed: 200
@@ -108,7 +109,7 @@ export default function ChangeSpeed({ title }: ToolComponentProps) {
title={title} title={title}
input={input} input={input}
inputComponent={ inputComponent={
<ToolFileInput <ToolImageInput
value={input} value={input}
onChange={setInput} onChange={setInput}
accept={['image/gif']} accept={['image/gif']}

View File

@@ -2,11 +2,7 @@
"compilerOptions": { "compilerOptions": {
"baseUrl": "./src", "baseUrl": "./src",
"target": "esnext", "target": "esnext",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
@@ -19,30 +15,14 @@
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"types": [ "types": ["vite/client", "vitest/globals", "@testing-library/jest-dom"],
"vite/client",
"vitest/globals",
"@testing-library/jest-dom"
],
"paths": { "paths": {
"@tools/*": [ "@tools/*": ["./tools/*"],
"./tools/*" "@assets/*": ["./assets/*"],
], "@components/*": ["./components/*"],
"@assets/*": [ "@utils/*": ["./utils/*"]
"./assets/*"
],
"@components/*": [
"./components/*"
],
"@utils/*": [
"./utils/*"
]
} }
}, },
"include": [ "include": ["src", "./@types"],
"src" "exclude": ["node_modules"]
],
"exclude": [
"node_modules"
]
} }