Skip to content

Commit

Permalink
Merge pull request #14 from WeFoxTech/feature/byteunit-conversion-tool
Browse files Browse the repository at this point in the history
add byte unit conversion tool 💯
  • Loading branch information
foxundermoon authored Mar 21, 2020
2 parents 0b4fd75 + cebd83f commit 17865a3
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 0 deletions.
9 changes: 9 additions & 0 deletions packages/website/components/icons/Sdcard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SvgIcon } from '@material-ui/core';

export const SdcardIcon: React.FC = props => {
return (
<SvgIcon viewBox="0 0 1549 1024" {...props}>
<path d="M768 0H384L128 256v640c0 70.6 57.4 128 128 128h512c70.6 0 128-57.4 128-128V128C896 57.4 838.6 0 768 0zM448 320h-96V128h96v192zm160 0h-96V128h96v192zm160 0h-96V128h96v192z" />
</SvgIcon>
);
};
180 changes: 180 additions & 0 deletions packages/website/components/tool/ByteConverter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import React from 'react';
import useTranslation from '~/src/hooks/useTranslation';
import TimeUnitSvg from '~/components/svgs/TimeUnit';
import {
Box,
TextField,
Typography,
TextareaAutosize,
Divider,
Button,
Grid,
IconButton,
CircularProgress,
Snackbar,
InputLabel,
Select,
MenuItem,
Hidden,
InputAdornment,
FormControl,
Input,
} from '@material-ui/core';
import BigNumber from 'bignumber.js';
import { TranslationKey, InlineLocale } from '~/src/translations/types';
import copy from 'copy-to-clipboard';
import FileCopyIcon from '@material-ui/icons/FileCopy';
import Alert from '@material-ui/lab/Alert';

const title: InlineLocale = {
zh: '字节单位换算工具',
en: 'Byte Unit Conversion Tool',
};

const units = {
bit: 1,
Byte: 8,
KiB: 2 ** 10 * 8,
MiB: 2 ** 20 * 8,
GiB: 2 ** 30 * 8,
TiB: 2 ** 40 * 8,
PiB: 2 ** 50 * 8,
EiB: 2 ** 60 * 8,
ZiB: 2 ** 70 * 8,
YiB: 2 ** 80 * 8,
};

type Units = typeof units;

type UnitKey = keyof Units;

export const ByteConverter: React.FC = () => {
const { t, locale } = useTranslation();
const [ns, setNs] = React.useState<null | BigNumber>(null);
const [fixedType, setFixedType] = React.useState<
Partial<{ [key: string]: string | null | undefined }>
>({});

const textArea = React.useRef<HTMLTextAreaElement>(null);

const [error, setError] = React.useState<null | string>(null);
const [success, setSuccess] = React.useState<null | string>(null);

const checkAndSetNs = (name: string, value: string, calc: (input: BigNumber) => BigNumber) => {
if (value) {
const dotTimes = (value.match(/\./g) || []).length;
if (dotTimes > 1) {
setError(`. appear ${dotTimes} times`);
} else {
if (/\.0*$/.test(value) || /^\d*\.$/.test(value)) {
setFixedType({ [name]: value });
} else if (/^[\d\.]+$/.test(value)) {
setNs(calc(new BigNumber(value, 10)));
setFixedType({ [name]: null });
} else {
const charArr = value.split('');
const lastChar = charArr.pop();
setError(`${lastChar} is not a number`);
}
}
} else {
setNs(null);
}
};

const copyText = (name: UnitKey, text: string) => {
const result = copy(text);
if (result) {
setSuccess(t('copySuccess'));
} else {
setError(t('copyFailure'));
}
};

const data: Array<[
UnitKey,
string,
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void
]> = Object.keys(units).map(ele => {
const key = ele as UnitKey;
return [
key,
fixedType[key] ?? ns?.dividedBy(units[key]).toString(10) ?? '',
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
checkAndSetNs(key, e.currentTarget.value, bn => bn.multipliedBy(units[key])),
];
});

return (
<Box>
<Snackbar
open={!!error || !!success}
autoHideDuration={2000}
onClose={e => {
setError(null);
setSuccess(null);
}}
>
<Alert
onClose={e => {
setError(null);
setSuccess(null);
}}
severity={error ? 'error' : success ? 'success' : 'info'}
>
{error || success}
</Alert>
</Snackbar>
<Box textAlign="center" justifyContent="center">
<Typography variant="h3">{title[locale]}</Typography>
<Box pt={4}>
<Button
color="primary"
variant="contained"
onClick={e => {
setNs(null);
setFixedType({});
}}
>
{t('reset')}
</Button>
</Box>
</Box>
<Box pt={2} pb={16} justifyContent="center" alignItems="center" textAlign="center">
<form>
<Grid container spacing={1} justify="center">
{data.map((e, i) => {
const [k, v, h] = e;
return (
<Grid key={i} item xs={12} md={6}>
<Box>
<FormControl>
<InputLabel htmlFor={`byte-unit-${k}`}>{k}</InputLabel>
<Input
id={`byte-unit-${k}`}
value={v}
onChange={h}
fullWidth
type="number"
multiline
endAdornment={
<InputAdornment position="end">
{v && (
<Button startIcon={<FileCopyIcon />} onClick={e => copyText(k, v)}>
{t('copy')}
</Button>
)}
</InputAdornment>
}
></Input>
</FormControl>
</Box>
</Grid>
);
})}
</Grid>
</form>
</Box>
</Box>
);
};
60 changes: 60 additions & 0 deletions packages/website/pages/[lang]/tool/byte-unit-converter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as React from 'react';
import Layout from '~/components/Layout';
import withLocale from '~/src/hocs/withLocale';
import { GetstaticProps } from '~/src/types/next';
import locales from '~/src/translations/locales';
import useTranslation from '~/src/hooks/useTranslation';
import { PageMeta } from '~/src/PageMeta';
import { ByteConverter } from '~/components/tool/ByteConverter';

