diff --git a/frontend/src/i18n/locales/de.yaml b/frontend/src/i18n/locales/de.yaml index 88e6f1926..36ba2b71c 100644 --- a/frontend/src/i18n/locales/de.yaml +++ b/frontend/src/i18n/locales/de.yaml @@ -307,9 +307,6 @@ upload: angeschaut werden, sobald die Verarbeitung abgeschlossen ist. Sie können diese Seite jetzt schließen. metadata: - title: Videotitel - description: Beschreibung - required: Pflichtfeld save: Speichern und fertigstellen note-writable-series: > Sie können Videos nur in Serien hochladen, auf die Sie Schreibzugriff haben. @@ -317,12 +314,19 @@ upload: errors: failed-to-upload: Hochladen fehlgeschlagen. unknown: Während des Dateiuploads ist ein unbekannter Fehler aufgetreten. - field-required: Dieses Feld darf nicht leer sein. opencast-server-error: Opencast-Server-Fehler (unerwartete Antwort). opencast-unreachable: 'Netzwerkfehler: Opencast kann nicht erreicht werden.' jwt-invalid: 'Interner Fremdauthentifizierungsfehler: Opencast hat das Hochladen nicht autorisiert.' failed-fetching-series-acl: Abruf der Serienzugangsberechtigungen fehlgeschlagen. +metadata-form: + title: Titel + description: Beschreibung + required: Pflichtfeld + save: Änderungen speichern + errors: + field-required: Dieses Feld darf nicht leer sein. + acl: unknown-user-note: unbekannt no-entries: Keine Einträge diff --git a/frontend/src/i18n/locales/en.yaml b/frontend/src/i18n/locales/en.yaml index 2ae364fea..839b8e3c6 100644 --- a/frontend/src/i18n/locales/en.yaml +++ b/frontend/src/i18n/locales/en.yaml @@ -305,9 +305,6 @@ upload: Your video was uploaded successfully and is now being processed. It can be watched as soon as processing is done. You may close this page now. metadata: - title: Video title - description: Description - required: required save: Save and finish note-writable-series: > Videos can only be uploaded into series for which you have write-access. @@ -315,12 +312,19 @@ upload: errors: failed-to-upload: Failed to upload video. unknown: Unknown error occurred during video upload. - field-required: This field is required (cannot be empty). opencast-server-error: Opencast server error (unexpected response). opencast-unreachable: 'Network error: Opencast cannot be reached.' jwt-invalid: 'Internal cross-authentication error: Opencast did not authorize the upload.' failed-fetching-series-acl: Failed to fetch series acl. +metadata-form: + title: Title + description: Description + required: required + save: Save changes + errors: + field-required: This field is required (cannot be empty). + acl: unknown-user-note: unknown no-entries: No entries diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 867f7f8c4..550494fc4 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -24,14 +24,14 @@ import { UploadRoute } from "./routes/Upload"; import { SearchRoute } from "./routes/Search"; import { InvalidUrlRoute } from "./routes/InvalidUrl"; import { BlockEmbedRoute, EmbedOpencastVideoRoute, EmbedVideoRoute } from "./routes/Embed"; -import { ManageVideoDetailsRoute } from "./routes/manage/Video/Details"; +import { ManageVideoDetailsRoute } from "./routes/manage/Video/VideoDetails"; import { ManageVideoTechnicalDetailsRoute } from "./routes/manage/Video/TechnicalDetails"; import React from "react"; -import { ManageVideoAccessRoute } from "./routes/manage/Video/Access"; +import { ManageVideoAccessRoute } from "./routes/manage/Video/VideoAccess"; import { DirectPlaylistOCRoute, DirectPlaylistRoute } from "./routes/Playlist"; import { ManageSeriesRoute } from "./routes/manage/Series"; -import { ManageSeriesDetailsRoute } from "./routes/manage/Series/Details"; -import { ManageSeriesAccessRoute } from "./routes/manage/Series/Access"; +import { ManageSeriesDetailsRoute } from "./routes/manage/Series/SeriesDetails"; +import { ManageSeriesAccessRoute } from "./routes/manage/Series/SeriesAccess"; diff --git a/frontend/src/routes/Upload.tsx b/frontend/src/routes/Upload.tsx index faefac1ab..a1f74e572 100644 --- a/frontend/src/routes/Upload.tsx +++ b/frontend/src/routes/Upload.tsx @@ -2,9 +2,12 @@ import React, { MutableRefObject, ReactNode, useEffect, useId, useRef, useState import { useTranslation } from "react-i18next"; import { fetchQuery, graphql, useFragment } from "react-relay"; import { keyframes } from "@emotion/react"; -import { Controller, useController, useForm } from "react-hook-form"; +import { Controller, FormProvider, useController } from "react-hook-form"; import { LuCheckCircle, LuUpload, LuInfo } from "react-icons/lu"; -import { Spinner, WithTooltip, assertNever, bug, unreachable } from "@opencast/appkit"; +import { + Spinner, WithTooltip, assertNever, bug, + unreachable, Button, boxError, ErrorBox, Card, +} from "@opencast/appkit"; import { RootLoader } from "../layout/Root"; import { environment, loadQuery } from "../relay"; @@ -13,13 +16,16 @@ import { makeRoute } from "../rauta"; import { ErrorDisplay, errorDisplayInfo } from "../util/err"; import { mapAcl, useNavBlocker } from "./util"; import CONFIG from "../config"; -import { Button, boxError, ErrorBox, Card } from "@opencast/appkit"; import { LinkButton } from "../ui/LinkButton"; -import { Form } from "../ui/Form"; -import { Input, TextArea } from "../ui/Input"; import { isRealUser, User, useUser } from "../User"; import { currentRef, useRefState } from "../util"; -import { FieldIsRequiredNote, InputContainer, TitleLabel } from "../ui/metadata"; +import { + FieldIsRequiredNote, + InputContainer, + MetadataFields, + MetadataForm, + useMetadataForm, +} from "../ui/metadata"; import { PageTitle } from "../layout/header/ui"; import { useRouter } from "../router"; import { getJwt } from "../relay/auth"; @@ -27,7 +33,7 @@ import { VideoListSelector } from "../ui/SearchableSelect"; import { Breadcrumbs } from "../ui/Breadcrumbs"; import { ManageNav, ManageRoute } from "./manage"; import { COLORS } from "../color"; -import { COMMON_ROLES } from "../util/roles"; +import { defaultAclMap } from "../util/roles"; import { Acl, AclSelector, knownRolesFragment } from "../ui/Access"; import { AccessKnownRolesData$data, @@ -690,8 +696,6 @@ const MetaDataEdit: React.FC = ({ onSave, disabled, knownRole return unreachable(); } - const titleFieldId = useId(); - const descriptionFieldId = useId(); const seriesFieldId = useId(); const [lockedAcl, setLockedAcl] = useState(null); const [aclError, setAclError] = useState(null); @@ -744,104 +748,57 @@ const MetaDataEdit: React.FC = ({ onSave, disabled, knownRole } }; - const defaultAcl: Acl = new Map([ - [user.userRole, { - actions: new Set(["read", "write"]), - info: { - label: { "default": user.displayName }, - implies: null, - large: false, - }, - }], - [COMMON_ROLES.ANONYMOUS, { - actions: new Set(["read"]), - info: null, - }], - ]); - - const { register, handleSubmit, control, formState: { isValid, errors } } = useForm({ - mode: "onChange", - defaultValues: { acl: defaultAcl }, - }); + const { formMethods } = useMetadataForm({ acl: defaultAclMap(user) }); + const { handleSubmit, control, formState: { isValid, errors } } = formMethods; const { field: seriesField } = useController({ name: "series", control, rules: { - required: CONFIG.upload.requireSeries ? t("upload.errors.field-required") : false, + required: CONFIG.upload.requireSeries + ? t("metadata-form.errors.field-required") + : false, }, }); const onSubmit = handleSubmit(data => onSave(data)); - // We only allow submitting the form on clicking the button below so that - // pressing 'enter' inside inputs doesn't lead to submit the form too - // early. - return ( -
e.preventDefault()} - css={{ - margin: "32px 2px", - "label": { - color: "var(--color-neutral90)", - }, - }} - > - {/* Title */} - - - + + {/* Title & Description */} + + + {/* Series */} + + + onSeriesChange({ opencastId: data?.opencastId })} + onBlur={seriesField.onBlur} + required={CONFIG.upload.requireSeries} /> - {boxError(errors.title?.message)} + {boxError(errors.series?.message)} -
- {/* Description */} - - -