Skip to content

Commit ac9b30c

Browse files
committed
chore(website): uploa files through file input
1 parent 52d586a commit ac9b30c

File tree

6 files changed

+148
-79
lines changed

6 files changed

+148
-79
lines changed

components/ErrorHeader.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ export function ErrorHeader({ error }: ErrorHeaderProps): ReactElement {
4141
);
4242
}
4343

44+
const { extensions } = error;
4445
return (
4546
<Text type="subtitle-2" margin="none">
46-
Invalid file extension. Only non-minified text files are supported.
47+
{`Invalid file extension. Must be one of ${extensions.join(", ")}`}
4748
</Text>
4849
);
4950
}

components/FileDropzone.tsx

Lines changed: 24 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,19 @@
11
import cn from "classnames";
22
import { ReactElement, useEffect, useState } from "react";
3-
import {
4-
GetFileParser,
5-
getSplitFileUploads,
6-
useDropzone,
7-
useFileUpload,
8-
Text,
9-
TextContainer,
10-
FileUploadSVGIcon,
11-
} from "react-md";
3+
import { FileUploadSVGIcon, Text, TextContainer, useDropzone } from "react-md";
124

13-
import { ErrorModal } from "./ErrorModal";
145
import styles from "./FileDropzone.module.scss";
15-
import { usePlayground } from "./usePlayground";
6+
import { useUpload } from "./useUpload";
167

