mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-19 22:19:36 +02:00
feat: dark mode
This commit is contained in:
213
.idea/workspace.xml
generated
213
.idea/workspace.xml
generated
@@ -4,11 +4,33 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="refactor: time between dates">
|
||||
<change afterPath="$PROJECT_DIR$/src/components/BackButton.tsx" afterDir="false" />
|
||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: compress video icon">
|
||||
<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$/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/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>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -25,7 +47,7 @@
|
||||
<option name="PUSH_AUTO_UPDATE" value="true" />
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="fork/lfsjesus/feature/rotate-video" />
|
||||
<entry key="$PROJECT_DIR$" value="main" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
@@ -108,6 +130,20 @@
|
||||
"number": 73
|
||||
},
|
||||
"lastSeen": 1743265865001
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6Qp5nI",
|
||||
"number": 72
|
||||
},
|
||||
"lastSeen": 1743338472110
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"id": "PR_kwDOMJIfts6QsjlS",
|
||||
"number": 76
|
||||
},
|
||||
"lastSeen": 1743352150953
|
||||
}
|
||||
]
|
||||
}]]></component>
|
||||
@@ -139,56 +175,56 @@
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"ASKED_ADD_EXTERNAL_FILES": "true",
|
||||
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||
"Docker.Dockerfile build.executor": "Run",
|
||||
"Docker.Dockerfile.executor": "Run",
|
||||
"Playwright.Create transparent PNG.should make png color transparent.executor": "Run",
|
||||
"Playwright.JoinText Component.executor": "Run",
|
||||
"Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"Vitest.compute function (1).executor": "Run",
|
||||
"Vitest.compute function.executor": "Run",
|
||||
"Vitest.mergeText.executor": "Run",
|
||||
"Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run",
|
||||
"Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run",
|
||||
"Vitest.parsePageRanges.executor": "Run",
|
||||
"Vitest.removeDuplicateLines function.executor": "Run",
|
||||
"Vitest.removeDuplicateLines function.newlines option.executor": "Run",
|
||||
"Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run",
|
||||
"Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
|
||||
"Vitest.replaceText function.executor": "Run",
|
||||
"Vitest.timeBetweenDates.executor": "Run",
|
||||
"git-widget-placeholder": "main",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src/components/input",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"npm.build.executor": "Run",
|
||||
"npm.dev.executor": "Run",
|
||||
"npm.lint.executor": "Run",
|
||||
"npm.prebuild.executor": "Run",
|
||||
"npm.script:create:tool.executor": "Run",
|
||||
"npm.test.executor": "Run",
|
||||
"npm.test:e2e.executor": "Run",
|
||||
"npm.test:e2e:run.executor": "Run",
|
||||
"prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier",
|
||||
"project.structure.last.edited": "Problems",
|
||||
"project.structure.proportion": "0.0",
|
||||
"project.structure.side.proportion": "0.2",
|
||||
"settings.editor.selected.configurable": "refactai_advanced_settings",
|
||||
"ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ASKED_ADD_EXTERNAL_FILES": "true",
|
||||
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||
"Docker.Dockerfile build.executor": "Run",
|
||||
"Docker.Dockerfile.executor": "Run",
|
||||
"Playwright.Create transparent PNG.should make png color transparent.executor": "Run",
|
||||
"Playwright.JoinText Component.executor": "Run",
|
||||
"Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run",
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"Vitest.compute function (1).executor": "Run",
|
||||
"Vitest.compute function.executor": "Run",
|
||||
"Vitest.mergeText.executor": "Run",
|
||||
"Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run",
|
||||
"Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run",
|
||||
"Vitest.parsePageRanges.executor": "Run",
|
||||
"Vitest.removeDuplicateLines function.executor": "Run",
|
||||
"Vitest.removeDuplicateLines function.newlines option.executor": "Run",
|
||||
"Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run",
|
||||
"Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
|
||||
"Vitest.replaceText function.executor": "Run",
|
||||
"Vitest.timeBetweenDates.executor": "Run",
|
||||
"git-widget-placeholder": "dark-mode",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/@types",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"npm.build.executor": "Run",
|
||||
"npm.dev.executor": "Run",
|
||||
"npm.lint.executor": "Run",
|
||||
"npm.prebuild.executor": "Run",
|
||||
"npm.script:create:tool.executor": "Run",
|
||||
"npm.test.executor": "Run",
|
||||
"npm.test:e2e.executor": "Run",
|
||||
"npm.test:e2e:run.executor": "Run",
|
||||
"prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier",
|
||||
"project.structure.last.edited": "Problems",
|
||||
"project.structure.proportion": "0.0",
|
||||
"project.structure.side.proportion": "0.2",
|
||||
"settings.editor.selected.configurable": "refactai_advanced_settings",
|
||||
"ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}</component>
|
||||
}]]></component>
|
||||
<component name="ReactDesignerToolWindowState">
|
||||
<option name="myId2Visible">
|
||||
<map>
|
||||
@@ -200,21 +236,21 @@
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<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\.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\.github" />
|
||||
</key>
|
||||
<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\src\pages\tools" />
|
||||
<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\options" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager" selected="Vitest.calculateTimeBetweenDates">
|
||||
<component name="RunManager" selected="npm.dev">
|
||||
<configuration name="calculateTimeBetweenDates" type="JavaScriptTestRunnerVitest" temporary="true" nameIsGenerated="true">
|
||||
<node-interpreter value="project" />
|
||||
<vitest-package value="$PROJECT_DIR$/node_modules/vitest" />
|
||||
@@ -293,11 +329,11 @@
|
||||
</list>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Vitest.calculateTimeBetweenDates" />
|
||||
<item itemvalue="Vitest.timeBetweenDates" />
|
||||
<item itemvalue="npm.dev" />
|
||||
<item itemvalue="Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp" />
|
||||
<item itemvalue="Vitest.parsePageRanges" />
|
||||
<item itemvalue="Vitest.timeBetweenDates" />
|
||||
<item itemvalue="Vitest.calculateTimeBetweenDates" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
@@ -384,30 +420,7 @@
|
||||
<workItem from="1743018497879" duration="3895000" />
|
||||
<workItem from="1743047367993" duration="986000" />
|
||||
<workItem from="1743103182313" duration="4264000" />
|
||||
</task>
|
||||
<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>
|
||||
<workItem from="1743348610793" duration="21855000" />
|
||||
</task>
|
||||
<task id="LOCAL-00127" summary="chore: show tooloptions in example">
|
||||
<option name="closed" value="true" />
|
||||
@@ -777,7 +790,31 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1743106796406</updated>
|
||||
</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 />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -824,9 +861,6 @@
|
||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||
<option name="CHECK_NEW_TODO" value="false" />
|
||||
<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="chore: update meta" />
|
||||
<MESSAGE value="feat: change pgn opacity" />
|
||||
@@ -849,7 +883,10 @@
|
||||
<MESSAGE value="chore: show new tools in landing" />
|
||||
<MESSAGE value="chore: zoom on hover" />
|
||||
<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 name="XSLT-Support.FileAssociations.UIState">
|
||||
<expand />
|
||||
|
9
@types/theme.d.ts
vendored
Normal file
9
@types/theme.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import '@mui/material/styles';
|
||||
|
||||
declare module '@mui/material/styles' {
|
||||
interface TypeBackground {
|
||||
hover?: string;
|
||||
lightSecondary?: string;
|
||||
darkSecondary?: string;
|
||||
}
|
||||
}
|
BIN
public/assets/background-dark.png
Normal file
BIN
public/assets/background-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 140 KiB |
@@ -1,14 +1,14 @@
|
||||
import { BrowserRouter, useRoutes } from 'react-router-dom';
|
||||
import routesConfig from '../config/routesConfig';
|
||||
import Navbar from './Navbar';
|
||||
import { Suspense } from 'react';
|
||||
import { Suspense, useMemo, useState } from 'react';
|
||||
import Loading from './Loading';
|
||||
import { ThemeProvider } from '@mui/material';
|
||||
import theme from '../config/muiConfig';
|
||||
import { CssBaseline, ThemeProvider } from '@mui/material';
|
||||
import { CustomSnackBarProvider } from '../contexts/CustomSnackBarContext';
|
||||
import { SnackbarProvider } from 'notistack';
|
||||
import { tools } from '../tools';
|
||||
import './index.css';
|
||||
import { darkTheme, lightTheme } from '../config/muiConfig';
|
||||
|
||||
const AppRoutes = () => {
|
||||
const updatedRoutesConfig = [...routesConfig];
|
||||
@@ -19,21 +19,29 @@ const AppRoutes = () => {
|
||||
};
|
||||
|
||||
function App() {
|
||||
const [darkMode, setDarkMode] = useState<boolean>(() => {
|
||||
return localStorage.getItem('theme') === 'dark';
|
||||
});
|
||||
const theme = useMemo(() => (darkMode ? darkTheme : lightTheme), [darkMode]);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<SnackbarProvider
|
||||
maxSnack={5}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
classes={{
|
||||
containerRoot: 'bottom-0 right-0 mb-52 md:mb-68 mr-8 lg:mr-80 z-99'
|
||||
}}
|
||||
>
|
||||
<CustomSnackBarProvider>
|
||||
<BrowserRouter>
|
||||
<Navbar />
|
||||
<Navbar
|
||||
onSwitchTheme={() => {
|
||||
setDarkMode((prevState) => !prevState);
|
||||
localStorage.setItem('theme', darkMode ? 'light' : 'dark');
|
||||
}}
|
||||
/>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<AppRoutes />
|
||||
</Suspense>
|
||||
|
@@ -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 SearchIcon from '@mui/icons-material/Search';
|
||||
import Grid from '@mui/material/Grid';
|
||||
@@ -25,6 +25,7 @@ const exampleTools: { label: string; url: string }[] = [
|
||||
];
|
||||
export default function Hero() {
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const theme = useTheme();
|
||||
const [filteredTools, setFilteredTools] = useState<DefinedTool[]>(
|
||||
_.shuffle(tools)
|
||||
);
|
||||
@@ -78,7 +79,7 @@ export default function Hero() {
|
||||
endAdornment: <SearchIcon />,
|
||||
sx: {
|
||||
borderRadius: 4,
|
||||
backgroundColor: 'white'
|
||||
backgroundColor: 'background.paper'
|
||||
}
|
||||
}}
|
||||
onChange={(event) => handleInputChange(event, event.target.value)}
|
||||
@@ -125,11 +126,13 @@ export default function Hero() {
|
||||
borderWidth: 1,
|
||||
padding: 1,
|
||||
borderRadius: 3,
|
||||
borderColor: 'grey',
|
||||
borderColor: theme.palette.mode === 'dark' ? '#363b41' : 'grey',
|
||||
borderStyle: 'solid',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: 'background.paper',
|
||||
cursor: 'pointer',
|
||||
'&:hover': { backgroundColor: '#FAFAFD' }
|
||||
'&:hover': {
|
||||
backgroundColor: 'background.hover'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography>{tool.label}</Typography>
|
||||
|
@@ -17,8 +17,13 @@ import {
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
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 theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
@@ -30,7 +35,9 @@ const Navbar: React.FC = () => {
|
||||
// { label: 'Features', path: '/features' }
|
||||
// { label: 'About Us', path: '/about-us' }
|
||||
];
|
||||
|
||||
const buttons: ReactNode[] = [
|
||||
<DarkModeIcon onClick={onSwitchTheme} style={{ cursor: 'pointer' }} />,
|
||||
<Icon
|
||||
onClick={() => window.open('https://discord.gg/SDbbn3hT4b', '_blank')}
|
||||
style={{ cursor: 'pointer' }}
|
||||
@@ -81,9 +88,10 @@ const Navbar: React.FC = () => {
|
||||
return (
|
||||
<AppBar
|
||||
position="static"
|
||||
style={{
|
||||
backgroundColor: '#F5F5FA',
|
||||
color: 'black'
|
||||
sx={{
|
||||
background: 'transparent',
|
||||
boxShadow: 'none',
|
||||
color: 'text.primary'
|
||||
}}
|
||||
>
|
||||
<Toolbar
|
||||
|
@@ -31,7 +31,7 @@ function ToolLinks() {
|
||||
<Grid container spacing={2} mt={1}>
|
||||
<Grid item md={12} lg={6}>
|
||||
<StyledButton
|
||||
sx={{ backgroundColor: 'white' }}
|
||||
sx={{ backgroundColor: 'background.paper' }}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onClick={() => scrollToElement('tool')}
|
||||
@@ -43,6 +43,7 @@ function ToolLinks() {
|
||||
<StyledButton
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
sx={{ backgroundColor: 'background.paper' }}
|
||||
onClick={() => scrollToElement('examples')}
|
||||
>
|
||||
See Examples
|
||||
|
@@ -38,7 +38,7 @@ export default function ToolLayout({
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
sx={{ backgroundColor: '#F5F5FA' }}
|
||||
sx={{ backgroundColor: 'background.default' }}
|
||||
>
|
||||
<Helmet>
|
||||
<title>{`${title} - Omni Tools`}</title>
|
||||
|
@@ -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 ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@@ -10,6 +18,7 @@ export default function ToolCard({
|
||||
link,
|
||||
icon
|
||||
}: ToolCardProps) {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Card
|
||||
@@ -17,10 +26,13 @@ export default function ToolCard({
|
||||
raised
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
bgcolor: '#5581b5',
|
||||
borderColor: '#5581b5',
|
||||
bgcolor: 'background.darkSecondary',
|
||||
borderColor: 'background.darkSecondary',
|
||||
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',
|
||||
height: '100%',
|
||||
'&:hover': {
|
||||
|
@@ -38,14 +38,16 @@ export default function ExampleCard<T>({
|
||||
changeInputResult(sampleText, sampleOptions);
|
||||
}}
|
||||
sx={{
|
||||
bgcolor: theme.palette.background.default,
|
||||
bgcolor: 'background.lightSecondary',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
borderRadius: 2,
|
||||
transition: 'background-color 0.3s ease',
|
||||
cursor: 'pointer',
|
||||
'&: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'}`
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@@ -94,7 +94,7 @@ export default function BaseFileInput({
|
||||
border: preview ? 0 : 1,
|
||||
borderRadius: 2,
|
||||
boxShadow: '5',
|
||||
bgcolor: 'white',
|
||||
bgcolor: 'background.paper',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
@@ -106,7 +106,8 @@ export default function BaseFileInput({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundImage: `url(${greyPattern})`,
|
||||
backgroundImage:
|
||||
theme.palette.mode === 'dark' ? null : `url(${greyPattern})`,
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
@@ -126,7 +127,13 @@ export default function BaseFileInput({
|
||||
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
|
||||
use a {type} from your clipboard, drag and drop a file from
|
||||
desktop
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
@@ -52,7 +52,7 @@ export default function ToolTextInput({
|
||||
rows={10}
|
||||
sx={{
|
||||
'&.MuiTextField-root': {
|
||||
backgroundColor: 'white'
|
||||
backgroundColor: 'background.paper'
|
||||
}
|
||||
}}
|
||||
inputProps={{
|
||||
|
@@ -30,7 +30,7 @@ const ColorSelector: React.FC<ColorSelectorProps & TextFieldProps> = ({
|
||||
<Box mb={1}>
|
||||
<Stack direction={'row'}>
|
||||
<TextField
|
||||
sx={{ backgroundColor: 'white' }}
|
||||
sx={{ backgroundColor: 'background.paper' }}
|
||||
value={color}
|
||||
onChange={handleColorChange}
|
||||
{...props}
|
||||
|
@@ -19,7 +19,7 @@ const TextFieldWithDesc = ({
|
||||
<Box>
|
||||
<TextField
|
||||
placeholder={placeholder}
|
||||
sx={{ backgroundColor: 'white' }}
|
||||
sx={{ backgroundColor: 'background.paper' }}
|
||||
value={value}
|
||||
onChange={(event) => onOwnChange(event.target.value)}
|
||||
{...props}
|
||||
|
@@ -36,7 +36,7 @@ export default function ToolOptions<T extends FormikValues>({
|
||||
mb: 2,
|
||||
borderRadius: 2,
|
||||
padding: 2,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
backgroundColor: 'background.lightSecondary',
|
||||
boxShadow: '2'
|
||||
}}
|
||||
mt={2}
|
||||
|
@@ -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 InputHeader from '../InputHeader';
|
||||
import greyPattern from '@assets/grey-pattern.png';
|
||||
@@ -21,6 +21,7 @@ export default function ToolFileResult({
|
||||
}) {
|
||||
const [preview, setPreview] = React.useState<string | null>(null);
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
const theme = useTheme();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (value) {
|
||||
@@ -87,7 +88,7 @@ export default function ToolFileResult({
|
||||
border: preview ? 0 : 1,
|
||||
borderRadius: 2,
|
||||
boxShadow: '5',
|
||||
bgcolor: 'white'
|
||||
bgcolor: 'background.paper'
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
@@ -114,7 +115,8 @@ export default function ToolFileResult({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundImage: `url(${greyPattern})`
|
||||
backgroundImage:
|
||||
theme.palette.mode === 'dark' ? null : `url(${greyPattern})`
|
||||
}}
|
||||
>
|
||||
{fileType === 'image' && (
|
||||
|
@@ -52,7 +52,7 @@ export default function ToolTextResult({
|
||||
multiline
|
||||
sx={{
|
||||
'&.MuiTextField-root': {
|
||||
backgroundColor: 'white'
|
||||
backgroundColor: 'background.paper'
|
||||
}
|
||||
}}
|
||||
rows={10}
|
||||
|
@@ -1,13 +1,50 @@
|
||||
import { createTheme } from '@mui/material';
|
||||
import { createTheme, ThemeOptions } from '@mui/material';
|
||||
|
||||
const theme = createTheme({
|
||||
const sharedThemeOptions: ThemeOptions = {
|
||||
typography: {
|
||||
button: {
|
||||
textTransform: 'none'
|
||||
}
|
||||
},
|
||||
palette: { background: { default: '#ebf5ff' } },
|
||||
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' }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { getToolsByCategory } from '@tools/index';
|
||||
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 Typography from '@mui/material/Typography';
|
||||
import Button from '@mui/material/Button';
|
||||
@@ -19,6 +19,7 @@ const SingleCategory = function ({
|
||||
index: number;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const [hovered, setHovered] = useState<boolean>(false);
|
||||
const toggleHover = () => setHovered((prevState) => !prevState);
|
||||
return (
|
||||
@@ -32,7 +33,7 @@ const SingleCategory = function ({
|
||||
<Card
|
||||
sx={{
|
||||
height: '100%',
|
||||
backgroundColor: hovered ? '#FAFAFD' : 'white'
|
||||
backgroundColor: hovered ? 'background.hover' : 'background.paper'
|
||||
}}
|
||||
>
|
||||
<CardContent sx={{ height: '100%' }}>
|
||||
@@ -52,7 +53,11 @@ const SingleCategory = function ({
|
||||
color={categoriesColors[index % categoriesColors.length]}
|
||||
/>
|
||||
<Link
|
||||
style={{ fontSize: 20, fontWeight: 700, color: 'black' }}
|
||||
style={{
|
||||
fontSize: 20,
|
||||
fontWeight: 700,
|
||||
color: theme.palette.mode === 'dark' ? 'white' : 'black'
|
||||
}}
|
||||
to={'/categories/' + category.type}
|
||||
>
|
||||
{category.title}
|
||||
@@ -70,7 +75,7 @@ const SingleCategory = function ({
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Button
|
||||
sx={{ backgroundColor: 'white' }}
|
||||
sx={{ backgroundColor: 'background.default' }}
|
||||
fullWidth
|
||||
onClick={() => navigate(category.example.path)}
|
||||
variant={'outlined'}
|
||||
|
@@ -1,16 +1,23 @@
|
||||
import { Box } from '@mui/material';
|
||||
import { Box, useTheme } from '@mui/material';
|
||||
import Hero from 'components/Hero';
|
||||
import Categories from './Categories';
|
||||
|
||||
export default function Home() {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
padding={{
|
||||
xs: 1,
|
||||
md: 3,
|
||||
lg: 5,
|
||||
background: `url(/assets/background.svg)`,
|
||||
backgroundColor: '#F5F5FA'
|
||||
lg: 5
|
||||
}}
|
||||
sx={{
|
||||
background: `url(/assets/${
|
||||
theme.palette.mode === 'dark'
|
||||
? 'background-dark.png'
|
||||
: 'background.svg'
|
||||
})`,
|
||||
backgroundColor: 'background.default'
|
||||
}}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
|
@@ -26,7 +26,7 @@ export default function Home() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box sx={{ backgroundColor: '#F5F5FA' }}>
|
||||
<Box sx={{ backgroundColor: 'background.default' }}>
|
||||
<Box
|
||||
padding={{ xs: 1, md: 3, lg: 5 }}
|
||||
display={'flex'}
|
||||
@@ -55,12 +55,14 @@ export default function Home() {
|
||||
<Grid item xs={12} md={6} lg={4} key={tool.path}>
|
||||
<Stack
|
||||
sx={{
|
||||
backgroundColor: 'white',
|
||||
boxShadow: '5px 4px 2px #E9E9ED',
|
||||
backgroundColor: 'background.paper',
|
||||
boxShadow: `5px 4px 2px ${
|
||||
theme.palette.mode === 'dark' ? 'black' : '#E9E9ED'
|
||||
}`,
|
||||
cursor: 'pointer',
|
||||
height: '100%',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.background.default // Change this to your desired hover color
|
||||
backgroundColor: theme.palette.background.hover
|
||||
}
|
||||
}}
|
||||
onClick={() => navigate('/' + tool.path)}
|
||||
@@ -77,7 +79,12 @@ export default function Home() {
|
||||
color={categoriesColors[index % categoriesColors.length]}
|
||||
/>
|
||||
<Box>
|
||||
<Link style={{ fontSize: 20 }} to={'/' + tool.path}>
|
||||
<Link
|
||||
style={{
|
||||
fontSize: 20
|
||||
}}
|
||||
to={'/' + tool.path}
|
||||
>
|
||||
{tool.name}
|
||||
</Link>
|
||||
<Typography sx={{ mt: 2 }}>
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import ToolFileInput from '@components/input/ToolFileInput';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ToolFileResult from '@components/result/ToolFileResult';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
@@ -8,7 +7,6 @@ import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { parsePageRanges, splitPdf } from './service';
|
||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { FormikProps } from 'formik';
|
||||
import ToolPdfInput from '@components/input/ToolPdfInput';
|
||||
|
||||
type InitialValuesType = {
|
||||
|
@@ -15,7 +15,6 @@ export async function compressVideo(
|
||||
input: File,
|
||||
options: CompressVideoOptions
|
||||
): Promise<File> {
|
||||
console.log('Compressing video...', options);
|
||||
if (!ffmpeg.loaded) {
|
||||
await ffmpeg.load({
|
||||
wasmURL:
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { Box } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import ToolFileInput from '@components/input/ToolFileInput';
|
||||
import ToolFileResult from '@components/result/ToolFileResult';
|
||||
import TextFieldWithDesc from 'components/options/TextFieldWithDesc';
|
||||
import Typography from '@mui/material/Typography';
|
||||
@@ -9,6 +8,8 @@ import { FrameOptions, GifReader, GifWriter } from 'omggif';
|
||||
import { gifBinaryToFile } from '@utils/gif';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import ToolVideoInput from '@components/input/ToolVideoInput';
|
||||
import ToolImageInput from '@components/input/ToolImageInput';
|
||||
|
||||
const initialValues = {
|
||||
newSpeed: 200
|
||||
@@ -108,7 +109,7 @@ export default function ChangeSpeed({ title }: ToolComponentProps) {
|
||||
title={title}
|
||||
input={input}
|
||||
inputComponent={
|
||||
<ToolFileInput
|
||||
<ToolImageInput
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
accept={['image/gif']}
|
||||
|
@@ -2,11 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"target": "esnext",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
@@ -19,30 +15,14 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"types": [
|
||||
"vite/client",
|
||||
"vitest/globals",
|
||||
"@testing-library/jest-dom"
|
||||
],
|
||||
"types": ["vite/client", "vitest/globals", "@testing-library/jest-dom"],
|
||||
"paths": {
|
||||
"@tools/*": [
|
||||
"./tools/*"
|
||||
],
|
||||
"@assets/*": [
|
||||
"./assets/*"
|
||||
],
|
||||
"@components/*": [
|
||||
"./components/*"
|
||||
],
|
||||
"@utils/*": [
|
||||
"./utils/*"
|
||||
]
|
||||
"@tools/*": ["./tools/*"],
|
||||
"@assets/*": ["./assets/*"],
|
||||
"@components/*": ["./components/*"],
|
||||
"@utils/*": ["./utils/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
"include": ["src", "./@types"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
Reference in New Issue
Block a user