Skip to content

Commit

Permalink
Merge pull request #2855 from bakdata/allow-copy-paste-for-editor-for…
Browse files Browse the repository at this point in the history
…ms-and-history-followup

Also allow dropping a file within the Import Modal as well
  • Loading branch information
Kadrian authored Dec 13, 2022
2 parents 27b163b + 7f0a5f9 commit 1a91b1e
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 22 deletions.
1 change: 1 addition & 0 deletions frontend/src/js/button/SelectFileButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const SelectFileButton = styled(BasicButton)`
font-size: ${({ theme }) => theme.font.tiny};
display: flex;
align-items: center;
gap: 5px;
&:hover {
text-decoration: underline;
Expand Down
1 change: 1 addition & 0 deletions frontend/src/js/modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const Headline = styled("h3")`

const Subtitle = styled(`p`)`
margin: -15px 0 20px;
max-width: 600px;
`;

const ModalContent: FC<{ onClose: () => void; scrollable?: boolean }> = ({
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/js/ui-components/DropzoneWithFileInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ const DropzoneWithFileInput = <
)}
{showImportButton && onImportLines && (
<SxSelectFileButton onClick={() => setImportModalOpen(true)}>
<SxFaIcon icon="file" regular gray />
<SxFaIcon icon="file-import" gray />
{t("common.import")}
</SxSelectFileButton>
)}
Expand Down
137 changes: 119 additions & 18 deletions frontend/src/js/ui-components/ImportModal.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import styled from "@emotion/styled";
import { MouseEvent, useState } from "react";
import { ChangeEvent, MouseEvent, useEffect, useRef, useState } from "react";
import { NativeTypes } from "react-dnd-html5-backend";
import { createPortal } from "react-dom";
import { useTranslation } from "react-i18next";

import IconButton from "../button/IconButton";
import PrimaryButton from "../button/PrimaryButton";
import { getUniqueFileRows } from "../common/helpers/fileHelper";
import Modal from "../modal/Modal";

import DropzoneWithFileInput, { DragItemFile } from "./DropzoneWithFileInput";

const Content = styled("div")`
display: flex;
flex-direction: column;
Expand All @@ -23,6 +28,31 @@ const Textarea = styled("textarea")`
width: 100%;
`;

const HiddenFileInput = styled("input")`
display: none;
`;

const acceptedDropTypes = [NativeTypes.FILE];

const useCanReadClipboard = () => {
const [canReadClipboard, setCanReadClipboard] = useState(false);
useEffect(() => {
const checkPersmission = async () => {
const { state } = await navigator.permissions.query({
// @ts-ignore https://github.com/microsoft/TypeScript/issues/33923
name: "clipboard-read",
});

if (state === "granted" || state === "prompt") {
setCanReadClipboard(true);
}
};
checkPersmission();
}, []);

return canReadClipboard;
};

export const ImportModal = ({
onClose,
onSubmit,
Expand All @@ -32,15 +62,9 @@ export const ImportModal = ({
}) => {
const { t } = useTranslation();
const [textInput, setTextInput] = useState("");
const canReadClipboard = useCanReadClipboard();

const onPasteClick = async () => {
if (navigator.clipboard) {
const text = await navigator.clipboard.readText();
const lines = text.split("\n").map((line) => line.trim());

setTextInput(lines.join("\n"));
}
};
const fileInputRef = useRef<HTMLInputElement>(null);

const onSubmitClick = (
e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>,
Expand All @@ -53,30 +77,107 @@ export const ImportModal = ({
onClose();
};

return (
const onOpenFileDialog = () => {
if (fileInputRef.current) {
fileInputRef.current.click();
}
};

const autoFormatAndSet = (text: string) => {
let delimiter = "\n";

if (text.includes(";")) {
delimiter = ";";
} else if (text.includes(",")) {
delimiter = ",";
}

const formatted = text
.split(delimiter)
.map((part) => part.trim())
.join("\n");

setTextInput(formatted);
};

const onSelectFile = async (file: File) => {
const rows = await getUniqueFileRows(file);

autoFormatAndSet(rows.join("\n"));
};

const onPasteClick = async () => {
if (navigator.clipboard) {
const text = await navigator.clipboard.readText();

autoFormatAndSet(text);
}
};

const onDrop = async ({ files }: DragItemFile) => {
const file = files[0];
onSelectFile(file);
};

const onChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
const val = e.target.value;

// If the user is regularly typing or deleting, don't auto format
// but if the user is pasting, auto format
if (val.length - textInput.length < 3) {
setTextInput(val);
} else {
autoFormatAndSet(val);
}
};

return createPortal(
<Modal
headline={t("importModal.headline")}
subtitle={t("importModal.subtitle")}
onClose={onClose}
>
<Content>
<Textarea
rows={15}
value={textInput}
onChange={(e) => setTextInput(e.target.value)}
/>
<DropzoneWithFileInput
onDrop={onDrop}
acceptedDropTypes={acceptedDropTypes}
disableClick
accept="text/plain,text/csv"
>
{() => <Textarea rows={15} value={textInput} onChange={onChange} />}
</DropzoneWithFileInput>
<Row>
<IconButton icon="paste" onClick={onPasteClick}>
{t("importModal.paste")}
<IconButton icon="file" onClick={onOpenFileDialog}>
{t("common.openFileDialog")}
</IconButton>
{canReadClipboard && (
<IconButton icon="paste" onClick={onPasteClick}>
{t("importModal.paste")}
</IconButton>
)}
<PrimaryButton
disabled={textInput.length === 0}
onClick={onSubmitClick}
>
{t("importModal.submit")}
</PrimaryButton>
</Row>
<HiddenFileInput
type="file"
ref={fileInputRef}
accept="text/plain,text/csv"
onChange={(e) => {
if (e.target.files) {
onSelectFile(e.target.files[0]);
}

if (fileInputRef.current) {
fileInputRef.current.value = "";
}
}}
/>
</Content>
</Modal>
</Modal>,
document.getElementById("root")!,
);
};
5 changes: 3 additions & 2 deletions frontend/src/localization/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@
"logout": "Ausloggen",
"dateInvalid": "Ungültiges Datum",
"missingLabel": "Unbenannt",
"import": "Importieren"
"import": "Importieren",
"openFileDialog": "Datei auswählen"
},
"tooltip": {
"headline": "Info",
Expand Down Expand Up @@ -491,7 +492,7 @@
},
"importModal": {
"headline": "Zeilenweise Importieren",
"subtitle": "Alternativ zu Drag & Drop können zeilenweise Werte importiert werden, zum Beispiel aus der Zwischenablage.",
"subtitle": "Neben Drag & Drop können Werte zeilenweise importiert werden, zum Beispiel aus der Zwischenablage (Kopieren und Einfügen STRG+C / STRG+V).",
"paste": "Aus Zwischenablage einfügen",
"submit": "Übernehmen",
"pasted": "Importiert"
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/localization/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@
"logout": "Logout",
"dateInvalid": "Invalid date",
"missingLabel": "Unknown",
"import": "Import"
"import": "Import",
"openFileDialog": "Select file"
},
"tooltip": {
"headline": "Info",
Expand Down

0 comments on commit 1a91b1e

Please sign in to comment.