const commonKeyworks = ['wefox', 'Byte unit'];
const meta: PageMeta = {
title: 'byte unit converter',
keywords: [...commonKeyworks, 'technical consulting', 'Technical Adviser'],
description: 'Tools to convert various byte units',
author: 'fox, [email protected]',
zh: {
title: '字节单位转换工具',
keywords: [
...commonKeyworks,
'字节单位转换',
'字节单位',
'文件单位',
'存储单位',
'带宽单位',
'技术咨询',
'技术顾问',
],
description: '转换字节单位的工具',
},
};

const ByteUnitConvertPage = () => {
const { t, locale } = useTranslation();

return (
<Layout meta={meta}>
<ByteConverter />
</Layout>
);
};

export default withLocale(ByteUnitConvertPage);

export async function getStaticPaths() {
return {
paths: ['en', 'zh'].map(l => ({ params: { lang: l } })),
fallback: false,
};
}

export const getStaticProps: GetstaticProps = async ({ params }) => {
const { lang } = params;
const currentLocales = locales[lang];
return {
props: {
locale: lang,
translations: currentLocales['common'],
},
};
};
10 changes: 10 additions & 0 deletions packages/website/src/data/defaultMenusData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import InfoIcon from '@material-ui/icons/Info';
import KeyboardVoiceIcon from '@material-ui/icons/KeyboardVoice';
import AccessTimeIcon from '@material-ui/icons/AccessTime';
import ToolIcon from '@material-ui/icons/Build';
import { SdcardIcon } from '~/components/icons/Sdcard';

export const menusData: LayoutMenu[] = [
{
Expand Down Expand Up @@ -51,6 +52,15 @@ export const menusData: LayoutMenu[] = [
scroll: false,
},
},
{
type: 'link',
name: 'toolByteConverter',
icon: SdcardIcon,
linkProps: {
href: '/tool/byte-unit-converter',
scroll: false,
},
},
{
type: 'link',
name: 'toolTimeConverter',
Expand Down
1 change: 1 addition & 0 deletions packages/website/src/translations/locales/en/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const common = {
copyFailure: 'Copy Failure',
toolTextToSpeech: 'Text-to-speech Tool',
toolTimeConverter: 'Time converter tool',
toolByteConverter: 'Byte converter tool',
};

export default common;
1 change: 1 addition & 0 deletions packages/website/src/translations/locales/zh/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const common: AllTranslations['common'] = {
reset: '重置',
toolTextToSpeech: '文字转语音',
toolTimeConverter: '时间单位转换工具',
toolByteConverter: '字节单位转换工具',
};

export default common;

1 comment on commit 17865a3

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for website ready!

Built with commit 17865a3

https://home-website-1ru5n89hk.now.sh

Please sign in to comment.