17-
const getFileParser: GetFileParser = () => "readAsText";
18-
const extensions = [
19-
"md",
20-
"txt",
21-
"js",
22-
"jsx",
23-
"ts",
24-
"tsx",
25-
"json",
26-
"yml",
27-
"yaml",
28-
] as const;
29-
30-
export function FileDropzone(): ReactElement {
31-
const { setMarkdown } = usePlayground();
8+
export function FileDropzone(): ReactElement | null {
9+
const { onDrop } = useUpload();
3210
const [enabled, setEnabled] = useState(false);
33-
const { onDrop, stats, reset, errors, clearErrors } = useFileUpload({
34-
maxFiles: 1,
35-
getFileParser,
36-
extensions,
37-
onDrop() {
11+
const [isOver, handlers] = useDropzone({
12+
onDrop(event) {
3813
setEnabled(false);
14+
onDrop(event);
3915
},
4016
});
41-
const [isOver, handlers] = useDropzone({
42-
onDrop,
43-
onDragEnter: reset,
44-
});
45-
46-
const { complete } = getSplitFileUploads(stats);
47-
const [current] = complete;
48-
const type = current?.file.type?.replace(/^.*\//, "");
49-
const fileContents = current?.result;
50-
useEffect(() => {
51-
if (typeof fileContents !== "string") {
52-
return;
53-
}
54-
55-
let contents = fileContents;
56-
if (type !== "markdown" && type) {
57-
contents = `\`\`\`${type}
58-
${fileContents}\`\`\`
59-
`;
60-
}
61-
62-
setMarkdown(contents);
63-
}, [fileContents, setMarkdown, type]);
6417

6518
useEffect(() => {
6619
if (enabled) {
@@ -77,24 +30,23 @@ ${fileContents}\`\`\`
7730
};
7831
}, [enabled]);
7932

33+
if (!enabled) {
34+
return null;
35+
}
36+
8037
return (
81-
<>
82-
<ErrorModal errors={errors} clearErrors={clearErrors} />
83-
{enabled && (
84-
<div
85-
{...handlers}
86-
className={cn(styles.container, isOver && styles.hovering)}
87-
onMouseLeave={() => setEnabled(false)}
88-
>
89-
<TextContainer>
90-
<Text type="headline-5">
91-
Drag and drop a text file to update the markdown text with the
92-
file contents.
93-
</Text>
94-
<FileUploadSVGIcon />
95-
</TextContainer>
96-
</div>
97-
)}
98-
</>
38+
<div
39+
{...handlers}
40+
className={cn(styles.container, isOver && styles.hovering)}
41+
onMouseLeave={() => setEnabled(false)}
42+
>
43+
<TextContainer>
44+
<Text type="headline-5">
45+
Drag and drop a text file to update the markdown text with the file
46+
contents.
47+
</Text>
48+
<FileUploadSVGIcon />
49+
</TextContainer>
50+
</div>
9951
);
10052
}

components/Header.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
AppBar,
44
AppBarTitle,
55
Brightness4SVGIcon,
6+
FileInput,
67
FilterFramesSVGIcon,
78
FilterNoneSVGIcon,
89
HelpOutlineSVGIcon,
@@ -15,6 +16,7 @@ import {
1516
import { AppBarAction } from "./AppBarAction";
1617
import { HelpDialog } from "./HelpDialog";
1718
import { usePlayground } from "./usePlayground";
19+
import { useUpload } from "./useUpload";
1820

1921
export function Header(): ReactElement {
2022
const [visible, setVisible] = useState(false);
@@ -29,6 +31,7 @@ export function Header(): ReactElement {
2931
toggleSplitView,
3032
toggleCustomRenderers,
3133
} = usePlayground();
34+
const { accept, onChange } = useUpload();
3235

3336
return (
3437
<AppBar
@@ -48,6 +51,14 @@ export function Header(): ReactElement {
4851
>
4952
{splitView ? <SettingsOverscanSVGIcon /> : <ViewColumnSVGIcon />}
5053
</AppBarAction>
54+
<FileInput
55+
id="file-uploa"
56+
accept={accept}
57+
onChange={onChange}
58+
theme="clear"
59+
themeType="flat"
60+
style={{ flexShrink: 0 }}
61+
/>
5162
<AppBarAction
5263
aria-label="Custom Renderers"
5364
aria-pressed={customRenderers}

components/MarkdownEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function MarkdownEditor(): ReactElement {
1818
name="editor"
1919
theme="none"
2020
value={markdown}
21-
placeholder="# Enter some parkdown here!"
21+
placeholder="# Enter some markdown here!"
2222
className={styles.editor}
2323
resize="none"
2424
animate={false}

components/Playground.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ import { FileDropzone } from "./FileDropzone";
55
import { Header } from "./Header";
66
import { PlaygroundView } from "./PlaygroundView";
77
import { PlaygroundProvider } from "./usePlayground";
8+
import { UploadProvider } from "./useUpload";
89

910
const tabs = ["Editor", "Preview"];
1011

1112
export default function Playground(): ReactElement {
1213
return (
1314
<PlaygroundProvider>
14-
<TabsManager tabs={tabs} tabsId="editor-tabs">
15-
<Header />
16-
<FileDropzone />
17-
<PlaygroundView />
18-
</TabsManager>
15+
<UploadProvider>
16+
<TabsManager tabs={tabs} tabsId="editor-tabs">
17+
<Header />
18+
<FileDropzone />
19+
<PlaygroundView />
20+
</TabsManager>
21+
</UploadProvider>
1922
</PlaygroundProvider>
2023
);
2124
}

components/useUpload.tsx

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, {
2+
createContext,
3+
ReactElement,
4+
ReactNode,
5+
useContext,
6+
useEffect,
7+
useMemo,
8+
} from "react";
9+
import {
10+
FileUploadHookReturnValue,
11+
GetFileParser,
12+
getSplitFileUploads,
13+
useFileUpload,
14+
} from "react-md";
15+
import { ErrorModal } from "./ErrorModal";
16+
import { usePlayground } from "./usePlayground";
17+
18+
type UploadContext = Pick<
19+
FileUploadHookReturnValue,
20+
"onDrop" | "onChange" | "accept"
21+
>;
22+
23+
const noop = (): void => {
24+
// do nothing
25+
};
26+
27+
const context = createContext<UploadContext>({
28+
onDrop: noop,
29+
onChange: noop,
30+
accept: "",
31+
});
32+
context.displayName = "Upload";
33+
const { Provider } = context;
34+
35+
export function useUpload(): Readonly<UploadContext> {
36+
return useContext(context);
37+
}
38+
39+
const getFileParser: GetFileParser = () => "readAsText";
40+
const extensions = [
41+
"md",
42+
"txt",
43+
"js",
44+
"jsx",
45+
"ts",
46+
"tsx",
47+
"json",
48+
"yml",
49+
"yaml",
50+
"html",
51+
] as const;
52+
53+
export function UploadProvider({
54+
children,
55+
}: {
56+
children: ReactNode;
57+
}): ReactElement {
58+
const { setMarkdown } = usePlayground();
59+
const { onDrop, stats, reset, errors, clearErrors, accept, onChange } =
60+
useFileUpload({
61+
maxFiles: 1,
62+
getFileParser,
63+
extensions,
64+
});
65+
const { complete } = getSplitFileUploads(stats);
66+
const [current] = complete;
67+
const type = current?.file.type?.replace(/^.*\//, "");
68+
const fileContents = current?.result;
69+
useEffect(() => {
70+
if (typeof fileContents !== "string") {
71+
return;
72+
}
73+
74+
let contents = fileContents;
75+
if (type !== "markdown" && type) {
76+
contents = `\`\`\`${type}
77+
${fileContents}\`\`\`
78+
`;
79+
}
80+
81+
setMarkdown(contents);
82+
reset();
83+
// TODO: Add reset to dependency array after next release
84+
// eslint-disable-next-line react-hooks/exhaustive-deps
85+
}, [fileContents, setMarkdown, type]);
86+
87+
return (
88+
<Provider
89+
value={useMemo(
90+
() => ({
91+
onDrop,
92+
accept,
93+
onChange,
94+
}),
95+
[accept, onChange, onDrop]
96+
)}
97+
>
98+
<ErrorModal errors={errors} clearErrors={clearErrors} />
99+
{children}
100+
</Provider>
101+
);
102+
}

0 commit comments

Comments
 (0)