Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Support drag file to open #33

Merged
merged 7 commits into from
Sep 2, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { useRef, useState } from "react";
import { useEditor } from "~/src/components/editor/state/state";
import { useFile } from "~/src/components/file/state";
import { Layout } from "~/src/components/layout/layout";
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 +24,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 +44,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