mirror of
https://github.com/morten-olsen/parcel.git
synced 2026-02-08 01:36:24 +01:00
sec
This commit is contained in:
13
src/App.tsx
13
src/App.tsx
@@ -3,16 +3,19 @@ import { hot } from 'react-hot-loader/root';
|
|||||||
import { Layout } from 'antd';
|
import { Layout } from 'antd';
|
||||||
import { GithubProvider } from './contexts/Github';
|
import { GithubProvider } from './contexts/Github';
|
||||||
import { EncryptionProvider } from './contexts/Encryption';
|
import { EncryptionProvider } from './contexts/Encryption';
|
||||||
|
import { DecryptionProvider } from './contexts/Decryption';
|
||||||
import AppRouter from './Router';
|
import AppRouter from './Router';
|
||||||
|
|
||||||
const App: React.FC = () => (
|
const App: React.FC = () => (
|
||||||
<GithubProvider>
|
<GithubProvider>
|
||||||
<EncryptionProvider>
|
<EncryptionProvider>
|
||||||
<Layout style={{minHeight:"100vh"}}>
|
<DecryptionProvider>
|
||||||
<Layout.Content style={{ padding: '25px', maxWidth: '800px', width: '100%', margin: 'auto' }}>
|
<Layout style={{minHeight:"100vh"}}>
|
||||||
<AppRouter/>
|
<Layout.Content style={{ padding: '25px', maxWidth: '800px', width: '100%', margin: 'auto' }}>
|
||||||
</Layout.Content>
|
<AppRouter/>
|
||||||
</Layout>
|
</Layout.Content>
|
||||||
|
</Layout>
|
||||||
|
</DecryptionProvider>
|
||||||
</EncryptionProvider>
|
</EncryptionProvider>
|
||||||
</GithubProvider>
|
</GithubProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
|
|
||||||
import Encrypt from './screens/Encrypt';
|
import Encrypt from './screens/Encrypt';
|
||||||
|
import Decrypt from './screens/Decrypt';
|
||||||
import Welcome from './screens/Welcome';
|
import Welcome from './screens/Welcome';
|
||||||
import Debug from './screens/Debug';
|
import Debug from './screens/Debug';
|
||||||
|
|
||||||
@@ -18,6 +19,9 @@ const AppRouter: React.FC = () => (
|
|||||||
<Route path="/welcome">
|
<Route path="/welcome">
|
||||||
<Welcome />
|
<Welcome />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/decrypt">
|
||||||
|
<Decrypt />
|
||||||
|
</Route>
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
<Encrypt />
|
<Encrypt />
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import React, {useMemo} from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
List,
|
List,
|
||||||
Button,
|
Button,
|
||||||
Tooltip,
|
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
@@ -11,9 +10,8 @@ import {
|
|||||||
IssuesCloseOutlined,
|
IssuesCloseOutlined,
|
||||||
LockOutlined,
|
LockOutlined,
|
||||||
DownloadOutlined,
|
DownloadOutlined,
|
||||||
ShareAltOutlined,
|
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { FileType } from '../contexts/Encryption';
|
import FileType from '../types/File';
|
||||||
import { downloadLink } from '../helpers/files';
|
import { downloadLink } from '../helpers/files';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -22,22 +20,11 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const icons: {[name: string]: any} = {
|
const icons: {[name: string]: any} = {
|
||||||
encrypting: <SyncOutlined spin />,
|
processing: <SyncOutlined spin />,
|
||||||
failed: <IssuesCloseOutlined />,
|
failed: <IssuesCloseOutlined />,
|
||||||
encrypted: <LockOutlined />,
|
success: <LockOutlined />,
|
||||||
};
|
};
|
||||||
|
|
||||||
const share = async (file: FileType, fileData: File[]) => {
|
|
||||||
try {
|
|
||||||
navigator.share({
|
|
||||||
title: file.name,
|
|
||||||
files: fileData,
|
|
||||||
} as any);
|
|
||||||
} catch (err) {
|
|
||||||
alert(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const IconText = ({ icon, text, ...props }) => (
|
const IconText = ({ icon, text, ...props }) => (
|
||||||
<Button
|
<Button
|
||||||
{...props}
|
{...props}
|
||||||
@@ -50,13 +37,9 @@ const FileView: React.FC<Props> = ({
|
|||||||
remove,
|
remove,
|
||||||
}) => {
|
}) => {
|
||||||
const icon = icons[file.status];
|
const icon = icons[file.status];
|
||||||
const fileData = useMemo(() => [new File([file.link || ''], file.name, {
|
|
||||||
type: 'text/plain',
|
|
||||||
})], [file]);
|
|
||||||
|
|
||||||
const actions = [];
|
const actions = [];
|
||||||
|
|
||||||
if (file.link) {
|
if (file.blob) {
|
||||||
actions.push(
|
actions.push(
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="Are you sure delete this file?"
|
title="Are you sure delete this file?"
|
||||||
@@ -73,23 +56,13 @@ const FileView: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!!navigator.share && (navigator as any).canSare && (navigator as any).canShare({ files: fileData })) {
|
if (file.blob) {
|
||||||
actions.push(
|
|
||||||
<IconText
|
|
||||||
icon={ShareAltOutlined}
|
|
||||||
text="Share"
|
|
||||||
onClick={() => share(file, fileData)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.link) {
|
|
||||||
actions.push(
|
actions.push(
|
||||||
<IconText
|
<IconText
|
||||||
icon={DownloadOutlined}
|
icon={DownloadOutlined}
|
||||||
type="primary"
|
type="primary"
|
||||||
text="Download"
|
text="Download"
|
||||||
onClick={() => downloadLink(file.name, file.link!)}
|
onClick={() => downloadLink(file.name, file.blob!)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -101,7 +74,6 @@ const FileView: React.FC<Props> = ({
|
|||||||
<List.Item.Meta
|
<List.Item.Meta
|
||||||
avatar={icon}
|
avatar={icon}
|
||||||
title={file.name}
|
title={file.name}
|
||||||
description={`Encrypted for ${file.reciever}`}
|
|
||||||
/>
|
/>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import React, { useContext } from 'react';
|
import React from 'react';
|
||||||
import { Space, List, Empty, Button } from 'antd';
|
import { Space, List, Empty, Button } from 'antd';
|
||||||
import { DownloadOutlined } from '@ant-design/icons';
|
import { DownloadOutlined } from '@ant-design/icons';
|
||||||
import EncryptionContext from '../contexts/Encryption';
|
|
||||||
import useDownloadAll from '../hooks/useDownloadAll';
|
import useDownloadAll from '../hooks/useDownloadAll';
|
||||||
import File from './File';
|
import File from './File';
|
||||||
|
import FileType from '../types/File';
|
||||||
|
|
||||||
const Encrypt: React.FC = () => {
|
interface Props {
|
||||||
const { files, deleteFile } = useContext(EncryptionContext);
|
files: {[id: string]: FileType};
|
||||||
|
deleteFile: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Encrypt: React.FC<Props> = ({
|
||||||
|
files,
|
||||||
|
deleteFile,
|
||||||
|
}) => {
|
||||||
const { status, downloadAll } = useDownloadAll();
|
const { status, downloadAll } = useDownloadAll();
|
||||||
|
|
||||||
if (Object.keys(files).length === 0) {
|
if (Object.keys(files).length === 0) {
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { Theme } from '../theme';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
left?: React.ReactNode;
|
|
||||||
right?: React.ReactNode;
|
|
||||||
title?: string;
|
|
||||||
body?: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Cell = styled.div<{ theme: Theme }>`
|
|
||||||
padding: ${({ theme }) => theme.margin.medium / 2}px;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Wrapper = styled(Cell)`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
background: #dfe6e9;
|
|
||||||
margin-top: ${({ theme }) => theme.margin.medium}px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Title = styled.h2`
|
|
||||||
padding: 0px;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: bold;
|
|
||||||
`
|
|
||||||
|
|
||||||
const Main = styled(Cell)`
|
|
||||||
flex: 1;
|
|
||||||
justify-content: flex-start;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Row: React.FC<Props> = ({
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
title,
|
|
||||||
body,
|
|
||||||
children,
|
|
||||||
}) => (
|
|
||||||
<Wrapper style={{ display: 'flex' }}>
|
|
||||||
{left}
|
|
||||||
<Main>
|
|
||||||
{title && <Title>{title}</Title>}
|
|
||||||
{body}
|
|
||||||
{children}
|
|
||||||
</Main>
|
|
||||||
{right}
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
export {
|
|
||||||
Cell,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Row;
|
|
||||||
36
src/components/decrypt/AddFile.tsx
Normal file
36
src/components/decrypt/AddFile.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React, { useContext, useCallback } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Layout } from 'antd';
|
||||||
|
import { FileAddTwoTone } from '@ant-design/icons';
|
||||||
|
import { useDropzone } from 'react-dropzone';
|
||||||
|
import DecryptionContext from '../../contexts/Decryption';
|
||||||
|
|
||||||
|
const Icon = styled(FileAddTwoTone)`
|
||||||
|
font-size: 100px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DropWrapper = styled(Layout)`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 50px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AddFile: React.FC = () => {
|
||||||
|
const { addFile } = useContext(DecryptionContext);
|
||||||
|
const onDrop = useCallback(acceptedFiles => {
|
||||||
|
acceptedFiles.forEach(addFile);
|
||||||
|
}, [])
|
||||||
|
const {getRootProps, getInputProps} = useDropzone({ onDrop });
|
||||||
|
return (
|
||||||
|
<DropWrapper {...getRootProps()}>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
<Icon />
|
||||||
|
<p>Drag 'n' drop some files here, or click to select files</p>
|
||||||
|
</DropWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddFile;
|
||||||
@@ -3,7 +3,7 @@ import styled from 'styled-components';
|
|||||||
import { Layout } from 'antd';
|
import { Layout } from 'antd';
|
||||||
import { FileAddTwoTone } from '@ant-design/icons';
|
import { FileAddTwoTone } from '@ant-design/icons';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
import EncryptionContext from '../contexts/Encryption';
|
import EncryptionContext from '../../contexts/Encryption';
|
||||||
|
|
||||||
const Icon = styled(FileAddTwoTone)`
|
const Icon = styled(FileAddTwoTone)`
|
||||||
font-size: 100px;
|
font-size: 100px;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useCallback, useContext } from 'react';
|
import React, { useState, useCallback, useContext } from 'react';
|
||||||
import { Input, Form, Button } from 'antd';
|
import { Input, Form, Button } from 'antd';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import EncryptionContext from '../contexts/Encryption';
|
import EncryptionContext from '../../contexts/Encryption';
|
||||||
|
|
||||||
const AddText : React.FC = () => {
|
const AddText : React.FC = () => {
|
||||||
const { addText } = useContext(EncryptionContext);
|
const { addText } = useContext(EncryptionContext);
|
||||||
106
src/contexts/Decryption.tsx
Normal file
106
src/contexts/Decryption.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import React, { useState, useCallback, useContext, createContext, useEffect } from 'react';
|
||||||
|
import * as openpgp from 'openpgp';
|
||||||
|
import GithubContext from './Github';
|
||||||
|
import FileType from '../types/File';
|
||||||
|
import { createFile } from '../helpers/files';
|
||||||
|
|
||||||
|
interface DecryptionContextType {
|
||||||
|
publicKey: string | undefined;
|
||||||
|
files: {[id: string]: FileType};
|
||||||
|
addFile: (file: File) => Promise<void>;
|
||||||
|
deleteFile: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DecryptionContext = createContext<DecryptionContextType>({
|
||||||
|
publicKey: undefined,
|
||||||
|
files: {},
|
||||||
|
addFile: async () => { throw new Error('Not using provider'); },
|
||||||
|
deleteFile: async () => { throw new Error('Not using provider'); },
|
||||||
|
});
|
||||||
|
|
||||||
|
const decrypt = async (privateKey: string, keys: string[], content: string) => {
|
||||||
|
const armoredKeys = await Promise.all(keys.map(openpgp.key.readArmored));
|
||||||
|
const message = openpgp.message.fromText(content);
|
||||||
|
const encrypted = await openpgp.decrypt({
|
||||||
|
message,
|
||||||
|
privateKeys: [...(await openpgp.key.readArmored(privateKey)).keys],
|
||||||
|
publicKeys: armoredKeys.reduce<any>((output, key: any) => [...output, ...key.keys], []),
|
||||||
|
});
|
||||||
|
const { data } = encrypted;
|
||||||
|
const blob = new Blob([data], {
|
||||||
|
type: 'text/text',
|
||||||
|
});
|
||||||
|
return blob;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DecryptionProvider: React.FC = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const { username, keys } = useContext(GithubContext);
|
||||||
|
const [privateKey, setPrivateKey] = useState<string | undefined>(undefined);
|
||||||
|
const [publicKey, setPublicKey] = useState<string | undefined>(undefined);
|
||||||
|
const [files, setFiles] = useState<DecryptionContextType['files']>({});
|
||||||
|
|
||||||
|
const deleteFile = useCallback((id: string) => {
|
||||||
|
delete files[id];
|
||||||
|
setFiles({
|
||||||
|
...files,
|
||||||
|
});
|
||||||
|
}, [files]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const run = async () => {
|
||||||
|
const currentRawKey = localStorage.getItem('key');
|
||||||
|
if (currentRawKey) {
|
||||||
|
setPrivateKey(currentRawKey);
|
||||||
|
const key = await openpgp.key.readArmored(currentRawKey);
|
||||||
|
setPublicKey(key.keys[0].toPublic().armor());
|
||||||
|
} else {
|
||||||
|
const key = await openpgp.generateKey({
|
||||||
|
userIds: [{ name: 'unknown unknown', email: 'unknown@unknown.foo'}],
|
||||||
|
curve: 'ed25519',
|
||||||
|
});
|
||||||
|
|
||||||
|
setPrivateKey(key.privateKeyArmored);
|
||||||
|
setPublicKey(key.publicKeyArmored);
|
||||||
|
localStorage.setItem('key', key.privateKeyArmored);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
run();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const addFile = useCallback(async (file: File) => {
|
||||||
|
if (!keys || !privateKey) return;
|
||||||
|
const addedFile = createFile(setFiles, file.name);
|
||||||
|
const reader = new FileReader()
|
||||||
|
|
||||||
|
reader.onabort = addedFile.setFailed,
|
||||||
|
reader.onerror = addedFile.setFailed,
|
||||||
|
reader.onload = () => {
|
||||||
|
addedFile.setContent(
|
||||||
|
decrypt(privateKey, keys, reader.result as string),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
reader.readAsText(file)
|
||||||
|
}, [keys, username]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DecryptionContext.Provider
|
||||||
|
value={{
|
||||||
|
publicKey,
|
||||||
|
files,
|
||||||
|
addFile,
|
||||||
|
deleteFile,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DecryptionContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
DecryptionProvider,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DecryptionContext;;
|
||||||
@@ -1,16 +1,8 @@
|
|||||||
import React, { useState, useCallback, useContext, createContext } from 'react';
|
import React, { useState, useCallback, useContext, createContext } from 'react';
|
||||||
import * as openpgp from 'openpgp';
|
import * as openpgp from 'openpgp';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { message } from 'antd';
|
|
||||||
import GithubContext from './Github';
|
import GithubContext from './Github';
|
||||||
|
import { createFile } from '../helpers/files';
|
||||||
export interface FileType {
|
import FileType from '../types/File';
|
||||||
name: string;
|
|
||||||
reciever: string;
|
|
||||||
status: 'encrypting' | 'failed' | 'encrypted';
|
|
||||||
error?: any;
|
|
||||||
link?: Blob;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EncryptionContextType {
|
interface EncryptionContextType {
|
||||||
files: {[id: string]: FileType};
|
files: {[id: string]: FileType};
|
||||||
@@ -55,74 +47,27 @@ const EncryptionProvider: React.FC = ({
|
|||||||
});
|
});
|
||||||
}, [files]);
|
}, [files]);
|
||||||
|
|
||||||
const add = (name: string) => {
|
|
||||||
const id = nanoid();
|
|
||||||
const file: FileType = {
|
|
||||||
name: `${name}.asc`,
|
|
||||||
reciever: username,
|
|
||||||
status: 'encrypting',
|
|
||||||
};
|
|
||||||
setFiles(files => ({
|
|
||||||
...files,
|
|
||||||
[id]: file,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const setError = (err: any) => {
|
|
||||||
console.error(err);
|
|
||||||
setFiles(files => ({
|
|
||||||
...files,
|
|
||||||
[id]: {
|
|
||||||
...files[id],
|
|
||||||
status: 'failed',
|
|
||||||
error: err,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
message.error(`Failed to encrypt ${name}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setContent = (text: string, keys: string[]) => {
|
|
||||||
const run = async () => {
|
|
||||||
try {
|
|
||||||
const encrypted = await encrypt(keys, text);
|
|
||||||
setFiles(files => ({
|
|
||||||
...files,
|
|
||||||
[id]: {
|
|
||||||
...files[id],
|
|
||||||
link: encrypted,
|
|
||||||
status: 'encrypted'
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
message.success(`Done encrypting ${name}`);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
run();
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
setContent,
|
|
||||||
setError,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const addFile = useCallback(async (file: File) => {
|
const addFile = useCallback(async (file: File) => {
|
||||||
if (!keys) return;
|
if (!keys) return;
|
||||||
const addedFile = add(file.name);
|
const addedFile = createFile(setFiles, `${file.name}.acs`);
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
|
|
||||||
reader.onabort = addedFile.setError,
|
reader.onabort = addedFile.setFailed,
|
||||||
reader.onerror = addedFile.setError,
|
reader.onerror = addedFile.setFailed,
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
addedFile.setContent(reader.result as string, keys);
|
addedFile.setContent(
|
||||||
|
encrypt(keys, reader.result as string),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
reader.readAsText(file)
|
reader.readAsText(file)
|
||||||
}, [keys, username]);
|
}, [keys, username]);
|
||||||
|
|
||||||
const addText = useCallback(async (text: string, name: string) => {
|
const addText = useCallback(async (text: string, name: string) => {
|
||||||
if (!keys) return;
|
if (!keys) return;
|
||||||
const file = add(`${name}.txt`);
|
const file = createFile(setFiles, `${name}.txt.asc`);
|
||||||
file.setContent(text, keys);
|
file.setContent(
|
||||||
|
encrypt(keys, text),
|
||||||
|
);
|
||||||
}, [keys, username]);
|
}, [keys, username]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import File from '../types/File';
|
||||||
|
|
||||||
export const downloadLink = (name: string, blob: Blob) => {
|
export const downloadLink = (name: string, blob: Blob) => {
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const downloadLink = document.createElement('a');
|
const downloadLink = document.createElement('a');
|
||||||
@@ -7,3 +10,49 @@ export const downloadLink = (name: string, blob: Blob) => {
|
|||||||
downloadLink.click();
|
downloadLink.click();
|
||||||
document.body.removeChild(downloadLink);
|
document.body.removeChild(downloadLink);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//type SetFilesType = (fn: (files: {[id: string]: File}) => {[id: string]: File}) => any;
|
||||||
|
type SetFilesType = any;
|
||||||
|
|
||||||
|
export const createFile = (setFiles: SetFilesType, name: string) => {
|
||||||
|
const id = nanoid();
|
||||||
|
const file: File = {
|
||||||
|
name,
|
||||||
|
status: 'processing',
|
||||||
|
};
|
||||||
|
setFiles((files) => ({
|
||||||
|
...files,
|
||||||
|
[id]: file,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const setContent = (input: Blob | Promise<Blob>) => {
|
||||||
|
Promise.resolve(input)
|
||||||
|
.then((blob) => {
|
||||||
|
setFiles((files) => ({
|
||||||
|
...files,
|
||||||
|
[id]: {
|
||||||
|
...files[id],
|
||||||
|
blob,
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.catch(setFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
const setFailed = (err: any) => {
|
||||||
|
setFiles((files) => ({
|
||||||
|
...files,
|
||||||
|
[id]: {
|
||||||
|
...files[id],
|
||||||
|
status: 'failed',
|
||||||
|
error: err,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
setContent,
|
||||||
|
setFailed,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ type Statuses = 'packing' | 'ready';
|
|||||||
const useDownloadAll = () => {
|
const useDownloadAll = () => {
|
||||||
const [status, setStatus] = useState<Statuses>('ready');
|
const [status, setStatus] = useState<Statuses>('ready');
|
||||||
const { files } = useContext(EncryptionContext);
|
const { files } = useContext(EncryptionContext);
|
||||||
const allFilesReady = Object.values(files).filter(f => f.link).length > 1;
|
const allFilesReady = Object.values(files).filter(f => f.status === 'success').length > 1;
|
||||||
|
|
||||||
const downloadAll = useCallback(() => {
|
const downloadAll = useCallback(() => {
|
||||||
setStatus('packing');
|
setStatus('packing');
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
const zip = new Zip();
|
const zip = new Zip();
|
||||||
Object.values(files).map((file) => {
|
Object.values(files).map((file) => {
|
||||||
zip.file(file.name, file.link!);
|
zip.file(file.name, file.blob!);
|
||||||
});
|
});
|
||||||
const link = await zip.generateAsync({ type: 'blob' });
|
const link = await zip.generateAsync({ type: 'blob' });
|
||||||
setStatus('ready');
|
setStatus('ready');
|
||||||
|
|||||||
44
src/screens/Decrypt.tsx
Normal file
44
src/screens/Decrypt.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import React, { useContext, useEffect, useCallback } from 'react';
|
||||||
|
import { Divider, Button } from 'antd';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import FileList from '../components/FileList';
|
||||||
|
import Add from '../components/decrypt/AddFile';
|
||||||
|
import DecryptionContext from '../contexts/Decryption';
|
||||||
|
import { downloadLink } from '../helpers/files';
|
||||||
|
|
||||||
|
const Decrypt: React.FC = () => {
|
||||||
|
const history = useHistory();
|
||||||
|
const { publicKey, files, deleteFile } = useContext(DecryptionContext);
|
||||||
|
useEffect(() => {
|
||||||
|
if (localStorage.getItem('welcome') !== 'seen') {
|
||||||
|
history.replace('/welcome');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const downloadPublicKey = useCallback(() => {
|
||||||
|
const publicKeyBlob = new Blob([publicKey!]);
|
||||||
|
downloadLink('public-key.asc', publicKeyBlob);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onClick={downloadPublicKey}
|
||||||
|
>
|
||||||
|
Download you sharing key
|
||||||
|
</Button>
|
||||||
|
<Add />
|
||||||
|
{Object.keys(files).length > 0 && (
|
||||||
|
<>
|
||||||
|
<Divider>Files</Divider>
|
||||||
|
<FileList
|
||||||
|
files={files}
|
||||||
|
deleteFile={deleteFile}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Decrypt;
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import React, { useContext, useEffect } from 'react';
|
import React, { useContext, useEffect } from 'react';
|
||||||
import { Divider } from 'antd';
|
import { Divider } from 'antd';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import Add from '../components/Add';
|
import Add from '../components/encrypt/Add';
|
||||||
import FileList from '../components/FileList';
|
import FileList from '../components/FileList';
|
||||||
import EncryptionContext from '../contexts/Encryption';
|
import EncryptionContext from '../contexts/Encryption';
|
||||||
|
|
||||||
const Encrypt: React.FC = () => {
|
const Encrypt: React.FC = () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { files } = useContext(EncryptionContext);
|
const { files, deleteFile } = useContext(EncryptionContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (localStorage.getItem('welcome') !== 'seen') {
|
if (localStorage.getItem('welcome') !== 'seen') {
|
||||||
history.replace('/welcome');
|
history.replace('/welcome');
|
||||||
@@ -20,7 +20,14 @@ const Encrypt: React.FC = () => {
|
|||||||
{Object.keys(files).length > 0 && (
|
{Object.keys(files).length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Divider>Files</Divider>
|
<Divider>Files</Divider>
|
||||||
<FileList />
|
<FileList
|
||||||
|
files={files}
|
||||||
|
deleteFile={deleteFile}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
<i style={{ textAlign: 'center', paddingTop: '10px', display: 'block', fontSize: 12 }}>
|
||||||
|
Note: files are not send to me, you still have to download the encrypted files and send it to me.
|
||||||
|
</i>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
24
src/types/File.ts
Normal file
24
src/types/File.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
interface FileBase {
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
blob?: Blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileProcessing extends FileBase {
|
||||||
|
name: string;
|
||||||
|
status: 'processing';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileFailed extends FileBase {
|
||||||
|
status: 'failed',
|
||||||
|
error: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileSuccess extends FileBase {
|
||||||
|
status: 'success';
|
||||||
|
blob: Blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileType = FileProcessing | FileFailed | FileSuccess;
|
||||||
|
|
||||||
|
export default FileType;
|
||||||
Reference in New Issue
Block a user