Skip to content

Commit

Permalink
File import in progress...
Browse files Browse the repository at this point in the history
  • Loading branch information
thsparks committed Feb 24, 2024
1 parent cd8edad commit 288ecb2
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 69 deletions.
66 changes: 66 additions & 0 deletions teachertool/src/components/DragAndDropFileSurface.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { classList } from "react-common/components/util";
import { Strings } from "../constants";
import { NoticeLabel } from "./NoticeLabel";
import { useState } from "react";
import css from "./styling/DragAndDropFileSurface.module.scss";

export interface DragAndDropFileSurfaceProps {
onFileDroppedAsync: (file: File) => void;
errorMessage?: string;
}
export const DragAndDropFileSurface: React.FC<DragAndDropFileSurfaceProps> = ({ onFileDroppedAsync, errorMessage }) => {
const [fileIsOverSurface, setFileIsOverSurface] = useState(false);

function handleDragOver(event: React.DragEvent<HTMLDivElement>) {
// Stop the browser from intercepting the file.
event.preventDefault();
}

function handleDragEnter(event: React.DragEvent<HTMLDivElement>) {
event.preventDefault();
setFileIsOverSurface(true);
}

function handleDragLeave(event: React.DragEvent<HTMLDivElement>) {
event.preventDefault();
setFileIsOverSurface(false);
}

function handleDrop(event: React.DragEvent<HTMLDivElement>) {
event.preventDefault();

setFileIsOverSurface(false);

const file = event.dataTransfer.files[0];
if (file) {
onFileDroppedAsync(file);
}
}

return (
<div className={css["drag-and-drop-file-surface"]}>
<div className={css["instruction-container"]}>
<i className={classList("fas fa-file-upload", css["upload-icon"])}></i>
<div className="no-select">{fileIsOverSurface ? Strings.ReleaseToUpload : Strings.DragAndDrop}</div>
</div>

{errorMessage && (
<div className={css["error-label-container"]}>
<NoticeLabel severity="error">{errorMessage}</NoticeLabel>
</div>
)}

{/*
Use a transparent div over everything to detect drop events.
We can't use drag-and-drop-file-surface directly because child elements interfere with the drag events.
*/}
<div
className={css["droppable-surface"]}
onDrop={handleDrop}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
/>
</div>
);
};
90 changes: 22 additions & 68 deletions teachertool/src/components/ImportRubricModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,96 +2,50 @@ import { useContext, useEffect, useState } from "react";
import { AppStateContext } from "../state/appStateContext";
import { Modal } from "react-common/components/controls/Modal";
import { hideModal } from "../transforms/hideModal";
import css from "./styling/ImportRubricModal.module.scss";
import { getRubricFromFileAsync } from "../transforms/getRubricFromFileAsync";
import { NoticeLabel } from "./NoticeLabel";
import { Rubric } from "../types/rubric";
import { RubricPreview } from "./RubricPreview";
import { setRubric } from "../transforms/setRubric";
import { DragAndDropFileSurface } from "./DragAndDropFileSurface";
import { Strings } from "../constants";
import css from "./styling/ImportRubricModal.module.scss";
import { isRubricLoaded } from "../state/helpers";
import { confirmAsync } from "../transforms/confirmAsync";

export interface IProps {}

export const ImportRubricModal: React.FC<IProps> = () => {
const { state: teacherTool } = useContext(AppStateContext);
const [selectedFile, setSelectedFile] = useState<File | undefined>(undefined);
const [selectedRubric, setSelectedRubric] = useState<Rubric | undefined>(undefined);
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);

useEffect(() => {
async function updatePreview(file: File) {
const parsedRubric = await getRubricFromFileAsync(file, false /* allow partial */);
if (!parsedRubric) {
setErrorMessage(lf("Invalid rubric file."));
} else {
setErrorMessage(undefined);
}
setSelectedRubric(parsedRubric);
}

if (selectedFile) {
updatePreview(selectedFile);
} else {
setSelectedRubric(undefined);
setErrorMessage(undefined);
}
}, [selectedFile]);

function closeModal() {
setSelectedFile(undefined);
setErrorMessage(undefined);
setSelectedRubric(undefined);
hideModal();
}

function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) {
if (event.target.files && event.target.files.length > 0) {
setSelectedFile(event.target.files[0]);
async function handleFileDroppedAsync(file: File) {
const parsedRubric = await getRubricFromFileAsync(file, false /* allow partial */);
if (!parsedRubric) {
setErrorMessage(lf("Invalid rubric file."));
} else {
setSelectedFile(undefined);
}
}
setErrorMessage(undefined);

function handleImportClicked() {
if (selectedRubric) {
setRubric(selectedRubric);
}
// TODO thsparks - set parsedRubric in app state as a "Pending Rubric" and open a whole new modal, which can also be used with the New Rubric command?

closeModal();
}
if (isRubricLoaded(teacherTool)) {
if (!(await confirmAsync(Strings.ConfirmReplaceRubric))) {
return;
}

const actions = [
{
label: lf("Cancel"),
className: "secondary",
onClick: closeModal,
},
{
label: lf("Import"),
className: "primary",
onClick: handleImportClicked,
disabled: !selectedRubric,
},
];
setRubric(parsedRubric);
closeModal();
}
}
}

return teacherTool.modal === "import-rubric" ? (
<Modal title={lf("Select rubric to import")} actions={actions} onClose={closeModal}>
<Modal title={Strings.ImportRubric} onClose={closeModal}>
<div className={css["import-rubric"]}>
<NoticeLabel severity="warning">
{lf("Warning! Your current rubric will be overwritten by the imported rubric.")}
</NoticeLabel>
{errorMessage && <NoticeLabel severity="error">{errorMessage}</NoticeLabel>}
{selectedRubric && (
<div className={css["rubric-preview-container"]}>
<RubricPreview rubric={selectedRubric} />
</div>
)}
<input
type="file"
tabIndex={0}
autoFocus
aria-label={lf("Select rubric file.")}
onChange={handleFileChange}
/>
<DragAndDropFileSurface onFileDroppedAsync={handleFileDroppedAsync} errorMessage={errorMessage} />
</div>
</Modal>
) : null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.drag-and-drop-file-surface {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;

background-color: var(--pxt-content-background);
border-radius: 0.5rem;
border: 1px dashed lighten(black, 50%); // todo thsparks : yet another theme variable?

padding: 2rem;
min-height: 30vh;

.instruction-container {
display: flex;
flex-direction: column;
align-items: center;

color: lighten(black, 50%); // todo thsparks : yet another theme variable?
font-size: 2rem;

.upload-icon {
font-size: 3rem;
}
}

.error-label-container {
position: absolute;
bottom: 2rem;
}

.droppable-surface {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
div[class*="common-modal-body"] {
background-color: var(--pxt-content-background);
}

.import-rubric {
display: flex;
flex-direction: column;
Expand All @@ -10,4 +14,4 @@
border-radius: 0.3rem;
background-color: var(--pxt-content-background-glass);
}
}
}
2 changes: 2 additions & 0 deletions teachertool/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export namespace Strings {
export const Actions = lf("Actions");
export const AutoRun = lf("auto-run");
export const AutoRunDescription = lf("Automatically re-evaluate when the rubric or project changes");
export const DragAndDrop = lf("Drag & Drop");
export const ReleaseToUpload = lf("Release to Upload");
}

export namespace Ticks {
Expand Down

0 comments on commit 288ecb2

Please sign in to comment.