Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
78 changes: 40 additions & 38 deletions apps/web/src/references/components/CreateReference.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,54 @@
import { Dialog, DialogContent, DialogTitle } from "@base/Dialog";
import NavTab from "@base/NavTab";
import NavTabs from "@base/NavTabs";
import EmptyReference from "./EmptyReference";
import ImportReference from "./ImportReference";
import { SelectBox, SelectBoxItem } from "@base/SelectBox";
import { useState } from "react";
import { CreateReferenceForm } from "./CreateReferenceForm";

type CreateReferenceProps = {
createReferenceType?: string;
setCreateReferenceType: (type?: string) => void;
open: boolean;
onOpenChange: (open: boolean) => void;
};

/**
* The create reference view with options to create an empty reference or import a reference
*/
export function CreateReference({
createReferenceType,
setCreateReferenceType,
}: CreateReferenceProps) {
export function CreateReference({ open, onOpenChange }: CreateReferenceProps) {
const [mode, setMode] = useState<"empty" | "import">("empty");

function handleOpenChange(open: boolean) {
onOpenChange(open);
if (!open) {
setMode("empty");
}
}

function handleSuccess() {
onOpenChange(false);
setMode("empty");
}

return (
<Dialog
open={Boolean(createReferenceType)}
onOpenChange={() => {
setCreateReferenceType(undefined);
}}
>
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent size="lg">
<DialogTitle>Create Reference</DialogTitle>
<NavTabs>
<NavTab
to="."
search={{ createReferenceType: "empty" }}
isActive={createReferenceType === "empty"}
>
Empty
</NavTab>
<NavTab
to="."
search={{ createReferenceType: "import" }}
isActive={createReferenceType === "import"}
>
Import
</NavTab>
</NavTabs>

{createReferenceType === "import" ? (
<ImportReference />
) : (
<EmptyReference />
)}
<SelectBox
className="grid-cols-2"
label="Method"
onValueChange={(value) => setMode(value as "empty" | "import")}
value={mode}
>
<SelectBoxItem value="empty">
<div>Empty</div>
<span>Start from a blank reference.</span>
</SelectBoxItem>
<SelectBoxItem value="import">
<div>Import</div>
<span>
Create a reference from a file previously exported from another
Virtool reference.
</span>
</SelectBoxItem>
</SelectBox>
<CreateReferenceForm mode={mode} onSuccess={handleSuccess} />
</DialogContent>
</Dialog>
);
Expand Down
173 changes: 173 additions & 0 deletions apps/web/src/references/components/CreateReferenceForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { DialogFooter } from "@base/Dialog";
import InputError from "@base/InputError";
import InputGroup from "@base/InputGroup";
import InputLabel from "@base/InputLabel";
import InputSimple from "@base/InputSimple";
import ProgressBarAffixed from "@base/ProgressBarAffixed";
import SaveButton from "@base/SaveButton";
import TextArea from "@base/TextArea";
import { useNavigate } from "@tanstack/react-router";
import { useId } from "react";
import { Controller, useForm } from "react-hook-form";
import { UploadBar } from "@/uploads/components/UploadBar";
import {
useCreateReference,
useImportReference,
useUploadReference,
} from "../queries";

type FormValues = {
name: string;
description: string;
organism: string;
upload: string;
};

type CreateReferenceFormProps = {
mode: "empty" | "import";
onSuccess: () => void;
};

/**
* A form for creating a reference from scratch or by importing a file, sharing
* name and description between the two modes
*/
export function CreateReferenceForm({
mode,
onSuccess,
}: CreateReferenceFormProps) {
const navigate = useNavigate();
const createMutation = useCreateReference();
const importMutation = useImportReference();
const { uploadMutation, fileName, uploadId, progress } = useUploadReference();

const nameId = useId();
const organismId = useId();
const descriptionId = useId();

const {
control,
formState: { errors },
handleSubmit,
register,
setError,
} = useForm<FormValues>({
defaultValues: {
name: "",
description: "",
organism: "",
upload: "",
},
});

function handleDrop(acceptedFiles: File[]) {
const file = acceptedFiles[0];
if (file === undefined) {
return;
}

uploadMutation.mutate(file);
}

function onSubmit(values: FormValues) {
if (mode === "import") {
if (uploadId === null) {
setError("upload", {
message: "Please wait for the upload to finish",
});
return;
}

importMutation.mutate(
{
name: values.name,
description: values.description,
importFrom: uploadId,
},
{
onSuccess: () => {
navigate({ to: "/refs", replace: true });
onSuccess();
},
},
);

return;
}

createMutation.mutate(
{
name: values.name,
description: values.description,
organism: values.organism,
},
{
onSuccess: () => {
navigate({ to: "/refs" });
onSuccess();
},
},
);
}

const uploadBarMessage =
fileName || (progress === 0 ? "Drag file here to upload" : "Uploading...");

return (
<form onSubmit={handleSubmit(onSubmit)}>
{mode === "import" && (
<Controller
control={control}
name="upload"
rules={{ required: "A reference file must be uploaded" }}
render={({ field: { onChange } }) => (
<div className="mb-4">
<ProgressBarAffixed color="green" now={progress} />
<UploadBar
message={uploadBarMessage}
onDrop={(acceptedFiles) => {
handleDrop(acceptedFiles);
const file = acceptedFiles[0];
if (file) {
onChange(file.name);
}
}}
multiple={false}
/>

<InputError>{errors.upload?.message}</InputError>
</div>
)}
/>
)}

<InputGroup className="pb-0">
<InputLabel htmlFor={nameId}>Name</InputLabel>
<InputSimple
id={nameId}
{...register("name", { required: "Required Field" })}
/>
<InputError>{errors.name?.message}</InputError>
</InputGroup>

{mode === "empty" && (
<InputGroup>
<InputLabel htmlFor={organismId}>Organism</InputLabel>
<InputSimple id={organismId} {...register("organism")} />
</InputGroup>
)}

<InputGroup>
<InputLabel htmlFor={descriptionId}>Description</InputLabel>
<TextArea id={descriptionId} {...register("description")} />
</InputGroup>

<DialogFooter>
<SaveButton
altText="Create"
disabled={mode === "import" && progress > 0 && uploadId === null}
/>
</DialogFooter>
</form>
);
}
52 changes: 0 additions & 52 deletions apps/web/src/references/components/EmptyReference.tsx

This file was deleted.

Loading