mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-11-15 20:38:33 +05:30
commit
d16cd41568
5 changed files with 207 additions and 68 deletions
|
|
@ -15,11 +15,16 @@ import { useState } from 'react';
|
|||
import { DefinedTool } from '@tools/defineTool';
|
||||
import { filterTools, tools } from '@tools/index';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import _ from 'lodash';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { getToolCategoryTitle } from '@utils/string';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { validNamespaces } from '../i18n';
|
||||
import {
|
||||
getBookmarkedToolPaths,
|
||||
isBookmarked,
|
||||
toggleBookmarked
|
||||
} from '@utils/bookmark';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
|
||||
const GroupHeader = styled('div')(({ theme }) => ({
|
||||
position: 'sticky',
|
||||
|
|
@ -36,61 +41,59 @@ const GroupItems = styled('ul')({
|
|||
padding: 0
|
||||
});
|
||||
|
||||
type ToolInfo = {
|
||||
label: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export default function Hero() {
|
||||
const { t } = useTranslation(validNamespaces);
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const theme = useTheme();
|
||||
const [filteredTools, setFilteredTools] = useState<DefinedTool[]>(tools);
|
||||
const [bookmarkedToolPaths, setBookmarkedToolPaths] = useState<string[]>(
|
||||
getBookmarkedToolPaths()
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const exampleTools: { label: string; url: string; translationKey: string }[] =
|
||||
[
|
||||
{
|
||||
label: t('translation:hero.examples.createTransparentImage'),
|
||||
url: '/image-generic/create-transparent',
|
||||
translationKey: 'translation:hero.examples.createTransparentImage'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.prettifyJson'),
|
||||
url: '/json/prettify',
|
||||
translationKey: 'translation:hero.examples.prettifyJson'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.changeGifSpeed'),
|
||||
url: '/gif/change-speed',
|
||||
translationKey: 'translation:hero.examples.changeGifSpeed'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.sortList'),
|
||||
url: '/list/sort',
|
||||
translationKey: 'translation:hero.examples.sortList'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.compressPng'),
|
||||
url: '/png/compress-png',
|
||||
translationKey: 'translation:hero.examples.compressPng'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.splitText'),
|
||||
url: '/string/split',
|
||||
translationKey: 'translation:hero.examples.splitText'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.splitPdf'),
|
||||
url: '/pdf/split-pdf',
|
||||
translationKey: 'translation:hero.examples.splitPdf'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.trimVideo'),
|
||||
url: '/video/trim',
|
||||
translationKey: 'translation:hero.examples.trimVideo'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.calculateNumberSum'),
|
||||
url: '/number/sum',
|
||||
translationKey: 'translation:hero.examples.calculateNumberSum'
|
||||
}
|
||||
];
|
||||
const exampleTools: ToolInfo[] = [
|
||||
{
|
||||
label: t('translation:hero.examples.createTransparentImage'),
|
||||
url: '/image-generic/create-transparent'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.prettifyJson'),
|
||||
url: '/json/prettify'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.changeGifSpeed'),
|
||||
url: '/gif/change-speed'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.sortList'),
|
||||
url: '/list/sort'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.compressPng'),
|
||||
url: '/png/compress-png'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.splitText'),
|
||||
url: '/string/split'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.splitPdf'),
|
||||
url: '/pdf/split-pdf'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.trimVideo'),
|
||||
url: '/video/trim'
|
||||
},
|
||||
{
|
||||
label: t('translation:hero.examples.calculateNumberSum'),
|
||||
url: '/number/sum'
|
||||
}
|
||||
];
|
||||
|
||||
const handleInputChange = (
|
||||
event: React.ChangeEvent<{}>,
|
||||
|
|
@ -99,6 +102,24 @@ export default function Hero() {
|
|||
setInputValue(newInputValue);
|
||||
setFilteredTools(filterTools(tools, newInputValue, t));
|
||||
};
|
||||
const toolsMap = new Map<string, ToolInfo>();
|
||||
for (const tool of filteredTools) {
|
||||
toolsMap.set(tool.path, {
|
||||
label: tool.name,
|
||||
url: '/' + tool.path
|
||||
});
|
||||
}
|
||||
|
||||
const displayedTools =
|
||||
bookmarkedToolPaths.length > 0
|
||||
? bookmarkedToolPaths.flatMap((path) => {
|
||||
const tool = toolsMap.get(path);
|
||||
if (tool === undefined) {
|
||||
return [];
|
||||
}
|
||||
return [{ ...tool, label: t(tool.label) }];
|
||||
})
|
||||
: exampleTools;
|
||||
|
||||
return (
|
||||
<Box width={{ xs: '90%', md: '80%', lg: '60%' }}>
|
||||
|
|
@ -159,14 +180,42 @@ export default function Hero() {
|
|||
{...props}
|
||||
onClick={() => navigate('/' + option.path)}
|
||||
>
|
||||
<Stack direction={'row'} spacing={2} alignItems={'center'}>
|
||||
<Icon fontSize={20} icon={option.icon} />
|
||||
<Box>
|
||||
<Typography fontWeight={'bold'}>{t(option.name)}</Typography>
|
||||
<Typography fontSize={12}>
|
||||
{t(option.shortDescription)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack
|
||||
direction={'row'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'space-between'}
|
||||
width={'100%'}
|
||||
>
|
||||
<Stack direction={'row'} spacing={2} alignItems={'center'}>
|
||||
<Icon fontSize={20} icon={option.icon} />
|
||||
<Box>
|
||||
<Typography fontWeight={'bold'}>{t(option.name)}</Typography>
|
||||
<Typography fontSize={12}>
|
||||
{t(option.shortDescription)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleBookmarked(option.path);
|
||||
setBookmarkedToolPaths(getBookmarkedToolPaths());
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
fontSize={20}
|
||||
color={
|
||||
isBookmarked(option.path)
|
||||
? theme.palette.primary.main
|
||||
: theme.palette.grey[500]
|
||||
}
|
||||
icon={
|
||||
isBookmarked(option.path)
|
||||
? 'mdi:bookmark'
|
||||
: 'mdi:bookmark-plus-outline'
|
||||
}
|
||||
/>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
|
|
@ -177,7 +226,7 @@ export default function Hero() {
|
|||
}}
|
||||
/>
|
||||
<Grid container spacing={2} mt={2}>
|
||||
{exampleTools.map((tool) => (
|
||||
{displayedTools.map((tool) => (
|
||||
<Grid
|
||||
onClick={() =>
|
||||
navigate(tool.url.startsWith('/') ? tool.url : `/${tool.url}`)
|
||||
|
|
@ -186,7 +235,7 @@ export default function Hero() {
|
|||
xs={12}
|
||||
md={6}
|
||||
lg={4}
|
||||
key={tool.translationKey}
|
||||
key={tool.label}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -202,10 +251,30 @@ export default function Hero() {
|
|||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
backgroundColor: 'background.hover'
|
||||
}
|
||||
},
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
<Typography>{tool.label}</Typography>
|
||||
<Stack direction={'row'} spacing={1} alignItems={'center'}>
|
||||
<Typography textAlign={'center'}>{tool.label}</Typography>
|
||||
{bookmarkedToolPaths.length > 0 && (
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const path = tool.url.substring(1);
|
||||
toggleBookmarked(path);
|
||||
setBookmarkedToolPaths(getBookmarkedToolPaths());
|
||||
}}
|
||||
size={'small'}
|
||||
>
|
||||
<Icon
|
||||
icon={'mdi:close'}
|
||||
color={theme.palette.grey[500]}
|
||||
fontSize={15}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Box, Button, styled, useTheme } from '@mui/material';
|
||||
import { Box, Button, Stack, styled, useTheme } from '@mui/material';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import ToolBreadcrumb from './ToolBreadcrumb';
|
||||
import { capitalizeFirstLetter } from '../utils/string';
|
||||
|
|
@ -7,6 +7,8 @@ import { Icon, IconifyIcon } from '@iconify/react';
|
|||
import { categoriesColors } from '../config/uiConfig';
|
||||
import { getToolsByCategory } from '@tools/index';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isBookmarked, toggleBookmarked } from '@utils/bookmark';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const StyledButton = styled(Button)(({ theme }) => ({
|
||||
|
|
@ -22,6 +24,7 @@ interface ToolHeaderProps {
|
|||
description: string;
|
||||
icon?: IconifyIcon | string;
|
||||
type: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
function ToolLinks() {
|
||||
|
|
@ -82,8 +85,11 @@ export default function ToolHeader({
|
|||
icon,
|
||||
title,
|
||||
description,
|
||||
type
|
||||
type,
|
||||
path
|
||||
}: ToolHeaderProps) {
|
||||
const theme = useTheme();
|
||||
const [bookmarked, setBookmarked] = useState<boolean>(isBookmarked(path));
|
||||
return (
|
||||
<Box my={4}>
|
||||
<ToolBreadcrumb
|
||||
|
|
@ -100,9 +106,27 @@ export default function ToolHeader({
|
|||
/>
|
||||
<Grid mt={1} container spacing={2}>
|
||||
<Grid item xs={12} md={8}>
|
||||
<Typography mb={2} fontSize={30} color={'primary'}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Stack direction={'row'} spacing={2} alignItems={'center'}>
|
||||
<Typography mb={2} fontSize={30} color={'primary'}>
|
||||
{title}
|
||||
</Typography>
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
toggleBookmarked(path);
|
||||
setBookmarked(!bookmarked);
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
fontSize={30}
|
||||
color={
|
||||
bookmarked
|
||||
? theme.palette.primary.main
|
||||
: theme.palette.grey[500]
|
||||
}
|
||||
icon={bookmarked ? 'mdi:bookmark' : 'mdi:bookmark-plus-outline'}
|
||||
/>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
<Typography fontSize={20}>{description}</Typography>
|
||||
<ToolLinks />
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -17,11 +17,13 @@ import { FullI18nKey } from '../i18n';
|
|||
export default function ToolLayout({
|
||||
children,
|
||||
icon,
|
||||
i18n,
|
||||
type,
|
||||
i18n
|
||||
fullPath
|
||||
}: {
|
||||
icon?: IconifyIcon | string;
|
||||
type: ToolCategory;
|
||||
fullPath: string;
|
||||
children: ReactNode;
|
||||
i18n?: {
|
||||
name: FullI18nKey;
|
||||
|
|
@ -68,6 +70,7 @@ export default function ToolLayout({
|
|||
description={toolDescription}
|
||||
icon={icon}
|
||||
type={type}
|
||||
path={fullPath}
|
||||
/>
|
||||
{children}
|
||||
<Separator backgroundColor="#5581b5" margin="50px" />
|
||||
|
|
|
|||
|
|
@ -65,7 +65,12 @@ export const defineTool = (
|
|||
component: function ToolComponent() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<ToolLayout icon={icon} type={basePath} i18n={i18n}>
|
||||
<ToolLayout
|
||||
icon={icon}
|
||||
type={basePath}
|
||||
i18n={i18n}
|
||||
fullPath={`${basePath}/${path}`}
|
||||
>
|
||||
<Component
|
||||
title={t(i18n.name)}
|
||||
longDescription={
|
||||
|
|
|
|||
38
src/utils/bookmark.ts
Normal file
38
src/utils/bookmark.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
const bookmarkedToolsKey = 'bookmarkedTools';
|
||||
|
||||
export function getBookmarkedToolPaths(): string[] {
|
||||
return (
|
||||
localStorage
|
||||
.getItem(bookmarkedToolsKey)
|
||||
?.split(',')
|
||||
?.filter((path) => path) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
export function isBookmarked(toolPath: string): boolean {
|
||||
return getBookmarkedToolPaths().some((path) => path === toolPath);
|
||||
}
|
||||
|
||||
export function toggleBookmarked(toolPath: string) {
|
||||
if (isBookmarked(toolPath)) {
|
||||
unbookmark(toolPath);
|
||||
} else {
|
||||
bookmark(toolPath);
|
||||
}
|
||||
}
|
||||
|
||||
function bookmark(toolPath: string) {
|
||||
localStorage.setItem(
|
||||
bookmarkedToolsKey,
|
||||
[toolPath, ...getBookmarkedToolPaths()].join(',')
|
||||
);
|
||||
}
|
||||
|
||||
function unbookmark(toolPath: string) {
|
||||
localStorage.setItem(
|
||||
bookmarkedToolsKey,
|
||||
getBookmarkedToolPaths()
|
||||
.filter((path) => path !== toolPath)
|
||||
.join(',')
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue