diff --git a/src/videoag/course/DownloadManager.tsx b/src/videoag/course/DownloadManager.tsx
index bc6a8d2c785a3b1dd6fdcb9e2584e989feb80d99..92561b27a747225032c587138096f86109835384 100644
--- a/src/videoag/course/DownloadManager.tsx
+++ b/src/videoag/course/DownloadManager.tsx
@@ -3,15 +3,18 @@ import Modal from "react-bootstrap/Modal";
 import DropdownButton from "react-bootstrap/DropdownButton";
 import DropdownItem from "react-bootstrap/DropdownItem";
 
-import type { course, publish_medium } from "@/videoag/api/types";
+import type { int, course, lecture } from "@/videoag/api/types";
 import { showWarningToast, showError } from "@/videoag/error/ErrorDisplay";
 import { useLanguage } from "@/videoag/localization/LanguageProvider";
 import { StylizedText } from "@/videoag/miscellaneous/StylizedText";
 import { filesizeToHuman } from "@/videoag/miscellaneous/Formatting";
 import { showInfoToast } from "@/videoag/miscellaneous/Util";
 
-import { sortSourceNames, sortSources, sourceToName } from "./Player";
-import { sortPublishMediaByQuality, getNamedPublishMedia, getSortedDownloadablePublishMedia, getMediumQualityName } from "./Medium";
+import {
+    getNamedPublishMedia,
+    getSortedDownloadablePublishMedia,
+    getMediumQualityName,
+} from "./Medium";
 
 interface DownloadStatus {
     downloadedBytes: number;
@@ -26,12 +29,12 @@ interface DownloadStatus {
 
 async function downloadToDirectoryHandle(
     replaceStatus: (lecture_id: int, fn: (old?: DownloadStatus) => DownloadStatus) => void,
-    filesToDownload: { lectureId: int, url: string, filename: string, fileSize: int }[],
+    filesToDownload: { lectureId: int; url: string; filename: string; fileSize: int }[],
     failNowOnException: any,
     requestStopDownload: any,
     saveDir: any,
-    onFileDownloaded: (fileHandle: any) => Promise<void>,
-): boolean {
+    onFileDownloaded: (fileHandle: any, filename: string) => Promise<void>,
+): Promise<boolean> {
     for (const toDownload of filesToDownload) {
         try {
             if (requestStopDownload.current) {
@@ -86,7 +89,7 @@ async function downloadToDirectoryHandle(
                     error: "Download cancelled",
                 }));
             } else {
-                await onFileDownloaded(fileHandle);
+                await onFileDownloaded(fileHandle, toDownload.filename);
                 replaceStatus(toDownload.lectureId, () => ({
                     downloadedBytes,
                     totalBytes,
@@ -105,16 +108,17 @@ async function downloadToDirectoryHandle(
 
 async function downloadWithFileSystemAPI(
     replaceStatus: (lecture_id: int, fn: (old?: DownloadStatus) => DownloadStatus) => void,
-    filesToDownload: { lectureId: int, url: string, filename: string, fileSize: int }[],
+    filesToDownload: { lectureId: int; url: string; filename: string; fileSize: int }[],
     failNowOnException: any,
     requestStopDownload: any,
-): boolean {
+): Promise<boolean> {
     if (!(window as any).showDirectoryPicker) {
         throw new Error("File System API unsupported");
     }
     const saveDir = await (window as any).showDirectoryPicker();
     if (!saveDir) {
-        return;
+        // User cancelled
+        return true;
     }
 
     await saveDir.requestPermission({ mode: "readwrite" });
@@ -125,28 +129,30 @@ async function downloadWithFileSystemAPI(
         failNowOnException,
         requestStopDownload,
         saveDir,
-        (fileHandle) => undefined,
-    )
+        async (fileHandle, filename) => {},
+    );
 }
 
 async function downloadWithOPFSAPI(
     replaceStatus: (lecture_id: int, fn: (old?: DownloadStatus) => DownloadStatus) => void,
-    filesToDownload: { lectureId: int, url: string, filename: string, fileSize: int }[],
+    filesToDownload: { lectureId: int; url: string; filename: string; fileSize: int }[],
     failNowOnException: any,
     requestStopDownload: any,
-): boolean {
+): Promise<boolean> {
     if (!navigator.storage || !navigator.storage.getDirectory) {
         throw new Error("OPFS Api unsupported");
     }
     let saveDir = await navigator.storage.getDirectory();
     if (!saveDir) {
-        return;
+        // User cancelled
+        return true;
     }
     // clear the directory
     saveDir.removeEntry("videoag", { recursive: true });
     saveDir = await saveDir.getDirectoryHandle("videoag", { create: true });
     if (!saveDir) {
-        return;
+        // User cancelled
+        return true;
     }
 
     return downloadToDirectoryHandle(
@@ -155,7 +161,7 @@ async function downloadWithOPFSAPI(
         failNowOnException,
         requestStopDownload,
         saveDir,
-        async (fileHandle) => {
+        async (fileHandle, filename) => {
             // create a element to download the file
             let a = document.createElement("a");
             a.href = URL.createObjectURL(await fileHandle.getFile());
@@ -163,17 +169,19 @@ async function downloadWithOPFSAPI(
             a.target = "_blank";
             a.click();
         },
-    )
+    );
 }
 
 async function downloadWithDownloadAPI(
     replaceStatus: (lecture_id: int, fn: (old?: DownloadStatus) => DownloadStatus) => void,
-    filesToDownload: { lectureId: int, url: string, filename: string, fileSize: int }[],
+    filesToDownload: { lectureId: int; url: string; filename: string; fileSize: int }[],
     failNowOnException: any,
     requestStopDownload: any,
-): boolean {
-    showWarningToast("Unable to store files in directory automatically. Using basic download API which might require" +
-        " you to selected the destination location for every file.");
+): Promise<boolean> {
+    showWarningToast(
+        "Unable to store files in directory automatically. Using basic download API which might require" +
+            " you to selected the destination location for every file.",
+    );
     for (const toDownload of filesToDownload) {
         try {
             if (requestStopDownload.current) {
@@ -203,21 +211,21 @@ async function downloadWithDownloadAPI(
 
 async function downloadMedia(
     downloadStatus: Map<int, DownloadStatus> | undefined,
-    setDownloadStatus: (fn: (old?: Map<int, DownloadStatus> | undefined) => Map<int, DownloadStatus> | undefined) => void,
+    setDownloadStatus: (
+        fn: (old?: Map<int, DownloadStatus> | undefined) => Map<int, DownloadStatus> | undefined,
+    ) => void,
     course: course,
     chosenMediumIdByLectureId: Map<int, int>,
     failNowOnException: any,
     requestStopDownload: any,
 ) {
-    const filesToDownload = [];
+    const filesToDownload: { lectureId: int; url: string; filename: string; fileSize: int }[] = [];
     for (const lecture of course.lectures!) {
         const chosenMediumId = chosenMediumIdByLectureId.get(lecture.id);
-        if (chosenMediumId === undefined)
-            continue;
+        if (chosenMediumId === undefined) continue;
 
         const medium = lecture.publish_media!.find((m) => m.id == chosenMediumId);
-        if (!medium)
-            throw new Error(`Unable to find medium with id ${chosenMediumId} in lecture`);
+        if (!medium) throw new Error(`Unable to find medium with id ${chosenMediumId} in lecture`);
 
         filesToDownload.push({
             lectureId: lecture.id,
@@ -225,14 +233,14 @@ async function downloadMedia(
             // TODO file ending mp4
             // TODO lecture date and not id
             filename: `videoag_${course.handle}_${lecture.id}_${getMediumQualityName(medium)}.mp4`,
-            fileSize: medium.file_size,
+            fileSize: medium.medium_metadata.file_size,
         });
     }
 
-    setDownloadStatus(new Map());
+    setDownloadStatus((oldStatus) => new Map<int, DownloadStatus>());
     const replaceStatus = (lectureId: int, fn: (old?: DownloadStatus) => DownloadStatus) => {
         setDownloadStatus((oldStatus) => {
-            const newStatus = new Map(oldStatus);
+            const newStatus = new Map<int, DownloadStatus>(oldStatus);
             newStatus.set(lectureId, fn(oldStatus?.get(lectureId)));
             return newStatus;
         });
@@ -241,10 +249,12 @@ async function downloadMedia(
     console.log("Starting download...");
     failNowOnException.current = false;
     const success = await downloadWithFileSystemAPI(
-            replaceStatus,
-            filesToDownload,
-            requestStopDownload,
-        ).catch((e) => {
+        replaceStatus,
+        filesToDownload,
+        failNowOnException,
+        requestStopDownload,
+    )
+        .catch((e) => {
             console.log("Error with File System Api", e);
             if (failNowOnException.current) {
                 showError(e, "An error occurred while downloading");
@@ -253,9 +263,11 @@ async function downloadMedia(
             return downloadWithOPFSAPI(
                 replaceStatus,
                 filesToDownload,
+                failNowOnException,
                 requestStopDownload,
             );
-        }).catch((e) => {
+        })
+        .catch((e) => {
             console.log("Error with OPFS Api", e);
             if (failNowOnException.current) {
                 showError(e, "An error occurred while downloading");
@@ -264,16 +276,18 @@ async function downloadMedia(
             return downloadWithDownloadAPI(
                 replaceStatus,
                 filesToDownload,
+                failNowOnException,
                 requestStopDownload,
             );
-        }).catch((e) => {
+        })
+        .catch((e) => {
             console.log("Error with Download Api", e);
             showError(e, "An error occurred while downloading");
             return false;
         });
-    console.log(`Download finished. Success: ${success}`)
+    console.log(`Download finished. Success: ${success}`);
     if (success) {
-        showInfoToast("Download finished", true)
+        showInfoToast("Download finished");
     }
 }
 
@@ -286,7 +300,7 @@ function DownloadAllLectureListItem({
 }: {
     lecture: lecture;
     selectedMediumId: int | undefined;
-    setSelectedMediumId: (int) => void;
+    setSelectedMediumId: (mediumId: int) => void;
     isDownloading: boolean;
     downloadStatus: DownloadStatus | undefined;
 }) {
@@ -305,14 +319,13 @@ function DownloadAllLectureListItem({
                 </div>
             );
         } else if (downloadStatus.error) {
-            displayedProgress = (
-                <span className="text-danger">
-                    {downloadStatus.get(lecture.id)?.error}
-                </span>
-            );
+            displayedProgress = <span className="text-danger">{downloadStatus.error}</span>;
         } else if (downloadStatus.done) {
             displayedProgress = <span className="text-success">Done</span>;
         } else {
+            const percent = Math.floor(
+                (downloadStatus.downloadedBytes / downloadStatus.totalBytes) * 100,
+            );
             displayedProgress = (
                 <>
                     <div className="progress" style={{ width: "100px" }}>
@@ -320,24 +333,16 @@ function DownloadAllLectureListItem({
                             className="progress-bar"
                             role="progressbar"
                             style={{
-                                width: downloadStatus.percent + "%",
+                                width: percent + "%",
                             }}
-                            aria-valuenow={downloadStatus.percent}
+                            aria-valuenow={percent}
                             aria-valuemin={0}
                             aria-valuemax={100}
                         />
                     </div>
                     <span>
-                        {Math.round(
-                            (downloadStatus.downloaded ?? 0) /
-                                1024 /
-                                1024,
-                        )}{" "}
-                        MB /{" "}
-                        {Math.round(
-                            (downloadStatus.total ?? 0) / 1024 / 1024,
-                        )}{" "}
-                        MB
+                        {Math.round((downloadStatus.downloadedBytes ?? 0) / 1024 / 1024)} MB /{" "}
+                        {Math.round((downloadStatus.totalBytes ?? 0) / 1024 / 1024)} MB
                     </span>
                 </>
             );
@@ -373,8 +378,9 @@ function DownloadAllLectureListItem({
             </td>
             <td
                 className={
-                    "col-6 make-span-overflow-scroll " +
-                    selectedMediumId !== undefined ? "" : "text-secondary"
+                    "col-6 make-span-overflow-scroll " + selectedMediumId !== undefined
+                        ? ""
+                        : "text-secondary"
                 }
             >
                 <StylizedText>{lecture.title}</StylizedText>
@@ -384,7 +390,7 @@ function DownloadAllLectureListItem({
                     className="form-select"
                     disabled={isDownloading || media.length == 0}
                     value={selectedMediumId}
-                    onChange={(e) => setSelectedMediumId(e.target.value)}
+                    onChange={(e) => setSelectedMediumId(parseInt(e.target.value))}
                 >
                     {getNamedPublishMedia(media).map(({ name, medium }) => (
                         <option key={medium.id} value={medium.id}>
@@ -400,7 +406,9 @@ function DownloadAllLectureListItem({
 
 export default function DownloadAllModal({ course }: { course: course }) {
     const [showModal, setShowModal] = useState(false);
-    const [chosenMediumIdByLectureId, setChosenMediumIdByLectureId] = useState<Map<int, int>>(new Map());
+    const [chosenMediumIdByLectureId, setChosenMediumIdByLectureId] = useState<Map<int, int>>(
+        new Map(),
+    );
 
     const requestStopDownload = useRef(false);
     const failNowOnException = useRef(false);
@@ -422,9 +430,8 @@ export default function DownloadAllModal({ course }: { course: course }) {
 
         const newChosenMedia = new Map<int, int>();
         for (const lecture of course.lectures!) {
-            const publishMedia = getSortedDownloadablePublishMedia(lecture.publish_media!)
-            if (publishMedia.length > 0)
-                newChosenMedia.set(lecture.id, publishMedia[0].id);
+            const publishMedia = getSortedDownloadablePublishMedia(lecture.publish_media!);
+            if (publishMedia.length > 0) newChosenMedia.set(lecture.id, publishMedia[0].id);
         }
         setChosenMediumIdByLectureId(newChosenMedia);
         // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -438,11 +445,10 @@ export default function DownloadAllModal({ course }: { course: course }) {
 
         for (const medium of media) {
             const qualityName = getMediumQualityName(medium);
-            if (!allQualityNames.includes(qualityName))
+            if (qualityName && !allQualityNames.includes(qualityName))
                 allQualityNames.push(qualityName);
 
-            if (medium.id === selectedMediumId)
-                totalSize += medium.medium_metadata.file_size;
+            if (medium.id === selectedMediumId) totalSize += medium.medium_metadata.file_size;
         }
     }
     if (allQualityNames.length === 0) {
@@ -455,11 +461,9 @@ export default function DownloadAllModal({ course }: { course: course }) {
 
         const newChosenMedia = new Map(chosenMediumIdByLectureId);
         for (const lecture of course.lectures!) {
-            if (newChosenMedia.get(lecture.id))
-                continue;
-            const publishMedia = getSortedDownloadablePublishMedia(lecture.publish_media!)
-            if (publishMedia.length > 0)
-                newChosenMedia.set(lecture.id, publishMedia[0].id);
+            if (newChosenMedia.get(lecture.id)) continue;
+            const publishMedia = getSortedDownloadablePublishMedia(lecture.publish_media!);
+            if (publishMedia.length > 0) newChosenMedia.set(lecture.id, publishMedia[0].id);
         }
         setChosenMediumIdByLectureId(newChosenMedia);
     };
@@ -479,9 +483,8 @@ export default function DownloadAllModal({ course }: { course: course }) {
 
         const newChosenMedia = new Map(chosenMediumIdByLectureId);
         for (const lecture of course.lectures!) {
-            if (newChosenMedia.get(lecture.id) === undefined)
-                continue;
-            for (const medium of lecture.publish_media) {
+            if (newChosenMedia.get(lecture.id) === undefined) continue;
+            for (const medium of lecture.publish_media!) {
                 if (getMediumQualityName(medium) === qualityToSet)
                     newChosenMedia.set(lecture.id, medium.id);
             }
@@ -533,8 +536,7 @@ export default function DownloadAllModal({ course }: { course: course }) {
                         >
                             <i
                                 className={
-                                    "bi me-1 " +
-                                    (isDownloading ? "bi-pause-fill" : "bi-play-fill")
+                                    "bi me-1 " + (isDownloading ? "bi-pause-fill" : "bi-play-fill")
                                 }
                             />
                             {isDownloading
@@ -586,12 +588,16 @@ export default function DownloadAllModal({ course }: { course: course }) {
                         style={{ tableLayout: "fixed" }}
                     >
                         <tbody>
-                            {course.lectures.map((lecture) => (
+                            {course.lectures!.map((lecture) => (
                                 <DownloadAllLectureListItem
                                     key={lecture.id}
                                     lecture={lecture}
                                     selectedMediumId={chosenMediumIdByLectureId.get(lecture.id)}
-                                    setSelectedMediumId={(medium_id) => setLectureToMediumId(lecture.id, medium_id)}
+                                    setSelectedMediumId={(medium_id) =>
+                                        setLectureToMediumId(lecture.id, medium_id)
+                                    }
+                                    isDownloading={isDownloading}
+                                    downloadStatus={downloadStatus?.get(lecture.id)}
                                 />
                             ))}
                         </tbody>
diff --git a/src/videoag/course/Medium.tsx b/src/videoag/course/Medium.tsx
index e61da73ce754e29b667e99e362c41b27262b41be..3a7a195ae5d9332c6030885724a398e2cf320652 100644
--- a/src/videoag/course/Medium.tsx
+++ b/src/videoag/course/Medium.tsx
@@ -1,17 +1,13 @@
 import { Dropdown } from "react-bootstrap";
 
-import {
-    OMDelete,
-    OMEdit,
-    OMHistory,
-    EmbeddedOMFieldComponent,
-} from "@/videoag/object_management/OMConfigComponent";
-import { useEditMode } from "@/videoag/object_management/EditModeProvider";
+import { publish_medium } from "@/videoag/api/types";
 import { useLanguage } from "@/videoag/localization/LanguageProvider";
 import { filesizeToHuman } from "@/videoag/miscellaneous/Formatting";
+import { useEditMode } from "@/videoag/object_management/EditModeProvider";
+import { OMDelete, EmbeddedOMFieldComponent } from "@/videoag/object_management/OMConfigComponent";
 
 export function getMediumQualityName(medium: publish_medium): string | undefined {
-    switch(medium.medium_metadata.type) {
+    switch (medium.medium_metadata.type) {
         case "plain_video":
             return `${medium.medium_metadata.vertical_resolution}p`;
         case "plain_audio":
@@ -26,19 +22,16 @@ export interface NamedPublishMedium {
     medium: publish_medium;
 }
 
-
 export function getNamedPublishMedia(media: publish_medium[]): NamedPublishMedium[] {
     // TODO Add more properties from metadata in case non-unique names are generated by this
 
     const namedMedia = [];
     for (const medium of media) {
-        const nameParts = []
-        if (medium.title)
-            nameParts.push(medium.title);
+        const nameParts = [];
+        if (medium.title) nameParts.push(medium.title);
 
         const qualityName = getMediumQualityName(medium);
-        if (qualityName)
-            nameParts.push(qualityName);
+        if (qualityName) nameParts.push(qualityName);
 
         nameParts.push(`(${filesizeToHuman(medium.medium_metadata.file_size)})`);
 
@@ -80,50 +73,50 @@ export function PublishMediumList({ publish_media }: { publish_media: publish_me
 
     return (
         <ul className="p-0">
-            {getNamedPublishMedia(sortPublishMediaByQuality(publish_media)).map(({ medium, name }) => (
-                <li
-                    key={medium.id}
-                    className={
-                        "d-flex border align-items-center px-2 py-1 rounded " +
-                        (medium.visible === false ? "bg-danger-subtle" : "")
-                    }
-                >
-                    <span className="bi bi-download" />
-                    <a href={medium.url} className="mx-1">
-                        {name}
-                    </a>
-                    {editMode && (
-                        <>
-                            <div className="flex-fill" />
-                            <EmbeddedOMFieldComponent
-                                object_type="publish_medium"
-                                object_id={medium.id!}
-                                field_id="visible"
-                                field_type="boolean"
-                                initialValue={medium.visible}
-                                className="me-2 align-self-center py-0"
-                            />
-                            <OMDelete
-                                object_type="publish_medium"
-                                object_id={medium.id!}
-                                className="py-0"
-                            />
-                        </>
-                    )}
-                </li>
-            ))}
+            {getNamedPublishMedia(sortPublishMediaByQuality(publish_media)).map(
+                ({ medium, name }) => (
+                    <li
+                        key={medium.id}
+                        className={
+                            "d-flex border align-items-center px-2 py-1 rounded " +
+                            (medium.visible === false ? "bg-danger-subtle" : "")
+                        }
+                    >
+                        <span className="bi bi-download" />
+                        <a href={medium.url} className="mx-1">
+                            {name}
+                        </a>
+                        {editMode && (
+                            <>
+                                <div className="flex-fill" />
+                                <EmbeddedOMFieldComponent
+                                    object_type="publish_medium"
+                                    object_id={medium.id!}
+                                    field_id="visible"
+                                    field_type="boolean"
+                                    initialValue={medium.visible}
+                                    className="me-2 align-self-center py-0"
+                                />
+                                <OMDelete
+                                    object_type="publish_medium"
+                                    object_id={medium.id!}
+                                    className="py-0"
+                                />
+                            </>
+                        )}
+                    </li>
+                ),
+            )}
         </ul>
     );
 }
 
 export function PublishMediumDownloadButton({
     publish_media,
-    className,
     direction,
     ...props
 }: {
     publish_media: publish_medium[];
-    className?: string;
     direction: "up" | "down";
     [key: string]: any;
 }) {
@@ -131,50 +124,49 @@ export function PublishMediumDownloadButton({
     const { language } = useLanguage();
 
     const namedMedia = getNamedPublishMedia(getSortedDownloadablePublishMedia(publish_media));
-    if(namedMedia.length === 0)
-        return (<></>);
+    if (namedMedia.length === 0) return <></>;
 
     return (
-        <Dropdown drop={direction}>
-            <Dropdown.Toggle
-                variant="primary"
-                disabled={publish_media === undefined || publish_media.length === 0}
-                {...props}
-            >
-                {language.get("ui.generic.download")}
-            </Dropdown.Toggle>
-
-            <Dropdown.Menu>
-                {namedMedia.map(({ medium, name }) => {
-                    let bgColor = "";
-                    if (medium.visible === false)
-                        bgColor = "bg-danger-subtle";
-
-                    return (
-                        <div
-                            key={medium.id}
-                            className={"d-flex align-items-center " + bgColor}
-                        >
-                            <Dropdown.Item href={medium.url} download>
-                                {name}
-                            </Dropdown.Item>
-                            {editMode && (
-                                <>
-                                    <EmbeddedOMFieldComponent
-                                        object_type="publish_medium"
-                                        object_id={medium.id!}
-                                        field_id="visible"
-                                        field_type="boolean"
-                                        initialValue={medium.visible}
-                                        className="me-2"
-                                    />
-                                    <OMDelete object_type="publish_medium" object_id={medium.id!} />
-                                </>
-                            )}
-                        </div>
-                    );
-                })}
-            </Dropdown.Menu>
-        </Dropdown>
+        <span {...props}>
+            <Dropdown drop={direction}>
+                <Dropdown.Toggle
+                    variant="primary"
+                    disabled={publish_media === undefined || publish_media.length === 0}
+                >
+                    {language.get("ui.generic.download")}
+                </Dropdown.Toggle>
+
+                <Dropdown.Menu>
+                    {namedMedia.map(({ medium, name }) => {
+                        let bgColor = "";
+                        if (medium.visible === false) bgColor = "bg-danger-subtle";
+
+                        return (
+                            <div key={medium.id} className={"d-flex align-items-center " + bgColor}>
+                                <Dropdown.Item href={medium.url} download>
+                                    {name}
+                                </Dropdown.Item>
+                                {editMode && (
+                                    <>
+                                        <EmbeddedOMFieldComponent
+                                            object_type="publish_medium"
+                                            object_id={medium.id!}
+                                            field_id="visible"
+                                            field_type="boolean"
+                                            initialValue={medium.visible}
+                                            className="me-2"
+                                        />
+                                        <OMDelete
+                                            object_type="publish_medium"
+                                            object_id={medium.id!}
+                                        />
+                                    </>
+                                )}
+                            </div>
+                        );
+                    })}
+                </Dropdown.Menu>
+            </Dropdown>
+        </span>
     );
-}
\ No newline at end of file
+}
diff --git a/src/videoag/form/TypeEditor.tsx b/src/videoag/form/TypeEditor.tsx
index 176077727c773883f58512850abab67207b91c73..52deacfe6e0f2c013ff7bc3b0a58764f0538ac7a 100644
--- a/src/videoag/form/TypeEditor.tsx
+++ b/src/videoag/form/TypeEditor.tsx
@@ -30,7 +30,7 @@ export function StringEditor({
     minLength?: int;
     maxLength?: int;
     allowUndefined?: boolean;
-    regex?: RegExp;
+    regex?: RegExp | string;
     regexInvalidMessage?: string | React.ReactNode;
 }) {
     useEffect(() => {
@@ -43,7 +43,11 @@ export function StringEditor({
         if (val === undefined) return allowUndefined === true;
         if (minLength !== undefined && val.length < minLength) return false;
         if (maxLength !== undefined && val.length > maxLength) return false;
-        if (regex !== undefined && !regex.test(val)) return false;
+        if (regex !== undefined) {
+            const regexRes = val.match(regex);
+            if (regexRes === null) return false;
+            if (regexRes[0].length != val.length) return false; // No full match
+        }
         return true;
     };
     const update = (val: any, affectNow: boolean) => {
diff --git a/src/videoag/miscellaneous/Util.tsx b/src/videoag/miscellaneous/Util.tsx
index 9fc94cc2bfc42f8d3dd618371e873c30cd2ced39..92bfc59de61bfe22be0dbb508b638480461cd0bd 100644
--- a/src/videoag/miscellaneous/Util.tsx
+++ b/src/videoag/miscellaneous/Util.tsx
@@ -47,7 +47,7 @@ export function mapMap<K, V, R>(map: Map<K, V>, func: (val: V) => R): Map<K, R>
     return newMap;
 }
 
-export function showInfoToast(message: React.ReactNode, autoClose: boolean) {
+export function showInfoToast(message: React.ReactNode, autoClose: number | false = 5000) {
     toast.info(message, {
         position: "top-center",
         autoClose: autoClose,
diff --git a/src/videoag/object_management/OMConfigComponent.tsx b/src/videoag/object_management/OMConfigComponent.tsx
index bfbb29a83f8e5ca9ab0d8eb27062349afff05df8..b10e96d21d6efda22d4041ca724abec02828d9e6 100644
--- a/src/videoag/object_management/OMConfigComponent.tsx
+++ b/src/videoag/object_management/OMConfigComponent.tsx
@@ -250,9 +250,12 @@ export function EmbeddedOMFieldComponent({
         switch (field_type) {
             case "string":
                 if (editMode && !value)
-                    formatted = <p><i className="text-muted">{"<empty>"}</i></p>
-                else
-                    formatted = <StylizedText markdown={allowMarkdown}>{value}</StylizedText>;
+                    formatted = (
+                        <p>
+                            <i className="text-muted">{"<empty>"}</i>
+                        </p>
+                    );
+                else formatted = <StylizedText markdown={allowMarkdown}>{value}</StylizedText>;
                 break;
             case "datetime":
                 formatted = datetimeToString(value);