Skip to content

Commit

Permalink
replace old import with the new scaffolding
Browse files Browse the repository at this point in the history
  • Loading branch information
insmac committed Oct 31, 2023
1 parent ab7351f commit 53395d3
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 13 deletions.
157 changes: 157 additions & 0 deletions packages/web-console/src/modules/Import/dropbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import React, { useContext, useEffect, useRef, useState } from "react"
import styled from "styled-components"
import { Search2 } from "@styled-icons/remix-line"
import { ImportContext } from "./import-file"
import { Text } from "../../components"
import { Button, Box, Heading } from "@questdb/react-components"
import { QuestContext } from "../../providers"

const Root = styled(Box).attrs({ flexDirection: "column" })<{
isDragging: boolean
}>`
width: 100%;
flex: 1;
padding: 4rem 0 0;
gap: 2rem;
background: ${({ theme }) => theme.color.backgroundLighter};
border: 3px dashed ${({ isDragging }) => (isDragging ? "#7f839b" : "#333543")};
box-shadow: inset 0 0 10px 0 #1b1c23;
transition: all 0.15s ease-in-out;
`

const Actions = styled(Box).attrs({ flexDirection: "column" })`
margin: auto;
`

const Caution = styled.div`
margin-top: auto;
padding: 2rem;
width: 100%;
background: ${({ theme }) => theme.color.backgroundDarker};
text-align: center;
`

const CautionText = styled(Text)`
color: #8b8fa7;
a {
color: ${({ theme }) => theme.color.foreground};
}
`
// 25MB
const CHUNK_BYTE_OFFSET= 2.5e+7

const getFileChunk = (file: File, byteOffset: number) => {
return new Promise((resolve, reject) => {
const timeStart = performance.now()
const reader = new FileReader()
reader.onload = () => {
const timeEnd = performance.now()
console.info(`${file.name} split time: ${timeEnd - timeStart}ms`)
resolve(reader.result)
}
reader.onerror = reject
reader.readAsArrayBuffer(file.slice(0, byteOffset))
})
}

export const DropBox = () => {
const { dispatch } = useContext(ImportContext)
const { quest } = useContext(QuestContext)
const [isDragging, setIsDragging] = useState(false)
const uploadInputRef = useRef<HTMLInputElement | null>(null)

const handleDrag = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault()
e.stopPropagation()
setIsDragging(e.type === "dragenter" || e.type === "dragover")
}

const handleDrop = async (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault()
e.stopPropagation()
setIsDragging(false)
await onFileAdded(Array.from(e.dataTransfer.files)[0])
}

const handlePaste = async (event: Event) => {
const clipboardEvent = event as ClipboardEvent
const clipboardFiles = clipboardEvent.clipboardData?.files
if (clipboardFiles) {
await onFileAdded(Array.from(clipboardFiles)[0])
}
}

const onFileAdded = async (file: File) => {
const fileChunk =
file.size > CHUNK_BYTE_OFFSET
? await (async () => {
const chunk = await getFileChunk(file, CHUNK_BYTE_OFFSET)
return new File([chunk as ArrayBuffer], file.name) as File
})()
: file
dispatch({ step: "settings", file, fileChunk })
}

useEffect(() => {
window.addEventListener("paste", handlePaste)

return () => {
window.removeEventListener("paste", handlePaste)
}
}, [])

return (
<Root
onDragEnter={handleDrag}
onDragOver={handleDrag}
onDragLeave={handleDrag}
onDrop={handleDrop}
isDragging={isDragging}
>
<Actions>
<img
alt="File upload icon"
width="60"
height="80"
src="/assets/upload.svg"
/>
<Heading level={3}>Drag CSV files here or paste from clipboard</Heading>
<input
type="file"
id="file"
onChange={(e) => {
if (e.target.files === null) return
onFileAdded(Array.from(e.target.files)[0])
}}
ref={uploadInputRef}
style={{ display: "none" }}
value=""
/>
<Button
onClick={() => {
uploadInputRef.current?.click()
}}
prefixIcon={<Search2 size="18px" />}
skin="secondary"
>
Browse from disk
</Button>
</Actions>
<Caution>
<CautionText>
Suitable for small batches of CSV file upload. For database
migrations, we recommend the{" "}
<a
href="https://questdb.io/docs/guides/importing-data"
target="_blank"
rel="noopener noreferrer"
>
COPY SQL
</a>{" "}
command.
</CautionText>
</Caution>
</Root>
)
}
38 changes: 38 additions & 0 deletions packages/web-console/src/modules/Import/import-file.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { createContext, useReducer } from "react"
import { DropBox } from "./dropbox"
import { Settings } from "./settings"
import { Result } from "./result"

type State = {
step: "dropbox" | "settings" | "result"
file?: File
fileChunk?: File
}

const initialState: State = {
step: "dropbox",
file: undefined,
fileChunk: undefined,
}

export const ImportContext = createContext({
state: initialState,
dispatch: (action: Partial<State>) => {},
})

const reducer = (state: typeof initialState, action: Partial<State>) => ({
...state,
...action,
})

export const ImportFile = () => {
const [state, dispatch] = useReducer(reducer, initialState)

return (
<ImportContext.Provider value={{ state, dispatch }}>
{state.step === "dropbox" && <DropBox />}
{state.step === "settings" && <Settings />}
{state.step === "result" && <Result />}
</ImportContext.Provider>
)
}
5 changes: 5 additions & 0 deletions packages/web-console/src/modules/Import/result.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from "react"

export const Result = () => {
return <div>upload result</div>
}
12 changes: 12 additions & 0 deletions packages/web-console/src/modules/Import/settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React, { useContext } from "react"
import { ImportContext } from "./import-file"

export const Settings = () => {
const { state, dispatch } = useContext(ImportContext)

return (
<span onClick={() => dispatch({ step: "result" })}>
Settings for the chunk: {state.fileChunk?.name}
</span>
)
}
15 changes: 2 additions & 13 deletions packages/web-console/src/scenes/Console/import.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
import React from "react"
import { PaneContent, PaneWrapper } from "../../components"
import { ImportCSVFiles } from "../Import/ImportCSVFiles"
import { BusEvent } from "../../consts"
import { ImportFile } from "../../modules/Import/import-file"

export const Import = () => (
<PaneWrapper>
<PaneContent>
<ImportCSVFiles
onImported={(result) => {
if (result.status === "OK") {
bus.trigger(BusEvent.MSG_QUERY_SCHEMA)
bus.trigger(BusEvent.MSG_QUERY_FIND_N_EXEC, {
query: `"${result.location}"`,
options: { appendAt: "end" },
})
}
}}
/>
<ImportFile />
</PaneContent>
</PaneWrapper>
)

0 comments on commit 53395d3

Please sign in to comment.