Skip to content
Snippets Groups Projects
Commit 7ae0111b authored by Simon Künzel's avatar Simon Künzel
Browse files

Split Type Editor into multiple files

parent 93a571f0
No related branches found
No related tags found
No related merge requests found
import React, { useEffect, useRef, useState } from "react";
import Dropdown from "react-bootstrap/Dropdown";
import type {
GetUsersResponse,
field_description,
view_permissions,
user,
int,
} from "@/videoag/api/types";
import { useApi } from "@/videoag/api/ApiProvider";
import { showError } from "@/videoag/error/ErrorDisplay";
import type { int } from "@/videoag/api/types";
import { useLanguage } from "@/videoag/localization/LanguageProvider";
import { deepEquals } from "@/videoag/miscellaneous/Util";
type EditorArgs = {
export type EditorArgs = {
value?: any;
//affectNow specifies whether value should be applied (e.g. reload data, apply filter) immediately (e.g. user hit enter)
//or a bit later (debounced) (e.g. user typed in text field)
......@@ -23,79 +13,6 @@ type EditorArgs = {
hideErrors?: boolean;
};
export function OMFieldEditor({
description,
...args
}: {
description: field_description;
} & EditorArgs) {
const { value } = args;
switch (description.type) {
// TODO check which types actually exist now
case "string":
return (
<StringEditor
{...args}
minLength={description.min_length}
maxLength={description.max_length}
regex={description.pattern ? new RegExp(description.pattern) : undefined}
regexInvalidMessage={
description.pattern &&
`Invalid value. Must match pattern: ${description.pattern}`
}
/>
);
case "boolean":
return <BooleanEditor {...args} />;
case "int":
return (
<IntEditor
{...args}
minValue={description.min_value}
maxValue={description.max_value}
/>
);
case "datetime":
return <DatetimeEditor {...args} mayBeNull={description.may_be_null} />;
case "enum":
return (
<ChooserEditor
{...args}
langKeyPrefix={`enum.${description.name!}`}
possibleValues={description.enums!}
/>
);
case "view_permissions":
return <ViewPermissionsEditor {...args} />;
case "object_list":
// TODO other objects
if (description.object_type === "user") {
return <UserIdListEditor {...args} />;
}
return (
<span>
Unknown field type {description.type}. {JSON.stringify(value)}
</span>
);
// TODO media_process
// TODO object
default:
return (
<span>
Unknown field type {description.type}. {JSON.stringify(value)}
</span>
);
}
}
export function StringEditor({
value,
updateValue,
......@@ -427,423 +344,3 @@ export function ChooserEditor({
</select>
);
}
export function UserIdListEditor({
value,
updateValue,
disabled,
autoFocus,
hideErrors,
}: EditorArgs) {
const api = useApi();
const knownValue = useRef<int[]>();
const [users, setUsers] = useState<GetUsersResponse>();
const [search, setSearch] = useState<string>("");
useEffect(() => {
if (knownValue.current === value) return;
// Value was not changed by us, reset internal state
knownValue.current = value;
setUsers(undefined);
setSearch("");
api.getUsers()
.then(setUsers)
.catch((err) => {
showError(err, "Unable to load users");
});
}, [api, knownValue, value]);
useEffect(() => {
if (value !== undefined) return;
const newValue: int[] = [];
knownValue.current = newValue;
updateValue(newValue, true, true);
});
const selectedArray = value ?? [];
let userList = selectedArray.length > 0 ? selectedArray.join(", ") : "None";
if (users !== undefined && selectedArray.length > 0) {
userList = selectedArray
.map((id: int) => {
const user = users.users.find((u: user) => u.id === id);
if (user === undefined) return `#${id}`;
return `${user.display_name} (${user.handle}#${user.id})`;
})
.join(", ");
}
if (userList.length > 100) userList = userList.slice(0, 100) + "...";
const searchTerm = search.toLowerCase();
const userElements = (users === undefined ? [] : users.users).flatMap((user: user) => {
if (
!user.display_name.toLowerCase().includes(searchTerm) &&
!user.full_name.toLowerCase().includes(searchTerm) &&
!user.handle.toLowerCase().includes(searchTerm)
)
return [];
const toggle = () => {
const index = selectedArray.indexOf(user.id);
let newArray = selectedArray.slice();
if (index === -1) {
newArray.splice(newArray.length, 0, user.id); //Add user.id at end
} else {
newArray.splice(index, 1); //Remove user.id
}
knownValue.current = newArray;
updateValue(newArray, true, true);
};
return [
<tr key={user.id} className={selectedArray.includes(user.id) ? "table-primary" : ""}>
<td>
<input
type="checkbox"
checked={selectedArray.includes(user.id)}
onChange={toggle}
/>
</td>
<td onClick={toggle} className="pe-auto" style={{ cursor: "pointer" }}>
<span>{`${user.display_name} (${user.handle}#${user.id})`}</span>
</td>
</tr>,
];
});
return (
<Dropdown>
<Dropdown.Toggle variant="primary" style={{ textWrap: "wrap" }} disabled={disabled}>
{userList}
</Dropdown.Toggle>
<Dropdown.Menu className="p-1">
<div className="input-group mb-2 p-1">
<span className="input-group-text">
<i className="bi bi-search" />
</span>
<input
type="text"
className="form-control"
placeholder="Username"
value={search}
autoFocus
onChange={(e) => setSearch(e.target.value)}
/>
<button
type="button"
className="btn btn-outline-secondary"
onClick={() => setSearch("")}
disabled={search === ""}
>
<i className="bi bi-x-circle" />
</button>
</div>
{users === undefined ? (
<div className="spinner-border" role="status" />
) : (
<>
{userElements.length === 0 && (
<div className="text-center">No users found</div>
)}
<div className="overflow-auto" style={{ maxHeight: "500px" }}>
<table className="table table-sm table-hover w-100">
<thead>
<tr>
<th></th>
<th className="w-100"></th>
</tr>
</thead>
<tbody>{userElements}</tbody>
</table>
</div>
</>
)}
</Dropdown.Menu>
</Dropdown>
);
}
function createListEditor<V>(
disabled: boolean,
list: V[],
defaultNewValue: V,
onNewValueList: (newList: V[]) => void,
elementFactory: (value: V, onNewValue: (newVal: V) => void) => React.ReactNode,
) {
return (
<>
{list.map((value: V, index: int) => (
<div key={index} className="d-block">
{elementFactory(value, (newVal: V) =>
onNewValueList(
list.map((subValue: V, subIndex: int) =>
index === subIndex ? newVal : subValue,
),
),
)}
<button
type="button"
className={"btn btn-outline-danger m-1"}
onClick={(e) => {
const newList = list.slice();
newList.splice(index, 1);
onNewValueList(newList);
}}
title="Delete"
>
<i className="bi bi-trash-fill" />
</button>
</div>
))}
<button
className="btn btn-secondary"
type="button"
title="Add entry"
onClick={(e) => {
onNewValueList(list.concat([defaultNewValue]));
}}
disabled={disabled}
>
+
</button>
</>
);
}
export function ViewPermissionsEditor({
value,
updateValue,
disabled,
autoFocus,
hideErrors,
}: EditorArgs) {
const permTypes = ["inherit", "public", "private", "authentication"];
// We need to store the current value locally since there is no order for the passwords and it would be really
// annoying if the order changes while typing
// This also gives the benefit of storing values for types which are currently not selected
type view_permissions_with_invalid = Omit<view_permissions, "passwords"> & {
passwords?: null | { [key: string]: string };
};
const { language } = useLanguage();
const knownValue = useRef<view_permissions_with_invalid>();
const [allDataValue, setAllDataValue] = useState<view_permissions_with_invalid>({
type: "inherit",
});
const [passwordsArray, setPasswordsArray] = useState<[string, string][]>([]);
useEffect(() => {
if (deepEquals(value, knownValue.current)) return;
// Value was not changed by us, update internal state
const effectiveValue = value ?? { type: "inherit" };
const passwordsMap = effectiveValue.passwords ?? {};
setPasswordsArray(
Object.keys(passwordsMap).map((username: string) => [username, passwordsMap[username]]),
);
setAllDataValue(effectiveValue);
knownValue.current = effectiveValue;
if (effectiveValue !== value) updateValue(effectiveValue, true, true);
}, [value, updateValue]);
const updateData = (key: string, newData: any) =>
setAllDataValue((prev: view_permissions_with_invalid | undefined) => {
let isValid = true;
prev = prev ?? { type: "inherit" };
let newAllData: view_permissions_with_invalid = { ...prev, [key]: newData };
if (key === "passwords") {
setPasswordsArray(newData);
let newMap: { [key: string]: string } | null = {};
for (let ele of newData) {
if (newMap[ele[0]] !== undefined) {
newMap = null;
break;
}
newMap[ele[0]] = ele[1];
}
newAllData = { ...prev, passwords: newMap };
}
let newValue: view_permissions_with_invalid = { type: newAllData.type };
if (newValue.type === "authentication") {
newValue.rwth_authentication = newAllData.rwth_authentication ?? false;
newValue.fsmpi_authentication = newAllData.fsmpi_authentication ?? false;
newValue.moodle_course_ids = newAllData.moodle_course_ids ?? [];
newValue.passwords = newAllData.passwords !== undefined ? newAllData.passwords : {};
if (newValue.passwords === null) {
isValid = false;
} else if (
!newValue.rwth_authentication &&
!newValue.fsmpi_authentication &&
newValue.moodle_course_ids.length === 0 &&
Object.keys(newValue.passwords).length === 0
) {
isValid = false;
}
}
knownValue.current = newValue;
updateValue(newValue, isValid, true);
return newAllData;
});
let invalidMessage = undefined;
if (
hideErrors !== true &&
allDataValue?.type === "authentication" &&
allDataValue.passwords !== null &&
!(
allDataValue.rwth_authentication === true ||
allDataValue.fsmpi_authentication === true ||
(allDataValue.moodle_course_ids ?? []).length > 0 ||
Object.keys(allDataValue.passwords ?? {}).length > 0
)
) {
invalidMessage = (
<div className="d-block invalid-feedback">
Für Typ <i>Authentifizierung</i> muss mindestens eine Authentifizierungsmethode
vorhanden sein
</div>
);
}
let passwordsInvalidMessage;
if (hideErrors !== true && allDataValue?.type === "authentication") {
const passwordsMap: { [key: string]: null } = {};
for (let ele of passwordsArray) {
if (passwordsMap[ele[0]] !== undefined) {
passwordsInvalidMessage = (
<div className="d-block invalid-feedback">
Doppelter Nutzername &apos;{ele[0]}&apos;
</div>
);
break;
}
passwordsMap[ele[0]] = null;
}
}
return (
<div className="d-inline-grid">
<select
className="w-auto form-select"
value={allDataValue?.type ?? "inherit"}
onChange={(e) => updateData("type", e.target.value)}
disabled={disabled}
autoFocus={autoFocus}
>
{permTypes.map((type) => (
<option key={type} value={type}>
{language.get(`ui.view_permissions.type.${type}`)}
</option>
))}
</select>
{allDataValue?.type === "authentication" && (
<table className="w-100 table">
<thead>
<tr>
<th scope="col" style={{ width: "30%" }} />
<th scope="col" style={{ width: "70%" }} />
</tr>
</thead>
<tbody>
<tr>
<td>RWTH authentication</td>
<td>
<input
type="checkbox"
className="form-check-input"
name="rwth_authentication"
defaultChecked={allDataValue?.rwth_authentication ?? false}
disabled={disabled}
onChange={(e) =>
updateData("rwth_authentication", e.target.checked)
}
/>
</td>
</tr>
<tr>
<td>FSMPI authentication</td>
<td>
<input
type="checkbox"
className="form-check-input"
name="fsmpi_authentication"
defaultChecked={allDataValue?.fsmpi_authentication ?? false}
disabled={disabled}
onChange={(e) =>
updateData("fsmpi_authentication", e.target.checked)
}
/>
</td>
</tr>
<tr>
<td>Moodle course ids</td>
<td>
{createListEditor<int>(
disabled ?? false,
allDataValue?.moodle_course_ids ?? [],
0,
(newList: int[]) => updateData("moodle_course_ids", newList),
(value: int, onNewValue: (newVal: int) => void) => (
<input
className="mb-1"
type="number"
value={value}
onChange={(e) => {
onNewValue(parseInt(e.target.value));
}}
disabled={disabled ?? false}
/>
),
)}
</td>
</tr>
<tr>
<td>Passwords</td>
<td>
<div className="d-inline-grid">
<span>
{createListEditor<[string, string]>(
disabled ?? false,
passwordsArray,
["", ""],
(newList: [string, string][]) =>
updateData("passwords", newList),
(
value: [string, string],
onNewValue: (newVal: [string, string]) => void,
) => (
<>
<input
className="me-1"
type="text"
placeholder="Username"
value={value[0]}
onChange={(e) =>
onNewValue([e.target.value, value[1]])
}
disabled={disabled ?? false}
/>
<input
type="text"
placeholder="Password"
value={value[1]}
onChange={(e) =>
onNewValue([value[0], e.target.value])
}
disabled={disabled ?? false}
/>
</>
),
)}
</span>
{passwordsInvalidMessage}
</div>
</td>
</tr>
</tbody>
</table>
)}
{invalidMessage}
</div>
);
}
import type React from "react";
import { OverlayTrigger, Tooltip } from "react-bootstrap";
import { toast, Bounce } from "react-toastify";
export function deepEquals(value: any, other: any) {
if (value === null || other === null) {
......@@ -46,6 +47,18 @@ 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) {
toast.info(message, {
position: "top-center",
autoClose: autoClose,
closeOnClick: false,
draggable: false,
progress: undefined,
theme: "light", // Colors overridden in css
transition: Bounce,
});
}
export function TooltipButton({
children,
iconClassName,
......
......@@ -11,7 +11,6 @@ import type {
} from "@/videoag/api/types";
import { useApi } from "@/videoag/api/ApiProvider";
import { showError, showErrorToast } from "@/videoag/error/ErrorDisplay";
import { OMFieldEditor } from "@/videoag/form/TypeEditor";
import { useLanguage } from "@/videoag/localization/LanguageProvider";
import { useReloadBoundary } from "@/videoag/miscellaneous/ReloadBoundary";
import { useDebounceUpdateData } from "@/videoag/miscellaneous/PromiseHelpers";
......@@ -22,6 +21,7 @@ import StopNavigation from "@/videoag/miscellaneous/StopNavigation";
import { basePath } from "@/../basepath";
import { LockEditMode, useEditMode } from "./EditModeProvider";
import { OMFieldEditor } from "./OMFieldEditor";
const markdownSupportingFields = [
"announcement.text",
......@@ -249,6 +249,9 @@ export function EmbeddedOMFieldComponent({
let formatted: any = "Value of unknown type: " + value;
switch (field_type) {
case "string":
if (editMode && !value)
formatted = <p><i className="text-muted">{"<empty>"}</i></p>
else
formatted = <StylizedText markdown={allowMarkdown}>{value}</StylizedText>;
break;
case "datetime":
......@@ -625,15 +628,6 @@ export function OMEdit({
)}
{config?.fields !== undefined && (
<>
<div className="alert alert-warning">
{/* TODO: remove after release */}
Achtung: Die alte Webseite kann kein Markdown rendern, nur HTML.
Wenn hier also Markdown verwendet wird, wird dies auf der alten
Seite nicht richtig gerendert. Während der Transition werden aber
auf der neuen Seite die folgenden HTML-Tags gerendert: a, b, br, p
und strong (Welche auf der alten Seite auch korrekt angezeigt
werden)
</div>
<small className="text-muted">
Tipp: Viele Felder können im Edit-Mode auch direkt durch einen
Doppelklick bearbeitet werden
......
import React from "react";
import type { field_description } from "@/videoag/api/types";
import {
EditorArgs,
StringEditor,
BooleanEditor,
IntEditor,
DatetimeEditor,
ChooserEditor,
} from "@/videoag/form/TypeEditor";
import { UserIdListEditor } from "./ObjectEditor";
import ViewPermissionsEditor from "./ViewPermissionsEditor";
export function OMFieldEditor({
description,
...args
}: {
description: field_description;
} & EditorArgs) {
const { value } = args;
switch (description.type) {
case "string":
return (
<StringEditor
{...args}
minLength={description.min_length}
maxLength={description.max_length}
regex={description.pattern ? new RegExp(description.pattern) : undefined}
regexInvalidMessage={
description.pattern &&
`Invalid value. Must match pattern: ${description.pattern}`
}
/>
);
case "boolean":
return <BooleanEditor {...args} />;
case "int":
return (
<IntEditor
{...args}
minValue={description.min_value}
maxValue={description.max_value}
/>
);
case "datetime":
return <DatetimeEditor {...args} mayBeNull={description.may_be_null} />;
case "enum":
return (
<ChooserEditor
{...args}
langKeyPrefix={`enum.${description.name!}`}
possibleValues={description.enums!}
/>
);
case "view_permissions":
return <ViewPermissionsEditor {...args} />;
case "object_list":
// TODO other objects
if (description.object_type === "user") {
return <UserIdListEditor {...args} />;
}
return (
<span>
Unknown field type {description.type}. {JSON.stringify(value)}
</span>
);
// TODO media_process
// TODO object
default:
return (
<span>
Unknown field type {description.type}. {JSON.stringify(value)}
</span>
);
}
}
import React, { useEffect, useRef, useState } from "react";
import Dropdown from "react-bootstrap/Dropdown";
import type { GetUsersResponse, user, int } from "@/videoag/api/types";
import { useApi } from "@/videoag/api/ApiProvider";
import { EditorArgs } from "@/videoag/form/TypeEditor";
import { showError } from "@/videoag/error/ErrorDisplay";
export function UserIdListEditor({
value,
updateValue,
disabled,
autoFocus,
hideErrors,
}: EditorArgs) {
const api = useApi();
const knownValue = useRef<int[]>();
const [users, setUsers] = useState<GetUsersResponse>();
const [search, setSearch] = useState<string>("");
useEffect(() => {
if (knownValue.current === value) return;
// Value was not changed by us, reset internal state
knownValue.current = value;
setUsers(undefined);
setSearch("");
api.getUsers()
.then(setUsers)
.catch((err) => {
showError(err, "Unable to load users");
});
}, [api, knownValue, value]);
useEffect(() => {
if (value !== undefined) return;
const newValue: int[] = [];
knownValue.current = newValue;
updateValue(newValue, true, true);
});
const selectedArray = value ?? [];
let userList = selectedArray.length > 0 ? selectedArray.join(", ") : "None";
if (users !== undefined && selectedArray.length > 0) {
userList = selectedArray
.map((id: int) => {
const user = users.users.find((u: user) => u.id === id);
if (user === undefined) return `#${id}`;
return `${user.display_name} (${user.handle}#${user.id})`;
})
.join(", ");
}
if (userList.length > 100) userList = userList.slice(0, 100) + "...";
const searchTerm = search.toLowerCase();
const userElements = (users === undefined ? [] : users.users).flatMap((user: user) => {
if (
!user.display_name.toLowerCase().includes(searchTerm) &&
!user.full_name.toLowerCase().includes(searchTerm) &&
!user.handle.toLowerCase().includes(searchTerm)
)
return [];
const toggle = () => {
const index = selectedArray.indexOf(user.id);
let newArray = selectedArray.slice();
if (index === -1) {
newArray.splice(newArray.length, 0, user.id); //Add user.id at end
} else {
newArray.splice(index, 1); //Remove user.id
}
knownValue.current = newArray;
updateValue(newArray, true, true);
};
return [
<tr key={user.id} className={selectedArray.includes(user.id) ? "table-primary" : ""}>
<td>
<input
type="checkbox"
checked={selectedArray.includes(user.id)}
onChange={toggle}
/>
</td>
<td onClick={toggle} className="pe-auto" style={{ cursor: "pointer" }}>
<span>{`${user.display_name} (${user.handle}#${user.id})`}</span>
</td>
</tr>,
];
});
return (
<Dropdown>
<Dropdown.Toggle variant="primary" style={{ textWrap: "wrap" }} disabled={disabled}>
{userList}
</Dropdown.Toggle>
<Dropdown.Menu className="p-1">
<div className="input-group mb-2 p-1">
<span className="input-group-text">
<i className="bi bi-search" />
</span>
<input
type="text"
className="form-control"
placeholder="Username"
value={search}
autoFocus
onChange={(e) => setSearch(e.target.value)}
/>
<button
type="button"
className="btn btn-outline-secondary"
onClick={() => setSearch("")}
disabled={search === ""}
>
<i className="bi bi-x-circle" />
</button>
</div>
{users === undefined ? (
<div className="spinner-border" role="status" />
) : (
<>
{userElements.length === 0 && (
<div className="text-center">No users found</div>
)}
<div className="overflow-auto" style={{ maxHeight: "500px" }}>
<table className="table table-sm table-hover w-100">
<thead>
<tr>
<th></th>
<th className="w-100"></th>
</tr>
</thead>
<tbody>{userElements}</tbody>
</table>
</div>
</>
)}
</Dropdown.Menu>
</Dropdown>
);
}
import React, { useEffect, useRef, useState } from "react";
import type { view_permissions, int } from "@/videoag/api/types";
import { EditorArgs } from "@/videoag/form/TypeEditor";
import { useLanguage } from "@/videoag/localization/LanguageProvider";
import { deepEquals } from "@/videoag/miscellaneous/Util";
function createListEditor<V>(
disabled: boolean,
list: V[],
defaultNewValue: V,
onNewValueList: (newList: V[]) => void,
elementFactory: (value: V, onNewValue: (newVal: V) => void) => React.ReactNode,
) {
return (
<>
{list.map((value: V, index: int) => (
<div key={index} className="d-block">
{elementFactory(value, (newVal: V) =>
onNewValueList(
list.map((subValue: V, subIndex: int) =>
index === subIndex ? newVal : subValue,
),
),
)}
<button
type="button"
className={"btn btn-outline-danger m-1"}
onClick={(e) => {
const newList = list.slice();
newList.splice(index, 1);
onNewValueList(newList);
}}
title="Delete"
>
<i className="bi bi-trash-fill" />
</button>
</div>
))}
<button
className="btn btn-secondary"
type="button"
title="Add entry"
onClick={(e) => {
onNewValueList(list.concat([defaultNewValue]));
}}
disabled={disabled}
>
+
</button>
</>
);
}
export default function ViewPermissionsEditor({
value,
updateValue,
disabled,
autoFocus,
hideErrors,
}: EditorArgs) {
const permTypes = ["inherit", "public", "private", "authentication"];
// We need to store the current value locally since there is no order for the passwords and it would be really
// annoying if the order changes while typing
// This also gives the benefit of storing values for types which are currently not selected
type view_permissions_with_invalid = Omit<view_permissions, "passwords"> & {
passwords?: null | { [key: string]: string };
};
const { language } = useLanguage();
const knownValue = useRef<view_permissions_with_invalid>();
const [allDataValue, setAllDataValue] = useState<view_permissions_with_invalid>({
type: "inherit",
});
const [passwordsArray, setPasswordsArray] = useState<[string, string][]>([]);
useEffect(() => {
if (deepEquals(value, knownValue.current)) return;
// Value was not changed by us, update internal state
const effectiveValue = value ?? { type: "inherit" };
const passwordsMap = effectiveValue.passwords ?? {};
setPasswordsArray(
Object.keys(passwordsMap).map((username: string) => [username, passwordsMap[username]]),
);
setAllDataValue(effectiveValue);
knownValue.current = effectiveValue;
if (effectiveValue !== value) updateValue(effectiveValue, true, true);
}, [value, updateValue]);
const updateData = (key: string, newData: any) =>
setAllDataValue((prev: view_permissions_with_invalid | undefined) => {
let isValid = true;
prev = prev ?? { type: "inherit" };
let newAllData: view_permissions_with_invalid = { ...prev, [key]: newData };
if (key === "passwords") {
setPasswordsArray(newData);
let newMap: { [key: string]: string } | null = {};
for (let ele of newData) {
if (newMap[ele[0]] !== undefined) {
newMap = null;
break;
}
newMap[ele[0]] = ele[1];
}
newAllData = { ...prev, passwords: newMap };
}
let newValue: view_permissions_with_invalid = { type: newAllData.type };
if (newValue.type === "authentication") {
newValue.rwth_authentication = newAllData.rwth_authentication ?? false;
newValue.fsmpi_authentication = newAllData.fsmpi_authentication ?? false;
newValue.moodle_course_ids = newAllData.moodle_course_ids ?? [];
newValue.passwords = newAllData.passwords !== undefined ? newAllData.passwords : {};
if (newValue.passwords === null) {
isValid = false;
} else if (
!newValue.rwth_authentication &&
!newValue.fsmpi_authentication &&
newValue.moodle_course_ids.length === 0 &&
Object.keys(newValue.passwords).length === 0
) {
isValid = false;
}
}
knownValue.current = newValue;
updateValue(newValue, isValid, true);
return newAllData;
});
let invalidMessage = undefined;
if (
hideErrors !== true &&
allDataValue?.type === "authentication" &&
allDataValue.passwords !== null &&
!(
allDataValue.rwth_authentication === true ||
allDataValue.fsmpi_authentication === true ||
(allDataValue.moodle_course_ids ?? []).length > 0 ||
Object.keys(allDataValue.passwords ?? {}).length > 0
)
) {
invalidMessage = (
<div className="d-block invalid-feedback">
Für Typ <i>Authentifizierung</i> muss mindestens eine Authentifizierungsmethode
vorhanden sein
</div>
);
}
let passwordsInvalidMessage;
if (hideErrors !== true && allDataValue?.type === "authentication") {
const passwordsMap: { [key: string]: null } = {};
for (let ele of passwordsArray) {
if (passwordsMap[ele[0]] !== undefined) {
passwordsInvalidMessage = (
<div className="d-block invalid-feedback">
Doppelter Nutzername &apos;{ele[0]}&apos;
</div>
);
break;
}
passwordsMap[ele[0]] = null;
}
}
return (
<div className="d-inline-grid">
<select
className="w-auto form-select"
value={allDataValue?.type ?? "inherit"}
onChange={(e) => updateData("type", e.target.value)}
disabled={disabled}
autoFocus={autoFocus}
>
{permTypes.map((type) => (
<option key={type} value={type}>
{language.get(`ui.view_permissions.type.${type}`)}
</option>
))}
</select>
{allDataValue?.type === "authentication" && (
<table className="w-100 table">
<thead>
<tr>
<th scope="col" style={{ width: "30%" }} />
<th scope="col" style={{ width: "70%" }} />
</tr>
</thead>
<tbody>
<tr>
<td>RWTH authentication</td>
<td>
<input
type="checkbox"
className="form-check-input"
name="rwth_authentication"
defaultChecked={allDataValue?.rwth_authentication ?? false}
disabled={disabled}
onChange={(e) =>
updateData("rwth_authentication", e.target.checked)
}
/>
</td>
</tr>
<tr>
<td>FSMPI authentication</td>
<td>
<input
type="checkbox"
className="form-check-input"
name="fsmpi_authentication"
defaultChecked={allDataValue?.fsmpi_authentication ?? false}
disabled={disabled}
onChange={(e) =>
updateData("fsmpi_authentication", e.target.checked)
}
/>
</td>
</tr>
<tr>
<td>Moodle course ids</td>
<td>
{createListEditor<int>(
disabled ?? false,
allDataValue?.moodle_course_ids ?? [],
0,
(newList: int[]) => updateData("moodle_course_ids", newList),
(value: int, onNewValue: (newVal: int) => void) => (
<input
className="mb-1"
type="number"
value={value}
onChange={(e) => {
onNewValue(parseInt(e.target.value));
}}
disabled={disabled ?? false}
/>
),
)}
</td>
</tr>
<tr>
<td>Passwords</td>
<td>
<div className="d-inline-grid">
<span>
{createListEditor<[string, string]>(
disabled ?? false,
passwordsArray,
["", ""],
(newList: [string, string][]) =>
updateData("passwords", newList),
(
value: [string, string],
onNewValue: (newVal: [string, string]) => void,
) => (
<>
<input
className="me-1"
type="text"
placeholder="Username"
value={value[0]}
onChange={(e) =>
onNewValue([e.target.value, value[1]])
}
disabled={disabled ?? false}
/>
<input
type="text"
placeholder="Password"
value={value[1]}
onChange={(e) =>
onNewValue([value[0], e.target.value])
}
disabled={disabled ?? false}
/>
</>
),
)}
</span>
{passwordsInvalidMessage}
</div>
</td>
</tr>
</tbody>
</table>
)}
{invalidMessage}
</div>
);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment