-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#228 Better dropzone styling + Add file drag & drop to folders
- Loading branch information
Showing
7 changed files
with
282 additions
and
73 deletions.
There are no files selected for viewing
104 changes: 104 additions & 0 deletions
104
data-browser/src/components/forms/FileDropzone/FileDropzone.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { Resource } from '@tomic/react'; | ||
import React, { useCallback, useEffect } from 'react'; | ||
import { useDropzone } from 'react-dropzone'; | ||
import { FaUpload } from 'react-icons/fa'; | ||
import styled, { keyframes } from 'styled-components'; | ||
import { ErrMessage } from '../InputStyles'; | ||
import { useUpload } from './useUpload'; | ||
|
||
export interface FileDropZoneProps { | ||
parentResource: Resource; | ||
onFilesUploaded?: (files: string[]) => void; | ||
} | ||
|
||
/** | ||
* A dropzone for adding files. Renders its children by default, unless you're | ||
* holding a file, an error occurred, or it's uploading. | ||
*/ | ||
export function FileDropZone({ | ||
parentResource, | ||
children, | ||
onFilesUploaded, | ||
}: React.PropsWithChildren<FileDropZoneProps>): JSX.Element { | ||
const { upload, isUploading, error } = useUpload(parentResource); | ||
const dropzoneRef = React.useRef<HTMLDivElement>(null); | ||
const onDrop = useCallback( | ||
async (files: File[]) => { | ||
const uploaded = await upload(files); | ||
onFilesUploaded?.(uploaded); | ||
}, | ||
[upload], | ||
); | ||
|
||
const { getRootProps, isDragActive } = useDropzone({ onDrop }); | ||
|
||
// Move the dropzone down if the user has scrolled down. | ||
useEffect(() => { | ||
if (isDragActive && dropzoneRef.current) { | ||
const rect = dropzoneRef.current.getBoundingClientRect(); | ||
|
||
if (rect.top < 0) { | ||
dropzoneRef.current.style.top = `calc(${Math.abs(rect.top)}px + 1rem)`; | ||
} | ||
} | ||
}, [isDragActive]); | ||
|
||
return ( | ||
<Root | ||
{...getRootProps()} | ||
// For some reason this is tabbable by default, but it does not seem to actually help users. | ||
// Let's disable it. | ||
tabIndex={-1} | ||
> | ||
{isUploading && <p>{'Uploading...'}</p>} | ||
{error && <ErrMessage>{error.message}</ErrMessage>} | ||
{children} | ||
{isDragActive && ( | ||
<VisualDropzone ref={dropzoneRef}> | ||
<TextWrapper> | ||
<FaUpload /> Drop files here to upload. | ||
</TextWrapper> | ||
</VisualDropzone> | ||
)} | ||
</Root> | ||
); | ||
} | ||
|
||
const Root = styled.div` | ||
height: 100%; | ||
position: relative; | ||
`; | ||
|
||
const fadeIn = keyframes` | ||
from { | ||
opacity: 0; | ||
backdrop-filter: blur(0px); | ||
} | ||
to { | ||
opacity: 1; | ||
backdrop-filter: blur(10px); | ||
} | ||
`; | ||
|
||
const VisualDropzone = styled.div` | ||
position: absolute; | ||
inset: 0; | ||
height: 90vh; | ||
background-color: ${p => | ||
p.theme.darkMode ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)'}; | ||
backdrop-filter: blur(10px); | ||
border: 3px dashed ${p => p.theme.colors.textLight}; | ||
border-radius: ${p => p.theme.radius}; | ||
display: grid; | ||
place-items: center; | ||
font-size: 1.8rem; | ||
color: ${p => p.theme.colors.textLight}; | ||
animation: 0.1s ${fadeIn} ease-in; | ||
`; | ||
|
||
const TextWrapper = styled.div` | ||
display: flex; | ||
align-items: center; | ||
gap: 1rem; | ||
padding: ${p => p.theme.margin}rem; | ||
`; |
75 changes: 75 additions & 0 deletions
75
data-browser/src/components/forms/FileDropzone/FileDropzoneInput.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { Resource } from '@tomic/react'; | ||
import React, { useCallback } from 'react'; | ||
import { useDropzone } from 'react-dropzone'; | ||
import { FaUpload } from 'react-icons/fa'; | ||
import styled from 'styled-components'; | ||
import { ErrMessage } from '../InputStyles'; | ||
import { useUpload } from './useUpload'; | ||
|
||
export interface FileDropzoneInputProps { | ||
parentResource: Resource; | ||
onFilesUploaded?: (files: string[]) => void; | ||
} | ||
|
||
/** | ||
* A dropzone for adding files. Renders its children by default, unless you're | ||
* holding a file, an error occurred, or it's uploading. | ||
*/ | ||
export function FileDropzoneInput({ | ||
parentResource, | ||
onFilesUploaded, | ||
}: FileDropzoneInputProps): JSX.Element { | ||
const { upload, isUploading, error } = useUpload(parentResource); | ||
|
||
const onFileSelect = useCallback( | ||
async (files: File[]) => { | ||
const uploaded = await upload(files); | ||
onFilesUploaded?.(uploaded); | ||
}, | ||
[upload], | ||
); | ||
|
||
const { getRootProps, getInputProps } = useDropzone({ | ||
onDrop: onFileSelect, | ||
}); | ||
|
||
return ( | ||
<> | ||
<VisualDropZone {...getRootProps()}> | ||
{error && <ErrMessage>{error.message}</ErrMessage>} | ||
<input {...getInputProps()} /> | ||
<TextWrapper> | ||
<FaUpload />{' '} | ||
{isUploading ? 'Uploading...' : 'Drop files or click here to upload.'} | ||
</TextWrapper> | ||
</VisualDropZone> | ||
</> | ||
); | ||
} | ||
|
||
const VisualDropZone = styled.div` | ||
background-color: ${p => | ||
p.theme.darkMode ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)'}; | ||
backdrop-filter: blur(10px); | ||
border: 2px dashed ${p => p.theme.colors.bg2}; | ||
border-radius: ${p => p.theme.radius}; | ||
display: grid; | ||
place-items: center; | ||
font-size: 1.3rem; | ||
color: ${p => p.theme.colors.textLight}; | ||
min-height: 10rem; | ||
cursor: pointer; | ||
&:hover, | ||
&focus { | ||
color: ${p => p.theme.colors.main}; | ||
border-color: ${p => p.theme.colors.main}; | ||
} | ||
`; | ||
|
||
const TextWrapper = styled.div` | ||
display: flex; | ||
align-items: center; | ||
padding: ${p => p.theme.margin}rem; | ||
gap: 1rem; | ||
`; |
56 changes: 56 additions & 0 deletions
56
data-browser/src/components/forms/FileDropzone/useUpload.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { | ||
properties, | ||
Resource, | ||
uploadFiles, | ||
useArray, | ||
useStore, | ||
} from '@tomic/react'; | ||
import { useCallback, useState } from 'react'; | ||
|
||
export interface UseUploadResult { | ||
/** Uploads files to the upload endpoint and returns the created subjects. */ | ||
upload: (acceptedFiles: File[]) => Promise<string[]>; | ||
isUploading: boolean; | ||
error: Error | undefined; | ||
} | ||
|
||
export function useUpload(parentResource: Resource): UseUploadResult { | ||
const store = useStore(); | ||
const [isUploading, setIsUploading] = useState(false); | ||
const [error, setError] = useState<Error | undefined>(undefined); | ||
const [subResources, setSubResources] = useArray( | ||
parentResource, | ||
properties.subResources, | ||
); | ||
|
||
const upload = useCallback( | ||
async (acceptedFiles: File[]) => { | ||
try { | ||
setError(undefined); | ||
setIsUploading(true); | ||
const netUploaded = await uploadFiles( | ||
acceptedFiles, | ||
store, | ||
parentResource.getSubject(), | ||
); | ||
const allUploaded = [...netUploaded]; | ||
setIsUploading(false); | ||
setSubResources([...subResources, ...allUploaded]); | ||
|
||
return allUploaded; | ||
} catch (e) { | ||
setError(e); | ||
setIsUploading(false); | ||
|
||
return []; | ||
} | ||
}, | ||
[parentResource], | ||
); | ||
|
||
return { | ||
upload, | ||
isUploading, | ||
error, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.