1364 lines
No EOL
54 KiB
TypeScript
1364 lines
No EOL
54 KiB
TypeScript
import * as React from "react";
|
|
import {useEffect, useRef, useState} from "react";
|
|
import {FlatInputField} from "tc-shared/ui/react-elements/InputField";
|
|
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
|
import {EventHandler, ReactEventHandler, Registry} from "tc-shared/events";
|
|
import {Switch} from "tc-shared/ui/react-elements/Switch";
|
|
import PermissionType from "tc-shared/permission/PermissionType";
|
|
import * as log from "tc-shared/log";
|
|
import {LogCategory} from "tc-shared/log";
|
|
|
|
import ResizeObserver from "resize-observer-polyfill";
|
|
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
|
|
import {Button} from "tc-shared/ui/react-elements/Button";
|
|
import {IconRenderer, RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon";
|
|
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
|
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
|
|
import {copy_to_clipboard} from "tc-shared/utils/helpers";
|
|
import {createInfoModal} from "tc-shared/ui/elements/Modal";
|
|
import {getIconManager} from "tc-shared/file/Icons";
|
|
|
|
const cssStyle = require("./PermissionEditor.scss");
|
|
|
|
export interface EditorGroupedPermissions {
|
|
groupId: string,
|
|
groupName: string,
|
|
permissions: {
|
|
id: number,
|
|
name: string;
|
|
description: string;
|
|
}[],
|
|
children: EditorGroupedPermissions[]
|
|
}
|
|
|
|
type PermissionEditorMode = "unset" | "no-permissions" | "normal";
|
|
|
|
export interface PermissionEditorEvents {
|
|
action_set_mode: { mode: PermissionEditorMode, failedPermission?: string }
|
|
action_toggle_client_button: { visible: boolean },
|
|
action_toggle_client_list: { visible: boolean },
|
|
|
|
action_set_filter: { filter?: string }
|
|
action_set_assigned_only: { value: boolean }
|
|
|
|
action_set_default_value: { value: number },
|
|
|
|
action_open_icon_select: { iconId?: number }
|
|
action_set_senseless_permissions: { permissions: string[] }
|
|
|
|
action_remove_permissions: {
|
|
permissions: {
|
|
name: string;
|
|
mode: "value" | "grant";
|
|
}[]
|
|
}
|
|
action_remove_permissions_result: {
|
|
permissions: {
|
|
name: string;
|
|
mode: "value" | "grant";
|
|
success: boolean;
|
|
}[]
|
|
}
|
|
|
|
action_set_permissions: {
|
|
permissions: {
|
|
name: string;
|
|
|
|
mode: "value" | "grant";
|
|
|
|
value?: number;
|
|
flagNegate?: boolean;
|
|
flagSkip?: boolean;
|
|
}[]
|
|
}
|
|
action_set_permissions_result: {
|
|
permissions: {
|
|
name: string;
|
|
|
|
mode: "value" | "grant";
|
|
|
|
newValue?: number; /* undefined if it didnt worked */
|
|
flagNegate?: boolean;
|
|
flagSkip?: boolean;
|
|
}[]
|
|
}
|
|
|
|
action_toggle_group: {
|
|
groupId: string | null; /* if null, all groups are affected */
|
|
collapsed: boolean;
|
|
}
|
|
|
|
action_start_permission_edit: {
|
|
target: "value" | "grant";
|
|
permission: string;
|
|
defaultValue: number;
|
|
},
|
|
|
|
action_add_permission_group: {
|
|
groupId: string,
|
|
mode: "value" | "grant";
|
|
},
|
|
action_remove_permission_group: {
|
|
groupId: string
|
|
mode: "value" | "grant";
|
|
}
|
|
|
|
query_permission_list: {},
|
|
query_permission_list_result: {
|
|
hideSenselessPermissions: boolean;
|
|
permissions: EditorGroupedPermissions[]
|
|
},
|
|
|
|
query_permission_values: {},
|
|
query_permission_values_result: {
|
|
status: "error" | "success"; /* no perms will cause a action_set_mode event with no permissions */
|
|
|
|
error?: string;
|
|
permissions?: {
|
|
name: string;
|
|
value?: number;
|
|
flagNegate?: boolean;
|
|
flagSkip?: boolean;
|
|
granted?: number;
|
|
}[]
|
|
}
|
|
}
|
|
|
|
const ButtonIconPreview = (props: { events: Registry<PermissionEditorEvents>, connection: ConnectionHandler }) => {
|
|
const [iconId, setIconId] = useState(0);
|
|
const [unset, setUnset] = useState(true);
|
|
|
|
props.events.reactUse("action_set_mode", event => setUnset(event.mode !== "normal"));
|
|
|
|
props.events.reactUse("action_remove_permissions_result", event => {
|
|
const iconPermission = event.permissions.find(e => e.name === PermissionType.I_ICON_ID);
|
|
if (!iconPermission || !iconPermission.success) return;
|
|
|
|
if (iconPermission.mode === "value")
|
|
setIconId(0);
|
|
});
|
|
|
|
props.events.reactUse("action_set_permissions_result", event => {
|
|
const iconPermission = event.permissions.find(e => e.name === PermissionType.I_ICON_ID);
|
|
if (!iconPermission) return;
|
|
|
|
if (typeof iconPermission.newValue === "number")
|
|
setIconId(iconPermission.newValue);
|
|
});
|
|
|
|
props.events.reactUse("query_permission_values_result", event => {
|
|
if (event.status !== "success") {
|
|
setIconId(0);
|
|
return;
|
|
}
|
|
|
|
const permission = event.permissions.find(e => e.name === PermissionType.I_ICON_ID);
|
|
if (!permission) {
|
|
setIconId(0);
|
|
return;
|
|
}
|
|
|
|
if (typeof permission.value === "number") {
|
|
setIconId(permission.value >>> 0);
|
|
} else {
|
|
setIconId(0);
|
|
}
|
|
});
|
|
|
|
let icon;
|
|
if (!unset && iconId > 0) {
|
|
icon = <RemoteIconRenderer key={"icon-" + iconId} icon={getIconManager().resolveIcon(iconId, props.connection.getCurrentServerUniqueId(), props.connection.handlerId)} />;
|
|
}
|
|
|
|
return (
|
|
<div className={cssStyle.containerIconSelect}>
|
|
<div className={cssStyle.preview}
|
|
onClick={() => props.events.fire("action_open_icon_select", {iconId: iconId})}>
|
|
{icon}
|
|
</div>
|
|
<div className={cssStyle.containerDropdown}>
|
|
<div className={cssStyle.button}>
|
|
<div className="arrow down"/>
|
|
</div>
|
|
<div className={cssStyle.dropdown}>
|
|
{iconId ? <div className={cssStyle.entry} key={"edit-icon"}
|
|
onClick={() => props.events.fire("action_open_icon_select", {iconId: iconId})}>
|
|
<Translatable>Edit icon</Translatable>
|
|
</div> : undefined}
|
|
{iconId ? <div className={cssStyle.entry} key={"remove-icon"}
|
|
onClick={() => props.events.fire("action_remove_permissions", {
|
|
permissions: [{
|
|
name: PermissionType.I_ICON_ID,
|
|
mode: "value"
|
|
}]
|
|
})}>
|
|
<Translatable>Remove icon</Translatable>
|
|
</div> : undefined}
|
|
{!iconId ? <div className={cssStyle.entry} key={"add-icon"}
|
|
onClick={() => props.events.fire("action_open_icon_select", {iconId: 0})}>
|
|
<Translatable>Add icon</Translatable>
|
|
</div> : undefined}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const ClientListButton = (props: { events: Registry<PermissionEditorEvents> }) => {
|
|
const [visible, setVisible] = useState(true);
|
|
const [toggled, setToggled] = useState(false);
|
|
|
|
props.events.reactUse("action_toggle_client_button", event => setVisible(event.visible));
|
|
props.events.reactUse("action_toggle_client_list", event => setToggled(event.visible));
|
|
|
|
return <Button
|
|
key={"button-clients"}
|
|
className={cssStyle.clients + " " + (visible ? "" : cssStyle.hidden)}
|
|
color={"green"}
|
|
onClick={() => props.events.fire("action_toggle_client_list", {visible: !toggled})}>
|
|
{toggled ? <Translatable key={"hide"}>Hide clients in group</Translatable> :
|
|
<Translatable key={"show"}>Show clients in group</Translatable>}
|
|
</Button>
|
|
};
|
|
|
|
const MenuBar = (props: { events: Registry<PermissionEditorEvents>, connection: ConnectionHandler }) => {
|
|
return <div className={cssStyle.containerMenuBar}>
|
|
<ClientListButton events={props.events}/>
|
|
<FlatInputField
|
|
className={cssStyle.filter}
|
|
|
|
label={<Translatable>Filter permissions</Translatable>}
|
|
labelType={"floating"}
|
|
labelClassName={cssStyle.label}
|
|
labelFloatingClassName={cssStyle.labelFloating}
|
|
onInput={text => props.events.fire("action_set_filter", {filter: text})}
|
|
/>
|
|
<div className={cssStyle.options}>
|
|
<Switch initialState={false} label={<Translatable>Assigned only</Translatable>}
|
|
onChange={state => props.events.fire("action_set_assigned_only", {value: state})}/>
|
|
{ /* <Switch initialState={true} label={<Translatable>Editable only</Translatable>} /> */}
|
|
</div>
|
|
<ButtonIconPreview events={props.events} connection={props.connection}/>
|
|
</div>;
|
|
};
|
|
|
|
interface LinkedGroupedPermissions {
|
|
groupId: string;
|
|
groupName: string;
|
|
|
|
depth: number;
|
|
parent: LinkedGroupedPermissions | undefined;
|
|
children: LinkedGroupedPermissions[];
|
|
|
|
permissions: {
|
|
id: number;
|
|
name: string;
|
|
description: string;
|
|
|
|
elementVisible: boolean;
|
|
}[],
|
|
anyPermissionVisible: boolean;
|
|
|
|
nextGroup: LinkedGroupedPermissions;
|
|
nextIfCollapsed: LinkedGroupedPermissions;
|
|
|
|
collapsed: boolean;
|
|
|
|
elementVisible: boolean;
|
|
}
|
|
|
|
const PermissionEntryRow = (props: {
|
|
events: Registry<PermissionEditorEvents>,
|
|
groupId: string,
|
|
permission: string,
|
|
value: PermissionValue,
|
|
isOdd: boolean,
|
|
depth: number,
|
|
offsetTop: number,
|
|
defaultValue: number,
|
|
description: string
|
|
}) => {
|
|
const [defaultValue, setDefaultValue] = useState(props.defaultValue);
|
|
const [value, setValue] = useState<number>(props.value.value);
|
|
const [forceValueUpdate, setForceValueUpdate] = useState(false);
|
|
const [valueEditing, setValueEditing] = useState(false);
|
|
const [valueApplying, setValueApplying] = useState(false);
|
|
|
|
const [flagNegated, setFlagNegated] = useState(props.value.flagNegate);
|
|
const [flagSkip, setFlagSkip] = useState(props.value.flagSkip);
|
|
|
|
const [granted, setGranted] = useState(props.value.granted);
|
|
const [forceGrantedUpdate, setForceGrantedUpdate] = useState(false);
|
|
const [grantedEditing, setGrantedEditing] = useState(false);
|
|
const [grantedApplying, setGrantedApplying] = useState(false);
|
|
|
|
const refGranted = useRef<HTMLInputElement>();
|
|
const refValueI = useRef<HTMLInputElement>();
|
|
const refValueB = useRef<Switch>();
|
|
|
|
const refSkip = useRef<Switch>();
|
|
const refNegate = useRef<Switch>();
|
|
|
|
const isActive = typeof value === "number" || typeof granted === "number";
|
|
const isBoolPermission = props.permission.startsWith("b_");
|
|
|
|
let valueElement, skipElement, negateElement, grantedElement;
|
|
if (typeof value === "number") {
|
|
if (isBoolPermission) {
|
|
valueElement = <Switch ref={refValueB} key={"value-b"} initialState={value >= 1} disabled={valueApplying}
|
|
onChange={flag => {
|
|
props.events.fire("action_set_permissions", {
|
|
permissions: [{
|
|
name: props.permission,
|
|
mode: "value",
|
|
value: flag ? 1 : 0,
|
|
flagSkip: flagSkip,
|
|
flagNegate: flagNegated
|
|
}]
|
|
});
|
|
}} onBlur={() => setValueEditing(false)}/>;
|
|
} else if (valueApplying) {
|
|
valueElement =
|
|
<input key={"value-i-applying"} className={cssStyle.applying} type="number" placeholder={tr("applying")}
|
|
readOnly={true} onChange={() => {
|
|
}}/>;
|
|
} else {
|
|
valueElement =
|
|
<input ref={refValueI} key={"value-i"} type="number" disabled={valueApplying} defaultValue={value}
|
|
onBlur={() => {
|
|
setValueEditing(false);
|
|
if (!refValueI.current)
|
|
return;
|
|
|
|
const newValue = refValueI.current.value;
|
|
if (newValue === "") {
|
|
if (typeof value !== "number" && !forceValueUpdate) {
|
|
/* no change */
|
|
return;
|
|
}
|
|
|
|
setForceValueUpdate(false);
|
|
props.events.fire("action_remove_permissions", {
|
|
permissions: [{
|
|
name: props.permission,
|
|
mode: "value"
|
|
}]
|
|
});
|
|
} else {
|
|
const numberValue = parseInt(newValue);
|
|
if (isNaN(numberValue)) return;
|
|
if (numberValue === value && !forceValueUpdate) {
|
|
/* no change */
|
|
return;
|
|
}
|
|
|
|
setForceValueUpdate(false);
|
|
props.events.fire("action_set_permissions", {
|
|
permissions: [{
|
|
name: props.permission,
|
|
mode: "value",
|
|
value: numberValue,
|
|
flagSkip: flagSkip,
|
|
flagNegate: flagNegated
|
|
}]
|
|
});
|
|
}
|
|
}} onChange={() => {
|
|
}} onKeyPress={e => e.key === "Enter" && e.currentTarget.blur()}/>;
|
|
}
|
|
|
|
skipElement = <Switch key={"skip"} initialState={flagSkip} disabled={valueApplying} onChange={flag => {
|
|
props.events.fire("action_set_permissions", {
|
|
permissions: [{
|
|
name: props.permission,
|
|
mode: "value",
|
|
value: value,
|
|
flagSkip: flag,
|
|
flagNegate: flagNegated
|
|
}]
|
|
});
|
|
}}/>;
|
|
negateElement = <Switch key={"negate"} initialState={flagNegated} disabled={valueApplying} onChange={flag => {
|
|
props.events.fire("action_set_permissions", {
|
|
permissions: [{
|
|
name: props.permission,
|
|
mode: "value",
|
|
value: value,
|
|
flagSkip: flagSkip,
|
|
flagNegate: flag
|
|
}]
|
|
});
|
|
}}/>;
|
|
}
|
|
|
|
if (typeof granted === "number") {
|
|
if (grantedApplying) {
|
|
grantedElement =
|
|
<input key={"grant-applying"} className={cssStyle.applying} type="number" placeholder={tr("applying")}
|
|
readOnly={true} onChange={() => {
|
|
}}/>;
|
|
} else {
|
|
grantedElement = <input ref={refGranted} key={"grant"} type="number" defaultValue={granted} onBlur={() => {
|
|
setGrantedEditing(false);
|
|
if (!refGranted.current)
|
|
return;
|
|
|
|
const newValue = refGranted.current.value;
|
|
if (newValue === "") {
|
|
if (typeof granted === "undefined")
|
|
return;
|
|
|
|
setForceGrantedUpdate(true);
|
|
props.events.fire("action_remove_permissions", {
|
|
permissions: [{
|
|
name: props.permission,
|
|
mode: "grant"
|
|
}]
|
|
});
|
|
} else {
|
|
const numberValue = parseInt(newValue);
|
|
if (isNaN(numberValue)) return;
|
|
if (numberValue === granted && !forceGrantedUpdate) {
|
|
/* no change */
|
|
return;
|
|
}
|
|
|
|
setForceGrantedUpdate(true);
|
|
props.events.fire("action_set_permissions", {
|
|
permissions: [{
|
|
name: props.permission,
|
|
mode: "grant",
|
|
value: numberValue
|
|
}]
|
|
});
|
|
}
|
|
}} onChange={() => {
|
|
}} onKeyPress={e => e.key === "Enter" && e.currentTarget.blur()}/>;
|
|
}
|
|
}
|
|
|
|
props.events.reactUse("action_start_permission_edit", event => {
|
|
if (event.permission !== props.permission)
|
|
return;
|
|
|
|
if (event.target === "grant") {
|
|
setGranted(event.defaultValue);
|
|
setGrantedEditing(true);
|
|
setForceGrantedUpdate(true);
|
|
} else {
|
|
if (isBoolPermission && typeof value === "undefined") {
|
|
setValue(event.defaultValue >= 1 ? 1 : 0);
|
|
props.events.fire("action_set_permissions", {
|
|
permissions: [{
|
|
name: props.permission,
|
|
mode: "value",
|
|
value: event.defaultValue >= 1 ? 1 : 0,
|
|
flagSkip: flagSkip,
|
|
flagNegate: flagNegated
|
|
}]
|
|
});
|
|
} else {
|
|
setValue(event.defaultValue);
|
|
setForceValueUpdate(true);
|
|
setValueEditing(true);
|
|
}
|
|
}
|
|
});
|
|
|
|
props.events.reactUse("action_set_permissions", event => {
|
|
const values = event.permissions.find(e => e.name === props.permission);
|
|
if (!values) return;
|
|
|
|
if (values.mode === "value") {
|
|
setValueApplying(true);
|
|
refSkip.current?.setState({disabled: true});
|
|
refNegate.current?.setState({disabled: true});
|
|
} else {
|
|
setGrantedApplying(true);
|
|
}
|
|
});
|
|
|
|
props.events.reactUse("action_set_permissions_result", event => {
|
|
const result = event.permissions.find(e => e.name === props.permission);
|
|
if (!result) return;
|
|
|
|
if (result.mode === "value") {
|
|
setValueApplying(false);
|
|
if (typeof result.newValue === "number") {
|
|
setValue(result.newValue);
|
|
setFlagSkip(result.flagSkip);
|
|
setFlagSkip(result.flagNegate);
|
|
|
|
refValueB.current?.setState({disabled: false, checked: result.newValue >= 1});
|
|
refSkip.current?.setState({disabled: false, checked: result.flagSkip});
|
|
refNegate.current?.setState({disabled: false, checked: result.flagNegate});
|
|
refValueI.current && (refValueI.current.value = result.newValue.toString());
|
|
|
|
props.value.value = result.newValue;
|
|
props.value.flagSkip = result.flagSkip;
|
|
props.value.flagNegate = result.flagNegate;
|
|
} else {
|
|
refValueB.current?.setState({disabled: false, checked: props.value.value >= 1});
|
|
refSkip.current?.setState({disabled: false, checked: props.value.flagSkip});
|
|
refNegate.current?.setState({disabled: false, checked: props.value.flagNegate});
|
|
refValueI.current && (refValueI.current.value = props.value.value?.toString());
|
|
|
|
setValue(props.value.value);
|
|
setFlagSkip(props.value.flagSkip);
|
|
setFlagSkip(props.value.flagNegate);
|
|
}
|
|
} else {
|
|
setGrantedApplying(false);
|
|
if (typeof result.newValue === "number") {
|
|
setGranted(result.newValue);
|
|
refGranted.current && (refGranted.current.value = result.newValue.toString());
|
|
} else {
|
|
setGranted(props.value.granted);
|
|
refGranted.current && (refGranted.current.value = props.value.granted?.toString());
|
|
}
|
|
}
|
|
});
|
|
|
|
props.events.reactUse("action_remove_permissions", event => {
|
|
const modes = event.permissions.find(e => e.name === props.permission);
|
|
if (!modes) return;
|
|
|
|
if (modes.mode === "value") {
|
|
setValueApplying(true);
|
|
refValueB.current?.setState({disabled: true});
|
|
refSkip.current?.setState({disabled: true});
|
|
refNegate.current?.setState({disabled: true});
|
|
}
|
|
|
|
if (modes.mode === "grant")
|
|
setGrantedApplying(true);
|
|
});
|
|
|
|
props.events.reactUse("action_remove_permissions_result", event => {
|
|
const modes = event.permissions.find(e => e.name === props.permission);
|
|
if (!modes) return;
|
|
|
|
if (modes.mode === "value") {
|
|
modes.success && setValue(undefined);
|
|
setValueApplying(false);
|
|
setValueEditing(false);
|
|
|
|
modes.success && setFlagSkip(false);
|
|
modes.success && setFlagNegated(false);
|
|
}
|
|
|
|
if (modes.mode === "grant") {
|
|
modes.success && setGranted(undefined);
|
|
setGrantedEditing(false);
|
|
setGrantedApplying(false);
|
|
}
|
|
});
|
|
|
|
props.events.reactUse("action_set_default_value", event => setDefaultValue(event.value));
|
|
|
|
useEffect(() => {
|
|
if (grantedEditing)
|
|
refGranted.current?.focus();
|
|
|
|
if (valueEditing) {
|
|
refValueI.current?.focus();
|
|
refValueB.current?.focus();
|
|
}
|
|
});
|
|
|
|
return (
|
|
<div
|
|
className={cssStyle.row + " " + cssStyle.permission + " " + (props.isOdd ? "" : cssStyle.even) + " " + (isActive ? cssStyle.active : "")}
|
|
style={{paddingLeft: (props.depth + 1) + "em", top: props.offsetTop}}
|
|
onDoubleClick={e => {
|
|
if (e.isDefaultPrevented())
|
|
return;
|
|
|
|
props.events.fire("action_start_permission_edit", {
|
|
permission: props.permission,
|
|
target: "value",
|
|
defaultValue: defaultValue
|
|
});
|
|
e.preventDefault();
|
|
}}
|
|
onContextMenu={e => {
|
|
e.preventDefault();
|
|
|
|
let entries: contextmenu.MenuEntry[] = [];
|
|
if (typeof value === "undefined") {
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Add permission"),
|
|
callback: () => props.events.fire("action_start_permission_edit", {
|
|
permission: props.permission,
|
|
target: "value",
|
|
defaultValue: defaultValue
|
|
})
|
|
});
|
|
} else {
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Remove permission"),
|
|
callback: () => props.events.fire("action_remove_permissions", {
|
|
permissions: [{
|
|
name: props.permission,
|
|
mode: "value"
|
|
}]
|
|
})
|
|
});
|
|
}
|
|
|
|
if (typeof granted === "undefined") {
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Add grant permission"),
|
|
callback: () => props.events.fire("action_start_permission_edit", {
|
|
permission: props.permission,
|
|
target: "grant",
|
|
defaultValue: defaultValue
|
|
})
|
|
});
|
|
} else {
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Remove grant permission"),
|
|
callback: () => props.events.fire("action_remove_permissions", {
|
|
permissions: [{
|
|
name: props.permission,
|
|
mode: "grant"
|
|
}]
|
|
})
|
|
});
|
|
}
|
|
|
|
entries.push(contextmenu.Entry.HR());
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Collapse group"),
|
|
callback: () => props.events.fire("action_toggle_group", {groupId: props.groupId, collapsed: true})
|
|
});
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Expend all"),
|
|
callback: () => props.events.fire("action_toggle_group", {groupId: null, collapsed: false})
|
|
});
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Collapse all"),
|
|
callback: () => props.events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
|
});
|
|
entries.push(contextmenu.Entry.HR());
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Show permission description"),
|
|
callback: () => {
|
|
createInfoModal(
|
|
tr("Permission description"),
|
|
tr("Permission description for permission ") + props.permission + ": <br>" + props.description
|
|
).open();
|
|
}
|
|
});
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Copy permission name"),
|
|
callback: () => copy_to_clipboard(props.permission)
|
|
});
|
|
|
|
contextmenu.spawn_context_menu(e.pageX, e.pageY, ...entries);
|
|
}}
|
|
>
|
|
<div className={cssStyle.columnName}>
|
|
{props.permission}
|
|
</div>
|
|
<div className={cssStyle.columnValue}>{valueElement}</div>
|
|
<div className={cssStyle.columnSkip}>{skipElement}</div>
|
|
<div className={cssStyle.columnNegate}>{negateElement}</div>
|
|
<div className={cssStyle.columnGranted} onDoubleClick={e => {
|
|
props.events.fire("action_start_permission_edit", {
|
|
permission: props.permission,
|
|
target: "grant",
|
|
defaultValue: defaultValue
|
|
});
|
|
e.preventDefault();
|
|
}}>{grantedElement}</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const PermissionGroupRow = (props: { events: Registry<PermissionEditorEvents>, group: LinkedGroupedPermissions, isOdd: boolean, offsetTop: number }) => {
|
|
const [collapsed, setCollapsed] = useState(props.group.collapsed);
|
|
|
|
props.events.reactUse("action_toggle_group", event => {
|
|
if (event.groupId !== null && event.groupId !== props.group.groupId)
|
|
return;
|
|
|
|
setCollapsed(event.collapsed);
|
|
});
|
|
|
|
return (
|
|
<div className={cssStyle.row + " " + cssStyle.group + " " + (props.isOdd ? "" : cssStyle.even)}
|
|
style={{paddingLeft: props.group.depth + "em", top: props.offsetTop}} onContextMenu={e => {
|
|
e.preventDefault();
|
|
|
|
let entries = [];
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Add permissions to this group"),
|
|
callback: () => props.events.fire("action_add_permission_group", {
|
|
groupId: props.group.groupId,
|
|
mode: "value"
|
|
})
|
|
});
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Remove permissions from this group"),
|
|
callback: () => props.events.fire("action_remove_permission_group", {
|
|
groupId: props.group.groupId,
|
|
mode: "value"
|
|
})
|
|
});
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Add granted permissions to this group"),
|
|
callback: () => props.events.fire("action_add_permission_group", {
|
|
groupId: props.group.groupId,
|
|
mode: "grant"
|
|
})
|
|
});
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Remove granted permissions from this group"),
|
|
callback: () => props.events.fire("action_remove_permission_group", {
|
|
groupId: props.group.groupId,
|
|
mode: "grant"
|
|
})
|
|
});
|
|
entries.push(contextmenu.Entry.HR());
|
|
if (collapsed) {
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Expend group"),
|
|
callback: () => props.events.fire("action_toggle_group", {
|
|
groupId: props.group.groupId,
|
|
collapsed: false
|
|
})
|
|
});
|
|
} else {
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Collapse group"),
|
|
callback: () => props.events.fire("action_toggle_group", {
|
|
groupId: props.group.groupId,
|
|
collapsed: true
|
|
})
|
|
});
|
|
}
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Expend all"),
|
|
callback: () => props.events.fire("action_toggle_group", {groupId: null, collapsed: false})
|
|
});
|
|
entries.push({
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Collapse all"),
|
|
callback: () => props.events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
|
});
|
|
contextmenu.spawn_context_menu(e.pageX, e.pageY, ...entries);
|
|
}}
|
|
onDoubleClick={() => props.events.fire("action_toggle_group", {
|
|
collapsed: !collapsed,
|
|
groupId: props.group.groupId
|
|
})}
|
|
>
|
|
<div className={cssStyle.columnName}>
|
|
<div className={"arrow " + (collapsed ? "right" : "down")}
|
|
onClick={() => props.events.fire("action_toggle_group", {
|
|
collapsed: !collapsed,
|
|
groupId: props.group.groupId
|
|
})}/>
|
|
<div className={cssStyle.groupName} title={/* @tr-ignore */ tr(props.group.groupName)}>
|
|
<Translatable trIgnore={true}>{props.group.groupName}</Translatable>
|
|
</div>
|
|
</div>
|
|
<div className={cssStyle.columnValue}/>
|
|
<div className={cssStyle.columnSkip}/>
|
|
<div className={cssStyle.columnNegate}/>
|
|
<div className={cssStyle.columnGranted}/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
type PermissionValue = { value?: number, flagNegate?: boolean, flagSkip?: boolean, granted?: number };
|
|
|
|
@ReactEventHandler<PermissionList>(e => e.props.events)
|
|
class PermissionList extends React.Component<{ events: Registry<PermissionEditorEvents> }, { state: "loading" | "normal" | "error", viewHeight: number, scrollOffset: number, error?: string }> {
|
|
private readonly refContainer = React.createRef<HTMLDivElement>();
|
|
private resizeObserver: ResizeObserver;
|
|
|
|
private permissionsHead: LinkedGroupedPermissions;
|
|
private permissionByGroupId: { [key: string]: LinkedGroupedPermissions } = {};
|
|
private permissionValuesByName: { [key: string]: PermissionValue } = {};
|
|
|
|
private hideSenselessPermissions = true;
|
|
private senselessPermissions: string[] = [];
|
|
|
|
private currentListElements: React.ReactElement[] = [];
|
|
private heightPerElement = 28; /* default font size 28px */
|
|
private heightPerElementInitialized = false;
|
|
|
|
private filterText: string | undefined;
|
|
private filterAssignedOnly: boolean = false;
|
|
|
|
private loadingPermissionList = true;
|
|
private loadingPermissionValues = false;
|
|
|
|
private defaultPermissionValue = 1;
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
viewHeight: 0,
|
|
scrollOffset: 0,
|
|
state: "loading"
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const view = this.visibleEntries();
|
|
let elements = this.state.state === "normal" ? this.currentListElements.slice(Math.max(0, view.begin - 5), Math.min(view.end + 5, this.currentListElements.length)) : [];
|
|
|
|
return (
|
|
<div className={cssStyle.body} ref={this.refContainer}
|
|
onScroll={() => this.state.state === "normal" && this.setState({scrollOffset: this.refContainer.current.scrollTop})}
|
|
onContextMenu={e => {
|
|
if (e.isDefaultPrevented())
|
|
return;
|
|
|
|
e.preventDefault();
|
|
contextmenu.spawn_context_menu(e.pageX, e.pageY, {
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Expend all"),
|
|
callback: () => this.props.events.fire("action_toggle_group", {
|
|
groupId: null,
|
|
collapsed: false
|
|
})
|
|
}, {
|
|
type: contextmenu.MenuEntryType.ENTRY,
|
|
name: tr("Collapse all"),
|
|
callback: () => this.props.events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
|
});
|
|
}}>
|
|
{elements}
|
|
<div key={"space"} className={cssStyle.spaceAllocator}
|
|
style={{height: this.state.state === "normal" ? this.currentListElements.length * this.heightPerElement : 0}}/>
|
|
<div key={"loading"}
|
|
className={cssStyle.overlay + " " + (this.state.state === "loading" ? "" : cssStyle.hidden)}>
|
|
<a title={tr("loading")}><Translatable>loading</Translatable> <LoadingDots maxDots={3}/></a>
|
|
</div>
|
|
<div key={"error"}
|
|
className={cssStyle.overlay + " " + cssStyle.error + " " + (this.state.state === "error" ? "" : cssStyle.hidden)}>
|
|
<a title={tr("An error happened")}>{this.state.error}</a>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
private visibleEntries() {
|
|
let view_entry_count = Math.ceil(this.state.viewHeight / this.heightPerElement);
|
|
const view_entry_begin = Math.floor(this.state.scrollOffset / this.heightPerElement);
|
|
const view_entry_end = Math.min(this.currentListElements.length, view_entry_begin + view_entry_count);
|
|
|
|
return {
|
|
begin: view_entry_begin,
|
|
end: view_entry_end
|
|
}
|
|
}
|
|
|
|
componentDidMount(): void {
|
|
this.resizeObserver = new ResizeObserver(entries => {
|
|
if (entries.length !== 1) {
|
|
if (entries.length === 0)
|
|
log.warn(LogCategory.PERMISSIONS, tr("Permission editor resize observer fired resize event with no entries!"));
|
|
else
|
|
log.warn(LogCategory.PERMISSIONS, tr("Permission editor resize observer fired resize event with more than one entry which should not be possible (%d)!"), entries.length);
|
|
return;
|
|
}
|
|
const bounds = entries[0].contentRect;
|
|
if (this.state.viewHeight !== bounds.height) {
|
|
log.debug(LogCategory.PERMISSIONS, tr("Handling height update and change permission view height to %d from %d"), bounds.height, this.state.viewHeight);
|
|
this.setState({
|
|
viewHeight: bounds.height
|
|
});
|
|
}
|
|
});
|
|
this.resizeObserver.observe(this.refContainer.current);
|
|
|
|
this.props.events.fire("query_permission_list");
|
|
}
|
|
|
|
componentWillUnmount(): void {
|
|
this.resizeObserver.disconnect();
|
|
this.resizeObserver = undefined;
|
|
}
|
|
|
|
private initializeElementHeight() {
|
|
if (this.heightPerElementInitialized)
|
|
return;
|
|
|
|
requestAnimationFrame(() => {
|
|
const firstElement = this.refContainer.current?.firstElementChild;
|
|
/* the first element might be the space allocator in cases without any shown row elements */
|
|
if (firstElement && firstElement.classList.contains(cssStyle.row)) {
|
|
this.heightPerElementInitialized = true;
|
|
const rect = firstElement.getBoundingClientRect();
|
|
if (this.heightPerElement !== rect.height) {
|
|
this.heightPerElement = rect.height;
|
|
this.updateReactComponents();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
componentDidUpdate(prevProps: Readonly<{ events: Registry<PermissionEditorEvents> }>, prevState: Readonly<{ state: "loading" | "normal" | "error", viewHeight: number, scrollOffset: number, error?: string }>, snapshot?: any): void {
|
|
if (prevState.state !== "normal" && this.state.state === "normal")
|
|
requestAnimationFrame(() => this.refContainer.current.scrollTop = this.state.scrollOffset);
|
|
if (this.state.state === "normal")
|
|
this.initializeElementHeight();
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("query_permission_list")
|
|
private handlePermissionListQuery() {
|
|
this.loadingPermissionList = true;
|
|
this.setState({state: "loading"});
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("query_permission_list_result")
|
|
private handlePermissionList(event: PermissionEditorEvents["query_permission_list_result"]) {
|
|
this.loadingPermissionList = false;
|
|
|
|
this.hideSenselessPermissions = event.hideSenselessPermissions;
|
|
const visitGroup = (group: EditorGroupedPermissions, parent: LinkedGroupedPermissions, depth: number): LinkedGroupedPermissions => {
|
|
const result: LinkedGroupedPermissions = {
|
|
groupName: group.groupName,
|
|
groupId: group.groupId,
|
|
|
|
collapsed: false,
|
|
|
|
permissions: group.permissions.map(e => {
|
|
return {
|
|
name: e.name,
|
|
id: e.id,
|
|
description: e.description,
|
|
|
|
elementVisible: true
|
|
}
|
|
}),
|
|
anyPermissionVisible: true,
|
|
|
|
depth: depth,
|
|
parent: parent,
|
|
children: [],
|
|
|
|
nextGroup: undefined,
|
|
nextIfCollapsed: undefined, /* will be set later */
|
|
|
|
elementVisible: true,
|
|
};
|
|
|
|
if (group.children && group.children.length > 0) {
|
|
result.nextGroup = visitGroup(group.children[0], result, depth + 1);
|
|
result.children.push(result.nextGroup);
|
|
|
|
let currentHead = result.nextGroup;
|
|
for (let index = 1; index < group.children.length; index++) {
|
|
currentHead.nextIfCollapsed = visitGroup(group.children[index], result, depth + 1);
|
|
currentHead = currentHead.nextIfCollapsed;
|
|
result.children.push(currentHead);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
this.permissionsHead = visitGroup(event.permissions[0], undefined, 0);
|
|
let currentHead = this.permissionsHead;
|
|
for (let index = 1; index < event.permissions.length; index++) {
|
|
currentHead.nextIfCollapsed = visitGroup(event.permissions[index], undefined, 0);
|
|
currentHead = currentHead.nextIfCollapsed;
|
|
}
|
|
|
|
/* fixup the next group linkage */
|
|
currentHead = this.permissionsHead;
|
|
while (currentHead) {
|
|
if (!currentHead.nextIfCollapsed && currentHead.parent)
|
|
currentHead.nextIfCollapsed = currentHead.parent.nextIfCollapsed;
|
|
|
|
if (!currentHead.nextGroup)
|
|
currentHead.nextGroup = currentHead.nextIfCollapsed;
|
|
|
|
currentHead = currentHead.nextGroup;
|
|
}
|
|
|
|
/* build up the group key index */
|
|
this.permissionByGroupId = {};
|
|
currentHead = this.permissionsHead;
|
|
while (currentHead) {
|
|
this.permissionByGroupId[currentHead.groupId] = currentHead;
|
|
currentHead = currentHead.nextGroup;
|
|
}
|
|
|
|
this.setState({state: this.loadingPermissionList || this.loadingPermissionValues ? "loading" : this.state.error ? "error" : "normal"});
|
|
this.updateReactComponents();
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("action_set_senseless_permissions")
|
|
private handleSenselessPermissions(event: PermissionEditorEvents["action_set_senseless_permissions"]) {
|
|
this.senselessPermissions = event.permissions.slice(0);
|
|
this.updateReactComponents();
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("query_permission_values")
|
|
private handleRequestPermissionValues() {
|
|
this.loadingPermissionValues = true;
|
|
this.setState({state: "loading"});
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("query_permission_values_result")
|
|
private handleRequestPermissionValuesResult(event: PermissionEditorEvents["query_permission_values_result"]) {
|
|
this.loadingPermissionValues = false;
|
|
this.permissionValuesByName = {};
|
|
|
|
Object.values(this.permissionValuesByName).forEach(e => {
|
|
e.value = undefined;
|
|
e.granted = undefined;
|
|
});
|
|
(event.permissions || []).forEach(permission => Object.assign(this.permissionValuesByName[permission.name] || (this.permissionValuesByName[permission.name] = {}), {
|
|
value: permission.value,
|
|
granted: permission.granted,
|
|
flagNegate: permission.flagNegate,
|
|
flagSkip: permission.flagSkip
|
|
}));
|
|
|
|
this.setState({
|
|
state: this.loadingPermissionList || this.loadingPermissionValues ? "loading" : event.status !== "success" ? "error" : "normal",
|
|
error: event.error
|
|
});
|
|
|
|
if (event.status === "success")
|
|
this.updateReactComponents();
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("action_set_permissions_result")
|
|
private handlePermissionSetResult(event: PermissionEditorEvents["action_set_permissions_result"]) {
|
|
event.permissions.forEach(e => {
|
|
if (typeof e.newValue !== "number")
|
|
return;
|
|
|
|
const values = this.permissionValuesByName[e.name] || (this.permissionValuesByName[e.name] = {});
|
|
if (e.mode === "value") {
|
|
values.value = e.newValue;
|
|
values.flagSkip = e.flagSkip;
|
|
values.flagNegate = e.flagNegate;
|
|
} else {
|
|
values.granted = e.newValue;
|
|
}
|
|
});
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("action_remove_permissions_result")
|
|
private handlePermissionRemoveResult(event: PermissionEditorEvents["action_remove_permissions_result"]) {
|
|
event.permissions.forEach(e => {
|
|
if (!e.success)
|
|
return;
|
|
|
|
const values = this.permissionValuesByName[e.name] || (this.permissionValuesByName[e.name] = {});
|
|
if (e.mode === "value") {
|
|
values.value = undefined;
|
|
values.flagSkip = false;
|
|
values.flagNegate = false;
|
|
} else {
|
|
values.granted = undefined;
|
|
}
|
|
});
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("action_toggle_group")
|
|
private handleToggleGroup(event: PermissionEditorEvents["action_toggle_group"]) {
|
|
if (event.groupId === null) {
|
|
Object.values(this.permissionByGroupId).forEach(e => e.collapsed = event.collapsed);
|
|
} else {
|
|
const group = this.permissionByGroupId[event.groupId];
|
|
if (!group) {
|
|
console.warn(tr("Received group toogle for unknwon group: %s"), event.groupId);
|
|
return;
|
|
}
|
|
|
|
if (group.collapsed === event.collapsed)
|
|
return;
|
|
|
|
group.collapsed = event.collapsed;
|
|
}
|
|
this.updateReactComponents();
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("action_set_filter")
|
|
private handleSetFilter(event: PermissionEditorEvents["action_set_filter"]) {
|
|
if (this.filterText === event.filter)
|
|
return;
|
|
|
|
this.filterText = event.filter;
|
|
this.updateReactComponents();
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("action_set_assigned_only")
|
|
private handleSetAssignedFilter(event: PermissionEditorEvents["action_set_assigned_only"]) {
|
|
if (this.filterAssignedOnly === event.value)
|
|
return;
|
|
|
|
this.filterAssignedOnly = event.value;
|
|
this.updateReactComponents();
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("action_set_default_value")
|
|
private handleSetDefaultPermissionValue(event: PermissionEditorEvents["action_set_default_value"]) {
|
|
this.defaultPermissionValue = event.value;
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("action_add_permission_group")
|
|
private handleEnablePermissionGroup(event: PermissionEditorEvents["action_add_permission_group"]) {
|
|
const group = this.permissionByGroupId[event.groupId];
|
|
if (!group) return;
|
|
|
|
const permissions: { id: number, name: string, elementVisible: boolean }[] = [];
|
|
const visitGroup = (group: LinkedGroupedPermissions) => {
|
|
permissions.push(...group.permissions);
|
|
group.children.forEach(visitGroup);
|
|
};
|
|
visitGroup(group);
|
|
|
|
this.props.events.fire("action_set_permissions", {
|
|
permissions: permissions.map(e => {
|
|
return {
|
|
name: e.name,
|
|
mode: event.mode as "value" | "grant",
|
|
|
|
value: e.name.startsWith("b_") && event.mode === "value" ? 1 : this.defaultPermissionValue,
|
|
flagNegate: false,
|
|
flagSkip: false
|
|
}
|
|
})
|
|
});
|
|
}
|
|
|
|
@EventHandler<PermissionEditorEvents>("action_remove_permission_group")
|
|
private handleDisablePermissionGroup(event: PermissionEditorEvents["action_remove_permission_group"]) {
|
|
const group = this.permissionByGroupId[event.groupId];
|
|
if (!group) return;
|
|
|
|
const permissions: { id: number, name: string, elementVisible: boolean }[] = [];
|
|
const visitGroup = (group: LinkedGroupedPermissions) => {
|
|
permissions.push(...group.permissions);
|
|
group.children.forEach(visitGroup);
|
|
};
|
|
visitGroup(group);
|
|
|
|
this.props.events.fire("action_remove_permissions", {
|
|
permissions: permissions.map(e => {
|
|
return {
|
|
name: e.name,
|
|
mode: event.mode as "value" | "grant",
|
|
}
|
|
})
|
|
});
|
|
}
|
|
|
|
private updateReactComponents() {
|
|
let currentGroup = this.permissionsHead;
|
|
let visibleGroups: LinkedGroupedPermissions[] = [];
|
|
while (currentGroup) {
|
|
visibleGroups.push(currentGroup);
|
|
if (currentGroup.collapsed) {
|
|
currentGroup = currentGroup.nextIfCollapsed;
|
|
continue;
|
|
}
|
|
|
|
currentGroup.anyPermissionVisible = false;
|
|
for (const permission of currentGroup.permissions) {
|
|
permission.elementVisible = false;
|
|
if (this.filterText && permission.name.indexOf(this.filterText) === -1)
|
|
continue;
|
|
|
|
if (this.hideSenselessPermissions && this.senselessPermissions.findIndex(e => e === permission.name) !== -1)
|
|
continue;
|
|
|
|
const permissionValue = this.permissionValuesByName[permission.name] || (this.permissionValuesByName[permission.name] = {});
|
|
if (this.filterAssignedOnly) {
|
|
if (typeof permissionValue.value !== "number" && typeof permissionValue.granted !== "number") {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
permission.elementVisible = true;
|
|
currentGroup.anyPermissionVisible = true;
|
|
}
|
|
|
|
currentGroup = currentGroup.nextGroup;
|
|
}
|
|
|
|
/* update the visibility from the bottom to the top */
|
|
visibleGroups.sort((a, b) => b.depth - a.depth);
|
|
visibleGroups.forEach(e => {
|
|
for (const child of e.children) {
|
|
if (child.elementVisible) {
|
|
e.elementVisible = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
e.elementVisible = e.anyPermissionVisible;
|
|
});
|
|
|
|
/* lets build up the final list view */
|
|
this.currentListElements = [];
|
|
let index = 0;
|
|
currentGroup = this.permissionsHead;
|
|
while (currentGroup) {
|
|
if (currentGroup.elementVisible) {
|
|
this.currentListElements.push(<PermissionGroupRow key={"group-" + currentGroup.groupId}
|
|
events={this.props.events} group={currentGroup}
|
|
isOdd={index % 2 === 1}
|
|
offsetTop={this.heightPerElement * index}/>);
|
|
index++;
|
|
}
|
|
|
|
if (currentGroup.collapsed) {
|
|
currentGroup = currentGroup.nextIfCollapsed;
|
|
continue;
|
|
} else if (!currentGroup.elementVisible) {
|
|
currentGroup = currentGroup.nextGroup;
|
|
continue;
|
|
}
|
|
|
|
currentGroup.permissions.forEach(e => {
|
|
if (!e.elementVisible)
|
|
return;
|
|
|
|
this.currentListElements.push(<PermissionEntryRow
|
|
key={"permission-" + e.name + " - " + Math.random()} /* force a update of this */
|
|
events={this.props.events}
|
|
permission={e.name}
|
|
groupId={currentGroup.groupId}
|
|
isOdd={index % 2 === 1}
|
|
depth={currentGroup.depth}
|
|
offsetTop={this.heightPerElement * index}
|
|
value={this.permissionValuesByName[e.name] || {}}
|
|
defaultValue={this.defaultPermissionValue}
|
|
description={e.description}
|
|
/>);
|
|
index++;
|
|
});
|
|
|
|
currentGroup = currentGroup.nextGroup;
|
|
}
|
|
|
|
this.forceUpdate();
|
|
}
|
|
}
|
|
|
|
const PermissionTable = (props: { events: Registry<PermissionEditorEvents> }) => {
|
|
const [mode, setMode] = useState<PermissionEditorMode>("unset");
|
|
const [failedPermission, setFailedPermission] = useState(undefined);
|
|
|
|
props.events.reactUse("action_set_mode", event => {
|
|
setMode(event.mode);
|
|
setFailedPermission(event.failedPermission);
|
|
});
|
|
|
|
return (
|
|
<div className={cssStyle.permissionTable}>
|
|
<div className={cssStyle.header}>
|
|
<div className={cssStyle.row + " " + cssStyle.header}>
|
|
<div className={cssStyle.columnName}>
|
|
<a title={tr("Permission Name")}><Translatable>Permission Name</Translatable></a>
|
|
</div>
|
|
<div className={cssStyle.columnValue}>
|
|
<a title={tr("Value")}><Translatable>Value</Translatable></a>
|
|
</div>
|
|
<div className={cssStyle.columnSkip}>
|
|
<a title={tr("Skip")}><Translatable>Skip</Translatable></a>
|
|
</div>
|
|
<div className={cssStyle.columnNegate}>
|
|
<a title={tr("Negate")}><Translatable>Negate</Translatable></a>
|
|
</div>
|
|
<div className={cssStyle.columnGranted}>
|
|
<a title={tr("Granted")}><Translatable>Granted</Translatable></a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<PermissionList events={props.events}/>
|
|
<div className={cssStyle.overlay + " " + cssStyle.unset + " " + (mode === "unset" ? "" : cssStyle.hidden)}/>
|
|
|
|
<div
|
|
className={cssStyle.overlay + " " + cssStyle.noPermissions + " " + (mode === "no-permissions" ? "" : cssStyle.hidden)}>
|
|
<a><Translatable>You don't have the permissions to view this
|
|
permissions</Translatable><br/>({failedPermission})</a>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const RefreshButton = (props: { events: Registry<PermissionEditorEvents> }) => {
|
|
const [unset, setUnset] = useState(true);
|
|
const [nextTime, setNextTime] = useState(0);
|
|
const refButton = useRef<Button>();
|
|
|
|
props.events.reactUse("action_set_mode", event => setUnset(event.mode !== "normal" && event.mode !== "no-permissions"));
|
|
props.events.reactUse("query_permission_values", () => {
|
|
setNextTime(Date.now() + 5000);
|
|
refButton.current?.setState({disabled: true});
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (Date.now() >= nextTime) {
|
|
refButton.current?.setState({disabled: false});
|
|
return;
|
|
}
|
|
|
|
const id = setTimeout(() => refButton.current?.setState({disabled: false}), Math.max(0, nextTime - Date.now()));
|
|
return () => clearTimeout(id);
|
|
});
|
|
|
|
return <Button
|
|
ref={refButton}
|
|
disabled={unset || Date.now() < nextTime}
|
|
onClick={() => props.events.fire("query_permission_values")}
|
|
>
|
|
<IconRenderer icon={"client-check_update"}/> <Translatable>Update</Translatable>
|
|
</Button>
|
|
};
|
|
|
|
interface PermissionEditorProperties {
|
|
connection: ConnectionHandler;
|
|
events: Registry<PermissionEditorEvents>;
|
|
}
|
|
|
|
interface PermissionEditorState {
|
|
state: "no-permissions" | "unset" | "normal";
|
|
}
|
|
|
|
export class PermissionEditor extends React.Component<PermissionEditorProperties, PermissionEditorState> {
|
|
render() {
|
|
return [
|
|
<MenuBar key={"menu-bar"} events={this.props.events} connection={this.props.connection}/>,
|
|
<PermissionTable key={"table"} events={this.props.events}/>,
|
|
<div key={"footer"} className={cssStyle.containerFooter}>
|
|
<RefreshButton events={this.props.events}/>
|
|
</div>
|
|
]
|
|
}
|
|
|
|
componentDidMount(): void {
|
|
this.props.events.fire("action_set_mode", {mode: "unset"});
|
|
}
|
|
} |