diff --git a/mwdb/web/src/commons/api/index.tsx b/mwdb/web/src/commons/api/index.tsx index 2c75ce1dd..a3d4310cd 100644 --- a/mwdb/web/src/commons/api/index.tsx +++ b/mwdb/web/src/commons/api/index.tsx @@ -93,11 +93,14 @@ import { UpdateUserResponse, UploadFileResponse, UserRequestPasswordChangeResponse, + UploadFileRequest, + UploadConfigRequest, + UploadConfigResponse, + UploadBlobRequest, + UploadBlobResponse, } from "@mwdb-web/types/api"; import { - Attribute, Capability, - Comment, CreateUser, ObjectLegacyType, ObjectType, @@ -587,21 +590,22 @@ async function requestZipFileDownloadLink(id: string): Promise { return `${baseURL}/file/${id}/download/zip?token=${response.data.token}`; } -function uploadFile( - file: File, - parent: string, - upload_as: string, - attributes: Attribute[], - fileUploadTimeout: number, - share3rdParty: boolean -): UploadFileResponse { +function uploadFile(body: UploadFileRequest): UploadFileResponse { + const { + file, + parent, + shareWith, + attributes, + fileUploadTimeout, + share3rdParty, + } = body; let formData = new FormData(); - formData.append("file", file); + formData.append("file", file!); formData.append( "options", JSON.stringify({ parent: parent || null, - upload_as: upload_as, + upload_as: shareWith, attributes: attributes, share_3rd_party: share3rdParty, }) @@ -609,6 +613,21 @@ function uploadFile( return axios.post(`/file`, formData, { timeout: fileUploadTimeout }); } +function uploadBlob(body: UploadBlobRequest): UploadBlobResponse { + const { name, shareWith, parent, type } = body; + return axios.post(`/blob`, { + ...body, + blob_name: name, + blob_type: type, + upload_as: shareWith, + parent: parent || null, + }); +} + +function uploadConfig(body: UploadConfigRequest): UploadConfigResponse { + return axios.post("/config", body); +} + function getRemoteNames(): GetRemoteNamesResponse { return axios.get("/remote"); } @@ -845,6 +864,8 @@ export const api = { requestFileDownloadLink, requestZipFileDownloadLink, uploadFile, + uploadBlob, + uploadConfig, getRemoteNames, pushObjectRemote, pullObjectRemote, diff --git a/mwdb/web/src/commons/navigation/AppRoutes.tsx b/mwdb/web/src/commons/navigation/AppRoutes.tsx index b3979972c..ee115cf4a 100644 --- a/mwdb/web/src/commons/navigation/AppRoutes.tsx +++ b/mwdb/web/src/commons/navigation/AppRoutes.tsx @@ -15,8 +15,6 @@ import { RequiresAuth, RequiresCapability } from "../ui"; import { RecentSamplesView } from "@mwdb-web/components/File/Views/RecentSamplesView"; import { RecentConfigsView } from "@mwdb-web/components/Config/Views/RecentConfigsView"; import { RecentBlobsView } from "@mwdb-web/components/Blob/Views/RecentBlobsView"; -import { SearchView } from "@mwdb-web/components/Views/SearchView"; -import { UploadView } from "@mwdb-web/components/Views/UploadView"; import { ConfigStatsView } from "@mwdb-web/components/Config/Views/ConfigStatsView"; import { DocsView } from "@mwdb-web/components/Views/DocsView"; import { ShowSampleView } from "@mwdb-web/components/Views/ShowSampleView"; @@ -60,6 +58,10 @@ import { AttributeView } from "@mwdb-web/components/Settings/Views/AttributeView import { AttributeDetailsView } from "@mwdb-web/components/Settings/Views/AttributeDetailsView"; import { AttributesPermissionsView } from "@mwdb-web/components/Settings/Views/AttributePermissionsView"; import { AttributeEditTemplateView } from "@mwdb-web/components/Settings/Views/AttributeEditTemplateView"; +import { UploadConfigView } from "@mwdb-web/components/Upload/Views/UploadConfigView"; +import { UploadBlobView } from "@mwdb-web/components/Upload/Views/UploadBlobView"; +import { UploadFileView } from "@mwdb-web/components/Upload/Views/UploadFileView"; +import { SearchView } from "@mwdb-web/components/Views/SearchView"; export function AppRoutes() { return ( @@ -81,7 +83,25 @@ export function AppRoutes() { path="upload" element={ - + + + } + /> + + + + } + /> + + } /> diff --git a/mwdb/web/src/commons/ui/Label.tsx b/mwdb/web/src/commons/ui/Label.tsx index ad538ae5e..1e1c9d091 100644 --- a/mwdb/web/src/commons/ui/Label.tsx +++ b/mwdb/web/src/commons/ui/Label.tsx @@ -5,10 +5,11 @@ type Props = { required?: boolean; htmlFor?: string; className?: string; + children?: React.ReactNode; }; export function Label(props: Props) { - const { label, required, htmlFor, className } = props; + const { label, required, htmlFor, className, children } = props; const setClassName = useCallback(() => { let result = ""; @@ -24,6 +25,7 @@ export function Label(props: Props) { return ( ); } diff --git a/mwdb/web/src/commons/ui/NavDropdown.tsx b/mwdb/web/src/commons/ui/NavDropdown.tsx index 6402e722e..f53f9bddb 100644 --- a/mwdb/web/src/commons/ui/NavDropdown.tsx +++ b/mwdb/web/src/commons/ui/NavDropdown.tsx @@ -11,36 +11,32 @@ type Props = { export function NavDropdown(props: Props) { if (!props.elements.length) return ); } diff --git a/mwdb/web/src/components/DagreD3Plot.tsx b/mwdb/web/src/components/DagreD3Plot.tsx index a58ffb9f7..30ae95492 100644 --- a/mwdb/web/src/components/DagreD3Plot.tsx +++ b/mwdb/web/src/components/DagreD3Plot.tsx @@ -60,7 +60,7 @@ export function DagreD3Plot(props: Props) { function NodeComponent() { const Node = props.nodeComponent as any; useEffect(() => { - resolve(); + resolve(undefined); }, []); return ; } diff --git a/mwdb/web/src/components/Navigation.tsx b/mwdb/web/src/components/Navigation.tsx index 34aabbca1..5ae3c77a4 100644 --- a/mwdb/web/src/components/Navigation.tsx +++ b/mwdb/web/src/components/Navigation.tsx @@ -14,12 +14,11 @@ import { ConfigContext } from "@mwdb-web/commons/config"; import { fromPlugins, Extendable } from "@mwdb-web/commons/plugins"; import { ConfirmationModal, NavDropdown } from "@mwdb-web/commons/ui"; import { useRemote, useRemotePath } from "@mwdb-web/commons/remotes"; -import { Capability } from "@mwdb-web/types/types"; import logo from "../assets/logo.png"; import { AdminNav } from "./AdminNav"; import { RemoteDropdown } from "./RemoteDropdown"; -import { UploadButton } from "./UploadButton"; +import { Upload } from "./Upload/common/Upload"; export function Navigation() { const auth = useContext(AuthContext); @@ -60,7 +59,7 @@ export function Navigation() {
  • - +
  • ) : ( diff --git a/mwdb/web/src/components/ShowObject/common/RelationsTab.tsx b/mwdb/web/src/components/ShowObject/common/RelationsTab.tsx index dc83e336c..0e076bb0d 100644 --- a/mwdb/web/src/components/ShowObject/common/RelationsTab.tsx +++ b/mwdb/web/src/components/ShowObject/common/RelationsTab.tsx @@ -3,11 +3,9 @@ import { useContext } from "react"; import { useSearchParams } from "react-router-dom"; import { faProjectDiagram, faSearch } from "@fortawesome/free-solid-svg-icons"; - -import { RelationsPlotView } from "../../Views/RelationsPlotView"; - import { ObjectContext } from "@mwdb-web/commons/context"; import { ObjectAction, ObjectTab } from "@mwdb-web/commons/ui"; +import { RelationsPlotView } from "@mwdb-web/components/Views/RelationsPlotView"; export function RelationsTab() { const context = useContext(ObjectContext); diff --git a/mwdb/web/src/components/Upload/Views/UploadBlobView.tsx b/mwdb/web/src/components/Upload/Views/UploadBlobView.tsx new file mode 100644 index 000000000..6930b67c0 --- /dev/null +++ b/mwdb/web/src/components/Upload/Views/UploadBlobView.tsx @@ -0,0 +1,274 @@ +import { useContext } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import { useForm, useFieldArray, UseFormProps } from "react-hook-form"; +import { toast } from "react-toastify"; +import * as Yup from "yup"; +import { yupResolver } from "@hookform/resolvers/yup"; + +import { Attributes } from "../common/Attributes"; + +import { api } from "@mwdb-web/commons/api"; +import { AuthContext } from "@mwdb-web/commons/auth"; +import { Autocomplete, View, FormError, Label } from "@mwdb-web/commons/ui"; +import { Extendable } from "@mwdb-web/commons/plugins"; +import { useGroup } from "../common/hooks/useGroup"; +import { + getSharingModeHint, + sharingModeToUploadParam, +} from "../common/helpers"; +import { Capability } from "@mwdb-web/types/types"; +import { getErrorMessage } from "@mwdb-web/commons/helpers"; +import { isEmpty } from "lodash"; +import { UploadBlobRequest } from "@mwdb-web/types/api"; +import { Sharing } from "../common/Sharing"; + +type FormValues = UploadBlobRequest; + +const validationSchema: Yup.SchemaOf> = Yup.object().shape({ + content: Yup.string().required("Content is required"), + type: Yup.string().required("Type is required"), + name: Yup.string().required("Name is required"), + parent: Yup.string(), + shareWith: Yup.string(), + share3rdParty: Yup.bool(), + group: Yup.string(), + attributes: Yup.array(), +}); + +export function UploadBlobView() { + const searchParams = useSearchParams()[0]; + const formOptions: UseFormProps = { + resolver: yupResolver(validationSchema), + mode: "onSubmit", + reValidateMode: "onSubmit", + defaultValues: { + content: "", + shareWith: "", + type: "", + group: "", + parent: searchParams.get("parent") || "", + attributes: [], + }, + }; + + const { + register, + setValue, + handleSubmit, + watch, + formState: { errors }, + control, + } = useForm(formOptions); + + const auth = useContext(AuthContext); + const navigate = useNavigate(); + + const { groups } = useGroup(setValue, "shareWith"); + + const { shareWith, group } = watch(); + + const handleParentClear = () => { + if (searchParams.get("parent")) navigate("/blob_upload"); + setValue("parent", ""); + }; + + const updateSharingMode = (ev: React.ChangeEvent) => { + setValue("shareWith", ev.target.value); + setValue("group", ""); + }; + + const onSubmit = async (values: FormValues) => { + try { + const body: FormValues = { + ...values, + parent: searchParams.get("parent") || values.parent, + shareWith: sharingModeToUploadParam( + values.shareWith, + values.group + )!, + }; + const response = await api.uploadBlob(body); + navigate("/blob/" + response.data.id, { + replace: true, + }); + toast("Blob uploaded successfully.", { + type: "success", + }); + } catch (error) { + toast(getErrorMessage(error), { + type: "error", + }); + } + }; + + return ( + + +

    Blob upload

    +
    +
    +
    +
    +