Skip to content

Commit

Permalink
Merge pull request #33 from vimdotmd/feature/drop-file
Browse files Browse the repository at this point in the history
Feat: Support drag file to open
  • Loading branch information
Thien Do authored Sep 2, 2021
2 parents 84ec809 + a0c58ee commit 30a37e8
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 10 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/node_modules
/.parcel-cache
/dist

.DS_Store

.vercel
8 changes: 5 additions & 3 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
node_modules/
dist/
public/
/node_modules
/.parcel-cache
/dist
.DS_Store
.vercel
6 changes: 5 additions & 1 deletion src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { useLayout } from "~/src/components/layout/state";
import { Toolbar } from "~/src/components/toolbar/toolbar";
import { usePrefs } from "~src/components/prefs/state";
import s from "./app.module.css";
import { AppDrop } from "./drop/drop";
import { useAppDrop } from "./drop/state";
import { useEditorTheme } from "./state/editor-theme";
import { useFileDirty } from "./state/file-dirty";
import { useFileLoad } from "./state/file-load";
Expand All @@ -21,9 +23,10 @@ export const App = (): JSX.Element => {
useFileLoad({ editor, file });
const toolbar = useToolbarAutohide({ editor });
useEditorTheme({ editor, prefs });
const drop = useAppDrop({ file });

return (
<div className={s.app}>
<div className={s.app} {...drop.handlers}>
<AppTitle file={file} />
<div
className={[s.toolbar, toolbar.mute ? s.muted : ""].join(" ")}
Expand All @@ -40,6 +43,7 @@ export const App = (): JSX.Element => {
<div className={s.body}>
<Layout layout={layout.value} editor={editor} />
</div>
{drop.dragging && <AppDrop />}
</div>
);
};
16 changes: 16 additions & 0 deletions src/app/drop/drop.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.container {
position: absolute;
z-index: 1000;
top: 0;
left: 0;
width: 100%;
height: 100%;

background-color: rgba(var(--bg-color-rgb), 0.5);
backdrop-filter: blur(20px);

display: grid;
place-items: center;

pointer-events: none;
}
7 changes: 7 additions & 0 deletions src/app/drop/drop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import s from "./drop.module.css";

export const AppDrop = (): JSX.Element => (
<div className={s.container}>
<p className={s.body}>Drop file here to open</p>
</div>
);
66 changes: 66 additions & 0 deletions src/app/drop/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as React from "react";
import { FileState } from "~src/components/file/state";

interface AppDropState {
dragging: boolean;
handlers: Pick<
React.DOMAttributes<HTMLDivElement>,
"onDrop" | "onDragOver" | "onDragEnter" | "onDragLeave"
>;
}

interface Params {
file: FileState;
}

export const useAppDrop = (params: Params): AppDropState => {
// Keep a counter to reliably detect when the drag is still there. We can
// not mute pointer-events on App's children (editor and toolbar).
// https://stackoverflow.com/a/21002544
const counter = React.useRef(0);
// Since ref does not trigger re-render, we still need a state, but we will
// only set this state when counter is changed between 0 and 1 to avoid
// unnecessary re-renders
const [dragging, setDragging] = React.useState(false);

const onDrop = async (event: React.DragEvent) => {
event.preventDefault();
setDragging(false);
counter.current = 0;

const { items } = event.dataTransfer;
if (1 < items.length || items[0].kind !== "file")
throw Error("Only a single file upload is supported.");

const file = await items[0].getAsFileSystemHandle();
if (file) params.file.setFile(file as FileSystemFileHandle);
};

const onDragOver = (event: React.DragEvent): void => {
// Specifying this as a drop target
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#droptargets
event.preventDefault();
};

const onDragEnter = (event: React.DragEvent): void => {
// Specifying this as a drop target
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#droptargets
event.preventDefault();
if (counter.current === 0) setDragging(true);
counter.current = counter.current + 1;
};

const onDragLeave = (_event: React.DragEvent): void => {
if (counter.current === 1) setDragging(false);
counter.current = counter.current - 1;
};

const handlers: AppDropState["handlers"] = {
onDrop,
onDragEnter,
onDragLeave,
onDragOver,
};

return { handlers, dragging };
};
10 changes: 8 additions & 2 deletions src/components/file/state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { SetState } from "~src/utils/state/type";
import { set } from "idb-keyval";

Expand All @@ -9,6 +9,7 @@ export interface FileState {
setHandle: SetState<FileHandle | null>;
dirty: boolean;
setDirty: SetState<boolean>;
setFile: (handle: FileHandle | null) => void;
}

const useSaveHandle = (handle: FileHandle | null): void => {
Expand All @@ -23,5 +24,10 @@ export const useFile = (): FileState => {

useSaveHandle(handle);

return { handle, setHandle, dirty, setDirty };
const setFile = useCallback((handle: FileHandle | null): void => {
setHandle(handle);
setDirty(false);
}, []);

return { handle, setHandle, dirty, setDirty, setFile };
};
3 changes: 1 addition & 2 deletions src/components/toolbar/open.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ interface Props {
}

const setFile = async (props: Props, handle: FileHandle): Promise<void> => {
props.file.setHandle(handle);
props.file.setDirty(false);
props.file.setFile(handle);
};

const openFile = async (props: Props): Promise<void> => {
Expand Down

0 comments on commit 30a37e8

Please sign in to comment.