Updated the permission editor to be popoutable
parent
dd7fa648c4
commit
b1c2b80ec8
|
@ -8,7 +8,7 @@ import {openBanList} from "../ui/modal/ModalBanList";
|
||||||
import {formatMessage} from "../ui/frames/chat";
|
import {formatMessage} from "../ui/frames/chat";
|
||||||
import {CommandResult} from "../connection/ServerConnectionDeclaration";
|
import {CommandResult} from "../connection/ServerConnectionDeclaration";
|
||||||
import {spawnSettingsModal} from "../ui/modal/ModalSettings";
|
import {spawnSettingsModal} from "../ui/modal/ModalSettings";
|
||||||
import {spawnPermissionEditorModal} from "../ui/modal/permission/ModalPermissionEditor";
|
import {spawnPermissionEditorModal} from "../ui/modal/permission/ModalController";
|
||||||
import {tr, tra} from "../i18n/localize";
|
import {tr, tra} from "../i18n/localize";
|
||||||
import {spawnGlobalSettingsEditor} from "tc-shared/ui/modal/global-settings-editor/Controller";
|
import {spawnGlobalSettingsEditor} from "tc-shared/ui/modal/global-settings-editor/Controller";
|
||||||
import {spawnModalCssVariableEditor} from "tc-shared/ui/modal/css-editor/Controller";
|
import {spawnModalCssVariableEditor} from "tc-shared/ui/modal/css-editor/Controller";
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {ConnectionHandler} from "../ConnectionHandler";
|
import {ConnectionHandler} from "../ConnectionHandler";
|
||||||
import {Registry} from "../events";
|
import {Registry} from "../events";
|
||||||
import {VideoBroadcastType} from "tc-shared/connection/VideoConnection";
|
import {VideoBroadcastType} from "tc-shared/connection/VideoConnection";
|
||||||
|
import {PermissionEditorTab} from "tc-shared/ui/modal/permission/ModalDefinitions";
|
||||||
|
|
||||||
export type PermissionEditorTab = "groups-server" | "groups-channel" | "channel" | "client" | "client-channel";
|
|
||||||
export interface ClientGlobalControlEvents {
|
export interface ClientGlobalControlEvents {
|
||||||
/* open a basic window */
|
/* open a basic window */
|
||||||
action_open_window: {
|
action_open_window: {
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {spawnYesNo} from "../ui/modal/ModalYesNo";
|
||||||
import * as hex from "../crypto/hex";
|
import * as hex from "../crypto/hex";
|
||||||
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
|
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
|
||||||
import {spawnClientVolumeChange, spawnMusicBotVolumeChange} from "../ui/modal/ModalChangeVolumeNew";
|
import {spawnClientVolumeChange, spawnMusicBotVolumeChange} from "../ui/modal/ModalChangeVolumeNew";
|
||||||
import {spawnPermissionEditorModal} from "../ui/modal/permission/ModalPermissionEditor";
|
import {spawnPermissionEditorModal} from "../ui/modal/permission/ModalController";
|
||||||
import {global_client_actions} from "../events/GlobalEvents";
|
import {global_client_actions} from "../events/GlobalEvents";
|
||||||
import {ClientIcon} from "svg-sprites/client-icons";
|
import {ClientIcon} from "svg-sprites/client-icons";
|
||||||
import {VoiceClient} from "../voice/VoiceClient";
|
import {VoiceClient} from "../voice/VoiceClient";
|
||||||
|
@ -465,7 +465,10 @@ export class ClientEntry<Events extends ClientEvents = ClientEvents> extends Cha
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
icon_class: "client-permission_client",
|
icon_class: "client-permission_client",
|
||||||
name: tr("Client channel permissions"),
|
name: tr("Client channel permissions"),
|
||||||
callback: () => spawnPermissionEditorModal(this.channelTree.client, "client-channel", { clientDatabaseId: this.properties.client_database_id })
|
callback: () => spawnPermissionEditorModal(this.channelTree.client, "client-channel", {
|
||||||
|
clientDatabaseId: this.properties.client_database_id,
|
||||||
|
channelId: this.currentChannel()?.channelId
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}];
|
}];
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
export interface EditorGroupedPermissions {
|
||||||
|
groupId: string,
|
||||||
|
groupName: string,
|
||||||
|
permissions: {
|
||||||
|
id: number,
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}[],
|
||||||
|
children: EditorGroupedPermissions[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
}
|
|
@ -121,7 +121,7 @@ html:root {
|
||||||
.button {
|
.button {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
:global(.arrow) {
|
.arrow {
|
||||||
border-color: var(--text);
|
border-color: var(--text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,7 +271,7 @@ html:root {
|
||||||
color: var(--modal-permissions-table-entry-group-text) !important;
|
color: var(--modal-permissions-table-entry-group-text) !important;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
:global(.arrow) {
|
.arrow {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-color: var(--modal-permissions-table-entry-active-text);
|
border-color: var(--modal-permissions-table-entry-active-text);
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {useEffect, useRef, useState} from "react";
|
import {useContext, useEffect, useRef, useState} from "react";
|
||||||
import {FlatInputField} from "tc-shared/ui/react-elements/InputField";
|
import {FlatInputField} from "tc-shared/ui/react-elements/InputField";
|
||||||
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
||||||
import {EventHandler, ReactEventHandler, Registry} from "tc-shared/events";
|
import {EventHandler, ReactEventHandler, Registry} from "tc-shared/events";
|
||||||
|
@ -11,125 +11,32 @@ import ResizeObserver from "resize-observer-polyfill";
|
||||||
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
|
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
|
||||||
import {Button} from "tc-shared/ui/react-elements/Button";
|
import {Button} from "tc-shared/ui/react-elements/Button";
|
||||||
import {IconRenderer, RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon";
|
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 {copyToClipboard} from "tc-shared/utils/helpers";
|
import {copyToClipboard} from "tc-shared/utils/helpers";
|
||||||
import {createInfoModal} from "tc-shared/ui/elements/Modal";
|
import {createInfoModal} from "tc-shared/ui/elements/Modal";
|
||||||
import {getIconManager} from "tc-shared/file/Icons";
|
import {getIconManager} from "tc-shared/file/Icons";
|
||||||
|
import {
|
||||||
|
EditorGroupedPermissions,
|
||||||
|
PermissionEditorEvents,
|
||||||
|
PermissionEditorMode
|
||||||
|
} from "tc-shared/ui/modal/permission/EditorDefinitions";
|
||||||
|
import {ContextMenuEntry, spawnContextMenu} from "tc-shared/ui/ContextMenu";
|
||||||
|
import {Arrow} from "tc-shared/ui/react-elements/Arrow";
|
||||||
|
|
||||||
const cssStyle = require("./PermissionEditor.scss");
|
const cssStyle = require("./EditorRenderer.scss");
|
||||||
|
|
||||||
export interface EditorGroupedPermissions {
|
const EventContext = React.createContext<Registry<PermissionEditorEvents>>(undefined);
|
||||||
groupId: string,
|
const ServerInfoContext = React.createContext<{ handlerId: string, serverUniqueId: string }>(undefined);
|
||||||
groupName: string,
|
|
||||||
permissions: {
|
|
||||||
id: number,
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
}[],
|
|
||||||
children: EditorGroupedPermissions[]
|
|
||||||
}
|
|
||||||
|
|
||||||
type PermissionEditorMode = "unset" | "no-permissions" | "normal";
|
const ButtonIconPreview = React.memo(() => {
|
||||||
|
const serverInfo = useContext(ServerInfoContext);
|
||||||
|
const events = useContext(EventContext);
|
||||||
|
|
||||||
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 [iconId, setIconId] = useState(0);
|
||||||
const [unset, setUnset] = useState(true);
|
const [unset, setUnset] = useState(true);
|
||||||
|
|
||||||
props.events.reactUse("action_set_mode", event => setUnset(event.mode !== "normal"));
|
events.reactUse("action_set_mode", event => setUnset(event.mode !== "normal"));
|
||||||
|
|
||||||
props.events.reactUse("action_remove_permissions_result", event => {
|
events.reactUse("action_remove_permissions_result", event => {
|
||||||
const iconPermission = event.permissions.find(e => e.name === PermissionType.I_ICON_ID);
|
const iconPermission = event.permissions.find(e => e.name === PermissionType.I_ICON_ID);
|
||||||
if (!iconPermission || !iconPermission.success) return;
|
if (!iconPermission || !iconPermission.success) return;
|
||||||
|
|
||||||
|
@ -137,7 +44,7 @@ const ButtonIconPreview = (props: { events: Registry<PermissionEditorEvents>, co
|
||||||
setIconId(0);
|
setIconId(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
props.events.reactUse("action_set_permissions_result", event => {
|
events.reactUse("action_set_permissions_result", event => {
|
||||||
const iconPermission = event.permissions.find(e => e.name === PermissionType.I_ICON_ID);
|
const iconPermission = event.permissions.find(e => e.name === PermissionType.I_ICON_ID);
|
||||||
if (!iconPermission) return;
|
if (!iconPermission) return;
|
||||||
|
|
||||||
|
@ -145,7 +52,7 @@ const ButtonIconPreview = (props: { events: Registry<PermissionEditorEvents>, co
|
||||||
setIconId(iconPermission.newValue);
|
setIconId(iconPermission.newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
props.events.reactUse("query_permission_values_result", event => {
|
events.reactUse("query_permission_values_result", event => {
|
||||||
if (event.status !== "success") {
|
if (event.status !== "success") {
|
||||||
setIconId(0);
|
setIconId(0);
|
||||||
return;
|
return;
|
||||||
|
@ -166,63 +73,73 @@ const ButtonIconPreview = (props: { events: Registry<PermissionEditorEvents>, co
|
||||||
|
|
||||||
let icon;
|
let icon;
|
||||||
if (!unset && iconId > 0) {
|
if (!unset && iconId > 0) {
|
||||||
icon = <RemoteIconRenderer key={"icon-" + iconId} icon={getIconManager().resolveIcon(iconId, props.connection.getCurrentServerUniqueId(), props.connection.handlerId)} />;
|
icon = <RemoteIconRenderer key={"icon-" + iconId} icon={getIconManager().resolveIcon(iconId, serverInfo.serverUniqueId, serverInfo.handlerId)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssStyle.containerIconSelect}>
|
<div className={cssStyle.containerIconSelect}>
|
||||||
<div className={cssStyle.preview}
|
<div className={cssStyle.preview}
|
||||||
onClick={() => props.events.fire("action_open_icon_select", {iconId: iconId})}>
|
onClick={() => events.fire("action_open_icon_select", {iconId: iconId})}>
|
||||||
{icon}
|
{icon}
|
||||||
</div>
|
</div>
|
||||||
<div className={cssStyle.containerDropdown}>
|
<div className={cssStyle.containerDropdown}>
|
||||||
<div className={cssStyle.button}>
|
<div className={cssStyle.button}>
|
||||||
<div className="arrow down"/>
|
<Arrow direction={"down"} className={cssStyle.arrow} />
|
||||||
</div>
|
</div>
|
||||||
<div className={cssStyle.dropdown}>
|
<div className={cssStyle.dropdown}>
|
||||||
{iconId ? <div className={cssStyle.entry} key={"edit-icon"}
|
{iconId ? (
|
||||||
onClick={() => props.events.fire("action_open_icon_select", {iconId: iconId})}>
|
<div className={cssStyle.entry} key={"edit-icon"}
|
||||||
|
onClick={() => events.fire("action_open_icon_select", {iconId: iconId})}>
|
||||||
<Translatable>Edit icon</Translatable>
|
<Translatable>Edit icon</Translatable>
|
||||||
</div> : undefined}
|
</div>
|
||||||
{iconId ? <div className={cssStyle.entry} key={"remove-icon"}
|
) : undefined}
|
||||||
onClick={() => props.events.fire("action_remove_permissions", {
|
{iconId ? (
|
||||||
|
<div className={cssStyle.entry} key={"remove-icon"}
|
||||||
|
onClick={() => events.fire("action_remove_permissions", {
|
||||||
permissions: [{
|
permissions: [{
|
||||||
name: PermissionType.I_ICON_ID,
|
name: PermissionType.I_ICON_ID,
|
||||||
mode: "value"
|
mode: "value"
|
||||||
}]
|
}]
|
||||||
})}>
|
})}>
|
||||||
<Translatable>Remove icon</Translatable>
|
<Translatable>Remove icon</Translatable>
|
||||||
</div> : undefined}
|
</div>
|
||||||
{!iconId ? <div className={cssStyle.entry} key={"add-icon"}
|
) : undefined}
|
||||||
onClick={() => props.events.fire("action_open_icon_select", {iconId: 0})}>
|
{!iconId ? (
|
||||||
|
<div className={cssStyle.entry} key={"add-icon"}
|
||||||
|
onClick={() => events.fire("action_open_icon_select", {iconId: 0})}>
|
||||||
<Translatable>Add icon</Translatable>
|
<Translatable>Add icon</Translatable>
|
||||||
</div> : undefined}
|
</div>
|
||||||
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const ClientListButton = (props: { events: Registry<PermissionEditorEvents> }) => {
|
const ClientListButton = () => {
|
||||||
|
const events = useContext(EventContext);
|
||||||
const [visible, setVisible] = useState(true);
|
const [visible, setVisible] = useState(true);
|
||||||
const [toggled, setToggled] = useState(false);
|
const [toggled, setToggled] = useState(false);
|
||||||
|
|
||||||
props.events.reactUse("action_toggle_client_button", event => setVisible(event.visible));
|
events.reactUse("action_toggle_client_button", event => setVisible(event.visible));
|
||||||
props.events.reactUse("action_toggle_client_list", event => setToggled(event.visible));
|
events.reactUse("action_toggle_client_list", event => setToggled(event.visible));
|
||||||
|
|
||||||
return <Button
|
return <Button
|
||||||
key={"button-clients"}
|
key={"button-clients"}
|
||||||
className={cssStyle.clients + " " + (visible ? "" : cssStyle.hidden)}
|
className={cssStyle.clients + " " + (visible ? "" : cssStyle.hidden)}
|
||||||
color={"green"}
|
color={"green"}
|
||||||
onClick={() => props.events.fire("action_toggle_client_list", {visible: !toggled})}>
|
onClick={() => events.fire("action_toggle_client_list", {visible: !toggled})}>
|
||||||
{toggled ? <Translatable key={"hide"}>Hide clients in group</Translatable> :
|
{toggled ? <Translatable key={"hide"}>Hide clients in group</Translatable> :
|
||||||
<Translatable key={"show"}>Show clients in group</Translatable>}
|
<Translatable key={"show"}>Show clients in group</Translatable>}
|
||||||
</Button>
|
</Button>
|
||||||
};
|
};
|
||||||
|
|
||||||
const MenuBar = (props: { events: Registry<PermissionEditorEvents>, connection: ConnectionHandler }) => {
|
const MenuBar = React.memo(() => {
|
||||||
return <div className={cssStyle.containerMenuBar}>
|
const events = useContext(EventContext);
|
||||||
<ClientListButton events={props.events}/>
|
|
||||||
|
return (
|
||||||
|
<div className={cssStyle.containerMenuBar}>
|
||||||
|
<ClientListButton />
|
||||||
<FlatInputField
|
<FlatInputField
|
||||||
className={cssStyle.filter}
|
className={cssStyle.filter}
|
||||||
|
|
||||||
|
@ -230,16 +147,20 @@ const MenuBar = (props: { events: Registry<PermissionEditorEvents>, connection:
|
||||||
labelType={"floating"}
|
labelType={"floating"}
|
||||||
labelClassName={cssStyle.label}
|
labelClassName={cssStyle.label}
|
||||||
labelFloatingClassName={cssStyle.labelFloating}
|
labelFloatingClassName={cssStyle.labelFloating}
|
||||||
onInput={text => props.events.fire("action_set_filter", {filter: text})}
|
onInput={text => events.fire("action_set_filter", {filter: text})}
|
||||||
/>
|
/>
|
||||||
<div className={cssStyle.options}>
|
<div className={cssStyle.options}>
|
||||||
<Switch initialState={false} label={<Translatable>Assigned only</Translatable>}
|
<Switch
|
||||||
onChange={state => props.events.fire("action_set_assigned_only", {value: state})}/>
|
initialState={false}
|
||||||
|
label={<Translatable>Assigned only</Translatable>}
|
||||||
|
onChange={state => events.fire("action_set_assigned_only", {value: state})}
|
||||||
|
/>
|
||||||
{ /* <Switch initialState={true} label={<Translatable>Editable only</Translatable>} /> */}
|
{ /* <Switch initialState={true} label={<Translatable>Editable only</Translatable>} /> */}
|
||||||
</div>
|
</div>
|
||||||
<ButtonIconPreview events={props.events} connection={props.connection}/>
|
<ButtonIconPreview />
|
||||||
</div>;
|
</div>
|
||||||
};
|
);
|
||||||
|
});
|
||||||
|
|
||||||
interface LinkedGroupedPermissions {
|
interface LinkedGroupedPermissions {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
|
@ -266,8 +187,7 @@ interface LinkedGroupedPermissions {
|
||||||
elementVisible: boolean;
|
elementVisible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PermissionEntryRow = (props: {
|
const PermissionEntryRow = React.memo((props: {
|
||||||
events: Registry<PermissionEditorEvents>,
|
|
||||||
groupId: string,
|
groupId: string,
|
||||||
permission: string,
|
permission: string,
|
||||||
value: PermissionValue,
|
value: PermissionValue,
|
||||||
|
@ -277,6 +197,8 @@ const PermissionEntryRow = (props: {
|
||||||
defaultValue: number,
|
defaultValue: number,
|
||||||
description: string
|
description: string
|
||||||
}) => {
|
}) => {
|
||||||
|
const events = useContext(EventContext);
|
||||||
|
|
||||||
const [defaultValue, setDefaultValue] = useState(props.defaultValue);
|
const [defaultValue, setDefaultValue] = useState(props.defaultValue);
|
||||||
const [value, setValue] = useState<number>(props.value.value);
|
const [value, setValue] = useState<number>(props.value.value);
|
||||||
const [forceValueUpdate, setForceValueUpdate] = useState(false);
|
const [forceValueUpdate, setForceValueUpdate] = useState(false);
|
||||||
|
@ -306,7 +228,7 @@ const PermissionEntryRow = (props: {
|
||||||
if (isBoolPermission) {
|
if (isBoolPermission) {
|
||||||
valueElement = <Switch ref={refValueB} key={"value-b"} initialState={value >= 1} disabled={valueApplying}
|
valueElement = <Switch ref={refValueB} key={"value-b"} initialState={value >= 1} disabled={valueApplying}
|
||||||
onChange={flag => {
|
onChange={flag => {
|
||||||
props.events.fire("action_set_permissions", {
|
events.fire("action_set_permissions", {
|
||||||
permissions: [{
|
permissions: [{
|
||||||
name: props.permission,
|
name: props.permission,
|
||||||
mode: "value",
|
mode: "value",
|
||||||
|
@ -337,7 +259,7 @@ const PermissionEntryRow = (props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
setForceValueUpdate(false);
|
setForceValueUpdate(false);
|
||||||
props.events.fire("action_remove_permissions", {
|
events.fire("action_remove_permissions", {
|
||||||
permissions: [{
|
permissions: [{
|
||||||
name: props.permission,
|
name: props.permission,
|
||||||
mode: "value"
|
mode: "value"
|
||||||
|
@ -352,7 +274,7 @@ const PermissionEntryRow = (props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
setForceValueUpdate(false);
|
setForceValueUpdate(false);
|
||||||
props.events.fire("action_set_permissions", {
|
events.fire("action_set_permissions", {
|
||||||
permissions: [{
|
permissions: [{
|
||||||
name: props.permission,
|
name: props.permission,
|
||||||
mode: "value",
|
mode: "value",
|
||||||
|
@ -367,7 +289,7 @@ const PermissionEntryRow = (props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
skipElement = <Switch key={"skip"} initialState={flagSkip} disabled={valueApplying} onChange={flag => {
|
skipElement = <Switch key={"skip"} initialState={flagSkip} disabled={valueApplying} onChange={flag => {
|
||||||
props.events.fire("action_set_permissions", {
|
events.fire("action_set_permissions", {
|
||||||
permissions: [{
|
permissions: [{
|
||||||
name: props.permission,
|
name: props.permission,
|
||||||
mode: "value",
|
mode: "value",
|
||||||
|
@ -378,7 +300,7 @@ const PermissionEntryRow = (props: {
|
||||||
});
|
});
|
||||||
}}/>;
|
}}/>;
|
||||||
negateElement = <Switch key={"negate"} initialState={flagNegated} disabled={valueApplying} onChange={flag => {
|
negateElement = <Switch key={"negate"} initialState={flagNegated} disabled={valueApplying} onChange={flag => {
|
||||||
props.events.fire("action_set_permissions", {
|
events.fire("action_set_permissions", {
|
||||||
permissions: [{
|
permissions: [{
|
||||||
name: props.permission,
|
name: props.permission,
|
||||||
mode: "value",
|
mode: "value",
|
||||||
|
@ -392,12 +314,24 @@ const PermissionEntryRow = (props: {
|
||||||
|
|
||||||
if (typeof granted === "number") {
|
if (typeof granted === "number") {
|
||||||
if (grantedApplying) {
|
if (grantedApplying) {
|
||||||
grantedElement =
|
grantedElement = (
|
||||||
<input key={"grant-applying"} className={cssStyle.applying} type="number" placeholder={tr("applying")}
|
<input
|
||||||
readOnly={true} onChange={() => {
|
key={"grant-applying"}
|
||||||
}}/>;
|
className={cssStyle.applying}
|
||||||
|
type="number"
|
||||||
|
placeholder={tr("applying")}
|
||||||
|
readOnly={true}
|
||||||
|
onChange={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
grantedElement = <input ref={refGranted} key={"grant"} type="number" defaultValue={granted} onBlur={() => {
|
grantedElement = (
|
||||||
|
<input
|
||||||
|
ref={refGranted}
|
||||||
|
key={"grant"}
|
||||||
|
type="number"
|
||||||
|
defaultValue={granted}
|
||||||
|
onBlur={() => {
|
||||||
setGrantedEditing(false);
|
setGrantedEditing(false);
|
||||||
if (!refGranted.current)
|
if (!refGranted.current)
|
||||||
return;
|
return;
|
||||||
|
@ -408,7 +342,7 @@ const PermissionEntryRow = (props: {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
setForceGrantedUpdate(true);
|
setForceGrantedUpdate(true);
|
||||||
props.events.fire("action_remove_permissions", {
|
events.fire("action_remove_permissions", {
|
||||||
permissions: [{
|
permissions: [{
|
||||||
name: props.permission,
|
name: props.permission,
|
||||||
mode: "grant"
|
mode: "grant"
|
||||||
|
@ -423,7 +357,7 @@ const PermissionEntryRow = (props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
setForceGrantedUpdate(true);
|
setForceGrantedUpdate(true);
|
||||||
props.events.fire("action_set_permissions", {
|
events.fire("action_set_permissions", {
|
||||||
permissions: [{
|
permissions: [{
|
||||||
name: props.permission,
|
name: props.permission,
|
||||||
mode: "grant",
|
mode: "grant",
|
||||||
|
@ -431,12 +365,16 @@ const PermissionEntryRow = (props: {
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}} onChange={() => {
|
}}
|
||||||
}} onKeyPress={e => e.key === "Enter" && e.currentTarget.blur()}/>;
|
onChange={() => {
|
||||||
|
}}
|
||||||
|
onKeyPress={e => e.key === "Enter" && e.currentTarget.blur()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
props.events.reactUse("action_start_permission_edit", event => {
|
events.reactUse("action_start_permission_edit", event => {
|
||||||
if (event.permission !== props.permission)
|
if (event.permission !== props.permission)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -447,7 +385,7 @@ const PermissionEntryRow = (props: {
|
||||||
} else {
|
} else {
|
||||||
if (isBoolPermission && typeof value === "undefined") {
|
if (isBoolPermission && typeof value === "undefined") {
|
||||||
setValue(event.defaultValue >= 1 ? 1 : 0);
|
setValue(event.defaultValue >= 1 ? 1 : 0);
|
||||||
props.events.fire("action_set_permissions", {
|
events.fire("action_set_permissions", {
|
||||||
permissions: [{
|
permissions: [{
|
||||||
name: props.permission,
|
name: props.permission,
|
||||||
mode: "value",
|
mode: "value",
|
||||||
|
@ -464,7 +402,7 @@ const PermissionEntryRow = (props: {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
props.events.reactUse("action_set_permissions", event => {
|
events.reactUse("action_set_permissions", event => {
|
||||||
const values = event.permissions.find(e => e.name === props.permission);
|
const values = event.permissions.find(e => e.name === props.permission);
|
||||||
if (!values) return;
|
if (!values) return;
|
||||||
|
|
||||||
|
@ -477,7 +415,7 @@ const PermissionEntryRow = (props: {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
props.events.reactUse("action_set_permissions_result", event => {
|
events.reactUse("action_set_permissions_result", event => {
|
||||||
const result = event.permissions.find(e => e.name === props.permission);
|
const result = event.permissions.find(e => e.name === props.permission);
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
|
@ -518,7 +456,7 @@ const PermissionEntryRow = (props: {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
props.events.reactUse("action_remove_permissions", event => {
|
events.reactUse("action_remove_permissions", event => {
|
||||||
const modes = event.permissions.find(e => e.name === props.permission);
|
const modes = event.permissions.find(e => e.name === props.permission);
|
||||||
if (!modes) return;
|
if (!modes) return;
|
||||||
|
|
||||||
|
@ -533,7 +471,7 @@ const PermissionEntryRow = (props: {
|
||||||
setGrantedApplying(true);
|
setGrantedApplying(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
props.events.reactUse("action_remove_permissions_result", event => {
|
events.reactUse("action_remove_permissions_result", event => {
|
||||||
const modes = event.permissions.find(e => e.name === props.permission);
|
const modes = event.permissions.find(e => e.name === props.permission);
|
||||||
if (!modes) return;
|
if (!modes) return;
|
||||||
|
|
||||||
|
@ -553,7 +491,7 @@ const PermissionEntryRow = (props: {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
props.events.reactUse("action_set_default_value", event => setDefaultValue(event.value));
|
events.reactUse("action_set_default_value", event => setDefaultValue(event.value));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (grantedEditing)
|
if (grantedEditing)
|
||||||
|
@ -573,7 +511,7 @@ const PermissionEntryRow = (props: {
|
||||||
if (e.isDefaultPrevented())
|
if (e.isDefaultPrevented())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
props.events.fire("action_start_permission_edit", {
|
events.fire("action_start_permission_edit", {
|
||||||
permission: props.permission,
|
permission: props.permission,
|
||||||
target: "value",
|
target: "value",
|
||||||
defaultValue: defaultValue
|
defaultValue: defaultValue
|
||||||
|
@ -583,12 +521,12 @@ const PermissionEntryRow = (props: {
|
||||||
onContextMenu={e => {
|
onContextMenu={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
let entries: contextmenu.MenuEntry[] = [];
|
let entries: ContextMenuEntry[] = [];
|
||||||
if (typeof value === "undefined") {
|
if (typeof value === "undefined") {
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Add permission"),
|
label: tr("Add permission"),
|
||||||
callback: () => props.events.fire("action_start_permission_edit", {
|
click: () => events.fire("action_start_permission_edit", {
|
||||||
permission: props.permission,
|
permission: props.permission,
|
||||||
target: "value",
|
target: "value",
|
||||||
defaultValue: defaultValue
|
defaultValue: defaultValue
|
||||||
|
@ -596,9 +534,9 @@ const PermissionEntryRow = (props: {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Remove permission"),
|
label: tr("Remove permission"),
|
||||||
callback: () => props.events.fire("action_remove_permissions", {
|
click: () => events.fire("action_remove_permissions", {
|
||||||
permissions: [{
|
permissions: [{
|
||||||
name: props.permission,
|
name: props.permission,
|
||||||
mode: "value"
|
mode: "value"
|
||||||
|
@ -609,9 +547,9 @@ const PermissionEntryRow = (props: {
|
||||||
|
|
||||||
if (typeof granted === "undefined") {
|
if (typeof granted === "undefined") {
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Add grant permission"),
|
label: tr("Add grant permission"),
|
||||||
callback: () => props.events.fire("action_start_permission_edit", {
|
click: () => events.fire("action_start_permission_edit", {
|
||||||
permission: props.permission,
|
permission: props.permission,
|
||||||
target: "grant",
|
target: "grant",
|
||||||
defaultValue: defaultValue
|
defaultValue: defaultValue
|
||||||
|
@ -619,9 +557,9 @@ const PermissionEntryRow = (props: {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Remove grant permission"),
|
label: tr("Remove grant permission"),
|
||||||
callback: () => props.events.fire("action_remove_permissions", {
|
click: () => events.fire("action_remove_permissions", {
|
||||||
permissions: [{
|
permissions: [{
|
||||||
name: props.permission,
|
name: props.permission,
|
||||||
mode: "grant"
|
mode: "grant"
|
||||||
|
@ -629,28 +567,27 @@ const PermissionEntryRow = (props: {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
entries.push({ type: "separator" });
|
||||||
entries.push(contextmenu.Entry.HR());
|
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Collapse group"),
|
label: tr("Collapse group"),
|
||||||
callback: () => props.events.fire("action_toggle_group", {groupId: props.groupId, collapsed: true})
|
click: () => events.fire("action_toggle_group", {groupId: props.groupId, collapsed: true})
|
||||||
});
|
});
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Expend all"),
|
label: tr("Expend all"),
|
||||||
callback: () => props.events.fire("action_toggle_group", {groupId: null, collapsed: false})
|
click: () => events.fire("action_toggle_group", {groupId: null, collapsed: false})
|
||||||
});
|
});
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Collapse all"),
|
label: tr("Collapse all"),
|
||||||
callback: () => props.events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
click: () => events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
||||||
});
|
});
|
||||||
entries.push(contextmenu.Entry.HR());
|
entries.push({ type: "separator" });
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Show permission description"),
|
label: tr("Show permission description"),
|
||||||
callback: () => {
|
click: () => {
|
||||||
createInfoModal(
|
createInfoModal(
|
||||||
tr("Permission description"),
|
tr("Permission description"),
|
||||||
tr("Permission description for permission ") + props.permission + ": <br>" + props.description
|
tr("Permission description for permission ") + props.permission + ": <br>" + props.description
|
||||||
|
@ -658,12 +595,12 @@ const PermissionEntryRow = (props: {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Copy permission name"),
|
label: tr("Copy permission name"),
|
||||||
callback: () => copyToClipboard(props.permission)
|
click: () => copyToClipboard(props.permission)
|
||||||
});
|
});
|
||||||
|
|
||||||
contextmenu.spawn_context_menu(e.pageX, e.pageY, ...entries);
|
spawnContextMenu({ pageX: e.pageX, pageY: e.pageY }, entries);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={cssStyle.columnName}>
|
<div className={cssStyle.columnName}>
|
||||||
|
@ -673,7 +610,7 @@ const PermissionEntryRow = (props: {
|
||||||
<div className={cssStyle.columnSkip}>{skipElement}</div>
|
<div className={cssStyle.columnSkip}>{skipElement}</div>
|
||||||
<div className={cssStyle.columnNegate}>{negateElement}</div>
|
<div className={cssStyle.columnNegate}>{negateElement}</div>
|
||||||
<div className={cssStyle.columnGranted} onDoubleClick={e => {
|
<div className={cssStyle.columnGranted} onDoubleClick={e => {
|
||||||
props.events.fire("action_start_permission_edit", {
|
events.fire("action_start_permission_edit", {
|
||||||
permission: props.permission,
|
permission: props.permission,
|
||||||
target: "grant",
|
target: "grant",
|
||||||
defaultValue: defaultValue
|
defaultValue: defaultValue
|
||||||
|
@ -682,12 +619,13 @@ const PermissionEntryRow = (props: {
|
||||||
}}>{grantedElement}</div>
|
}}>{grantedElement}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const PermissionGroupRow = (props: { events: Registry<PermissionEditorEvents>, group: LinkedGroupedPermissions, isOdd: boolean, offsetTop: number }) => {
|
const PermissionGroupRow = React.memo((props: { group: LinkedGroupedPermissions, isOdd: boolean, offsetTop: number }) => {
|
||||||
|
const events = useContext(EventContext);
|
||||||
const [collapsed, setCollapsed] = useState(props.group.collapsed);
|
const [collapsed, setCollapsed] = useState(props.group.collapsed);
|
||||||
|
|
||||||
props.events.reactUse("action_toggle_group", event => {
|
events.reactUse("action_toggle_group", event => {
|
||||||
if (event.groupId !== null && event.groupId !== props.group.groupId)
|
if (event.groupId !== null && event.groupId !== props.group.groupId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -699,82 +637,86 @@ const PermissionGroupRow = (props: { events: Registry<PermissionEditorEvents>, g
|
||||||
style={{paddingLeft: props.group.depth + "em", top: props.offsetTop}} onContextMenu={e => {
|
style={{paddingLeft: props.group.depth + "em", top: props.offsetTop}} onContextMenu={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
let entries = [];
|
let entries: ContextMenuEntry[] = [];
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Add permissions to this group"),
|
label: tr("Add permissions to this group"),
|
||||||
callback: () => props.events.fire("action_add_permission_group", {
|
click: () => events.fire("action_add_permission_group", {
|
||||||
groupId: props.group.groupId,
|
groupId: props.group.groupId,
|
||||||
mode: "value"
|
mode: "value"
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Remove permissions from this group"),
|
label: tr("Remove permissions from this group"),
|
||||||
callback: () => props.events.fire("action_remove_permission_group", {
|
click: () => events.fire("action_remove_permission_group", {
|
||||||
groupId: props.group.groupId,
|
groupId: props.group.groupId,
|
||||||
mode: "value"
|
mode: "value"
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Add granted permissions to this group"),
|
label: tr("Add granted permissions to this group"),
|
||||||
callback: () => props.events.fire("action_add_permission_group", {
|
click: () => events.fire("action_add_permission_group", {
|
||||||
groupId: props.group.groupId,
|
groupId: props.group.groupId,
|
||||||
mode: "grant"
|
mode: "grant"
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Remove granted permissions from this group"),
|
label: tr("Remove granted permissions from this group"),
|
||||||
callback: () => props.events.fire("action_remove_permission_group", {
|
click: () => events.fire("action_remove_permission_group", {
|
||||||
groupId: props.group.groupId,
|
groupId: props.group.groupId,
|
||||||
mode: "grant"
|
mode: "grant"
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
entries.push(contextmenu.Entry.HR());
|
entries.push({ type: "separator" });
|
||||||
if (collapsed) {
|
if (collapsed) {
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Expend group"),
|
label: tr("Expend group"),
|
||||||
callback: () => props.events.fire("action_toggle_group", {
|
click: () => events.fire("action_toggle_group", {
|
||||||
groupId: props.group.groupId,
|
groupId: props.group.groupId,
|
||||||
collapsed: false
|
collapsed: false
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Collapse group"),
|
label: tr("Collapse group"),
|
||||||
callback: () => props.events.fire("action_toggle_group", {
|
click: () => events.fire("action_toggle_group", {
|
||||||
groupId: props.group.groupId,
|
groupId: props.group.groupId,
|
||||||
collapsed: true
|
collapsed: true
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Expend all"),
|
label: tr("Expend all"),
|
||||||
callback: () => props.events.fire("action_toggle_group", {groupId: null, collapsed: false})
|
click: () => events.fire("action_toggle_group", {groupId: null, collapsed: false})
|
||||||
});
|
});
|
||||||
entries.push({
|
entries.push({
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Collapse all"),
|
label: tr("Collapse all"),
|
||||||
callback: () => props.events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
click: () => events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
||||||
});
|
});
|
||||||
contextmenu.spawn_context_menu(e.pageX, e.pageY, ...entries);
|
|
||||||
|
spawnContextMenu({ pageX: e.pageX, pageY: e.pageY }, entries);
|
||||||
}}
|
}}
|
||||||
onDoubleClick={() => props.events.fire("action_toggle_group", {
|
onDoubleClick={() => events.fire("action_toggle_group", {
|
||||||
collapsed: !collapsed,
|
collapsed: !collapsed,
|
||||||
groupId: props.group.groupId
|
groupId: props.group.groupId
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className={cssStyle.columnName}>
|
<div className={cssStyle.columnName}>
|
||||||
<div className={"arrow " + (collapsed ? "right" : "down")}
|
<Arrow
|
||||||
onClick={() => props.events.fire("action_toggle_group", {
|
className={cssStyle.arrow}
|
||||||
|
direction={collapsed ? "right" : "down"}
|
||||||
|
onClick={() => events.fire("action_toggle_group", {
|
||||||
collapsed: !collapsed,
|
collapsed: !collapsed,
|
||||||
groupId: props.group.groupId
|
groupId: props.group.groupId
|
||||||
})}/>
|
})}
|
||||||
|
/>
|
||||||
<div className={cssStyle.groupName} title={/* @tr-ignore */ tr(props.group.groupName)}>
|
<div className={cssStyle.groupName} title={/* @tr-ignore */ tr(props.group.groupName)}>
|
||||||
<Translatable trIgnore={true}>{props.group.groupName}</Translatable>
|
<Translatable trIgnore={true}>{props.group.groupName}</Translatable>
|
||||||
</div>
|
</div>
|
||||||
|
@ -785,7 +727,7 @@ const PermissionGroupRow = (props: { events: Registry<PermissionEditorEvents>, g
|
||||||
<div className={cssStyle.columnGranted}/>
|
<div className={cssStyle.columnGranted}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
type PermissionValue = { value?: number, flagNegate?: boolean, flagSkip?: boolean, granted?: number };
|
type PermissionValue = { value?: number, flagNegate?: boolean, flagSkip?: boolean, granted?: number };
|
||||||
|
|
||||||
|
@ -835,18 +777,20 @@ class PermissionList extends React.Component<{ events: Registry<PermissionEditor
|
||||||
return;
|
return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
contextmenu.spawn_context_menu(e.pageX, e.pageY, {
|
spawnContextMenu({ pageX: e.pageX, pageY: e.pageY }, [
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
{
|
||||||
name: tr("Expend all"),
|
type: "normal",
|
||||||
callback: () => this.props.events.fire("action_toggle_group", {
|
label: tr("Expend all"),
|
||||||
|
click: () => this.props.events.fire("action_toggle_group", {
|
||||||
groupId: null,
|
groupId: null,
|
||||||
collapsed: false
|
collapsed: false
|
||||||
})
|
})
|
||||||
}, {
|
}, {
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
name: tr("Collapse all"),
|
label: tr("Collapse all"),
|
||||||
callback: () => this.props.events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
click: () => this.props.events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
||||||
});
|
}
|
||||||
|
]);
|
||||||
}}>
|
}}>
|
||||||
{elements}
|
{elements}
|
||||||
<div key={"space"} className={cssStyle.spaceAllocator}
|
<div key={"space"} className={cssStyle.spaceAllocator}
|
||||||
|
@ -1225,7 +1169,7 @@ class PermissionList extends React.Component<{ events: Registry<PermissionEditor
|
||||||
while (currentGroup) {
|
while (currentGroup) {
|
||||||
if (currentGroup.elementVisible) {
|
if (currentGroup.elementVisible) {
|
||||||
this.currentListElements.push(<PermissionGroupRow key={"group-" + currentGroup.groupId}
|
this.currentListElements.push(<PermissionGroupRow key={"group-" + currentGroup.groupId}
|
||||||
events={this.props.events} group={currentGroup}
|
group={currentGroup}
|
||||||
isOdd={index % 2 === 1}
|
isOdd={index % 2 === 1}
|
||||||
offsetTop={this.heightPerElement * index}/>);
|
offsetTop={this.heightPerElement * index}/>);
|
||||||
index++;
|
index++;
|
||||||
|
@ -1245,7 +1189,6 @@ class PermissionList extends React.Component<{ events: Registry<PermissionEditor
|
||||||
|
|
||||||
this.currentListElements.push(<PermissionEntryRow
|
this.currentListElements.push(<PermissionEntryRow
|
||||||
key={"permission-" + e.name + " - " + Math.random()} /* force a update of this */
|
key={"permission-" + e.name + " - " + Math.random()} /* force a update of this */
|
||||||
events={this.props.events}
|
|
||||||
permission={e.name}
|
permission={e.name}
|
||||||
groupId={currentGroup.groupId}
|
groupId={currentGroup.groupId}
|
||||||
isOdd={index % 2 === 1}
|
isOdd={index % 2 === 1}
|
||||||
|
@ -1265,11 +1208,12 @@ class PermissionList extends React.Component<{ events: Registry<PermissionEditor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const PermissionTable = (props: { events: Registry<PermissionEditorEvents> }) => {
|
const PermissionTable = React.memo(() => {
|
||||||
|
const events = useContext(EventContext);
|
||||||
const [mode, setMode] = useState<PermissionEditorMode>("unset");
|
const [mode, setMode] = useState<PermissionEditorMode>("unset");
|
||||||
const [failedPermission, setFailedPermission] = useState(undefined);
|
const [failedPermission, setFailedPermission] = useState(undefined);
|
||||||
|
|
||||||
props.events.reactUse("action_set_mode", event => {
|
events.reactUse("action_set_mode", event => {
|
||||||
setMode(event.mode);
|
setMode(event.mode);
|
||||||
setFailedPermission(event.failedPermission);
|
setFailedPermission(event.failedPermission);
|
||||||
});
|
});
|
||||||
|
@ -1295,25 +1239,28 @@ const PermissionTable = (props: { events: Registry<PermissionEditorEvents> }) =>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PermissionList events={props.events}/>
|
<PermissionList events={events} />
|
||||||
<div className={cssStyle.overlay + " " + cssStyle.unset + " " + (mode === "unset" ? "" : cssStyle.hidden)}/>
|
<div className={cssStyle.overlay + " " + cssStyle.unset + " " + (mode === "unset" ? "" : cssStyle.hidden)}/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cssStyle.overlay + " " + cssStyle.noPermissions + " " + (mode === "no-permissions" ? "" : cssStyle.hidden)}>
|
className={cssStyle.overlay + " " + cssStyle.noPermissions + " " + (mode === "no-permissions" ? "" : cssStyle.hidden)}>
|
||||||
<a><Translatable>You don't have the permissions to view this
|
<a>
|
||||||
permissions</Translatable><br/>({failedPermission})</a>
|
<Translatable>You don't have the permissions to view this permissions</Translatable><br/>
|
||||||
|
({failedPermission})
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const RefreshButton = (props: { events: Registry<PermissionEditorEvents> }) => {
|
const RefreshButton = React.memo(() => {
|
||||||
|
const events = useContext(EventContext);
|
||||||
const [unset, setUnset] = useState(true);
|
const [unset, setUnset] = useState(true);
|
||||||
const [nextTime, setNextTime] = useState(0);
|
const [nextTime, setNextTime] = useState(0);
|
||||||
const refButton = useRef<Button>();
|
const refButton = useRef<Button>();
|
||||||
|
|
||||||
props.events.reactUse("action_set_mode", event => setUnset(event.mode !== "normal" && event.mode !== "no-permissions"));
|
events.reactUse("action_set_mode", event => setUnset(event.mode !== "normal" && event.mode !== "no-permissions"));
|
||||||
props.events.reactUse("query_permission_values", () => {
|
events.reactUse("query_permission_values", () => {
|
||||||
setNextTime(Date.now() + 5000);
|
setNextTime(Date.now() + 5000);
|
||||||
refButton.current?.setState({disabled: true});
|
refButton.current?.setState({disabled: true});
|
||||||
});
|
});
|
||||||
|
@ -1331,14 +1278,15 @@ const RefreshButton = (props: { events: Registry<PermissionEditorEvents> }) => {
|
||||||
return <Button
|
return <Button
|
||||||
ref={refButton}
|
ref={refButton}
|
||||||
disabled={unset || Date.now() < nextTime}
|
disabled={unset || Date.now() < nextTime}
|
||||||
onClick={() => props.events.fire("query_permission_values")}
|
onClick={() => events.fire("query_permission_values")}
|
||||||
>
|
>
|
||||||
<IconRenderer icon={"client-check_update"}/> <Translatable>Update</Translatable>
|
<IconRenderer icon={"client-check_update"}/> <Translatable>Update</Translatable>
|
||||||
</Button>
|
</Button>
|
||||||
};
|
});
|
||||||
|
|
||||||
interface PermissionEditorProperties {
|
interface PermissionEditorProperties {
|
||||||
connection: ConnectionHandler;
|
handlerId: string;
|
||||||
|
serverUniqueId: string;
|
||||||
events: Registry<PermissionEditorEvents>;
|
events: Registry<PermissionEditorEvents>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1346,15 +1294,19 @@ interface PermissionEditorState {
|
||||||
state: "no-permissions" | "unset" | "normal";
|
state: "no-permissions" | "unset" | "normal";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PermissionEditor extends React.Component<PermissionEditorProperties, PermissionEditorState> {
|
export class EditorRenderer extends React.Component<PermissionEditorProperties, PermissionEditorState> {
|
||||||
render() {
|
render() {
|
||||||
return [
|
return (
|
||||||
<MenuBar key={"menu-bar"} events={this.props.events} connection={this.props.connection}/>,
|
<EventContext.Provider value={this.props.events}>
|
||||||
<PermissionTable key={"table"} events={this.props.events}/>,
|
<ServerInfoContext.Provider value={{ serverUniqueId: this.props.serverUniqueId, handlerId: this.props.handlerId }}>
|
||||||
|
<MenuBar key={"menu-bar"} />
|
||||||
|
<PermissionTable key={"table"} />
|
||||||
<div key={"footer"} className={cssStyle.containerFooter}>
|
<div key={"footer"} className={cssStyle.containerFooter}>
|
||||||
<RefreshButton events={this.props.events}/>
|
<RefreshButton />
|
||||||
</div>
|
</div>
|
||||||
];
|
</ServerInfoContext.Provider>
|
||||||
|
</EventContext.Provider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
|
@ -1,16 +1,7 @@
|
||||||
import {spawnReactModal} from "tc-shared/ui/react-elements/modal";
|
import {spawnModal} from "tc-shared/ui/react-elements/modal";
|
||||||
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler";
|
||||||
import * as React from "react";
|
|
||||||
import {useState} from "react";
|
|
||||||
import {ContextDivider} from "tc-shared/ui/react-elements/ContextDivider";
|
|
||||||
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import {
|
import {DefaultTabValues} from "tc-shared/ui/modal/permission/ModalRenderer";
|
||||||
EditorGroupedPermissions,
|
|
||||||
PermissionEditor,
|
|
||||||
PermissionEditorEvents
|
|
||||||
} from "tc-shared/ui/modal/permission/PermissionEditor";
|
|
||||||
import {SideBar} from "tc-shared/ui/modal/permission/TabHandler";
|
|
||||||
import {Group, GroupTarget, GroupType} from "tc-shared/permission/GroupManager";
|
import {Group, GroupTarget, GroupType} from "tc-shared/permission/GroupManager";
|
||||||
import {createErrorModal, createInfoModal} from "tc-shared/ui/elements/Modal";
|
import {createErrorModal, createInfoModal} from "tc-shared/ui/elements/Modal";
|
||||||
import {ClientNameInfo, CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
import {ClientNameInfo, CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
|
@ -31,325 +22,65 @@ import {
|
||||||
import {spawnGroupCreate} from "tc-shared/ui/modal/ModalGroupCreate";
|
import {spawnGroupCreate} from "tc-shared/ui/modal/ModalGroupCreate";
|
||||||
import {spawnModalGroupPermissionCopy} from "tc-shared/ui/modal/ModalGroupPermissionCopy";
|
import {spawnModalGroupPermissionCopy} from "tc-shared/ui/modal/ModalGroupPermissionCopy";
|
||||||
import {ErrorCode} from "tc-shared/connection/ErrorCode";
|
import {ErrorCode} from "tc-shared/connection/ErrorCode";
|
||||||
import {PermissionEditorTab} from "tc-shared/events/GlobalEvents";
|
|
||||||
import {LogCategory, logError, logWarn} from "tc-shared/log";
|
import {LogCategory, logError, logWarn} from "tc-shared/log";
|
||||||
import {useTr} from "tc-shared/ui/react-elements/Helper";
|
|
||||||
import {InternalModal} from "tc-shared/ui/react-elements/modal/Definitions";
|
|
||||||
|
|
||||||
const cssStyle = require("./ModalPermissionEditor.scss");
|
import {
|
||||||
|
GroupProperties,
|
||||||
|
GroupUpdateEntry,
|
||||||
|
PermissionEditorSubject,
|
||||||
|
PermissionEditorTab,
|
||||||
|
PermissionModalEvents
|
||||||
|
} from "tc-shared/ui/modal/permission/ModalDefinitions";
|
||||||
|
import {EditorGroupedPermissions, PermissionEditorEvents} from "tc-shared/ui/modal/permission/EditorDefinitions";
|
||||||
|
|
||||||
export type PermissionEditorSubject =
|
export function spawnPermissionEditorModal(connection: ConnectionHandler, defaultTab: PermissionEditorTab = "groups-server", defaultTabValues?: DefaultTabValues) {
|
||||||
"groups-server"
|
const modalEvents = new Registry<PermissionModalEvents>();
|
||||||
| "groups-channel"
|
const editorEvents = new Registry<PermissionEditorEvents>();
|
||||||
| "channel"
|
|
||||||
| "client"
|
|
||||||
| "client-channel"
|
|
||||||
| "none";
|
|
||||||
export const PermissionTabName: { [T in PermissionEditorTab]: { name: string, useTranslate: () => string, renderTranslate: () => React.ReactNode } } = {
|
|
||||||
"groups-server": {name: "Server Groups", useTranslate: () => useTr("Server Groups"), renderTranslate: () => <Translatable>Server Groups</Translatable>},
|
|
||||||
"groups-channel": {name: "Channel Groups", useTranslate: () => useTr("Channel Groups"), renderTranslate: () => <Translatable>Channel Groups</Translatable>},
|
|
||||||
"channel": {name: "Channel Permissions", useTranslate: () => useTr("Channel Permissions"), renderTranslate: () => <Translatable>Channel Permissions</Translatable>},
|
|
||||||
"client": {name: "Client Permissions", useTranslate: () => useTr("Client Permissions"), renderTranslate: () => <Translatable>Client Permissions</Translatable>},
|
|
||||||
"client-channel": {name: "Client Channel Permissions", useTranslate: () => useTr("Client Channel Permissions"), renderTranslate: () => <Translatable>Client Channel Permissions</Translatable>},
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GroupProperties = {
|
modalEvents.enableDebug("modal-permissions");
|
||||||
id: number,
|
editorEvents.enableDebug("permissions-editor");
|
||||||
type: "query" | "template" | "normal";
|
|
||||||
|
|
||||||
name: string,
|
initializePermissionModalResultHandlers(modalEvents);
|
||||||
iconId: number,
|
initializePermissionModalController(connection, modalEvents);
|
||||||
|
initializePermissionEditor(connection, modalEvents, editorEvents);
|
||||||
|
|
||||||
sortId: number;
|
modalEvents.on("action_activate_tab", event => editorEvents.fire("action_toggle_client_button", { visible: event.tab === "groups-server" }));
|
||||||
saveDB: boolean;
|
editorEvents.on("action_toggle_client_list", event => modalEvents.fire("notify_client_list_toggled", { visible: event.visible }));
|
||||||
|
|
||||||
needed_modify_power: number;
|
modalEvents.on("notify_initial_rendered", () => {
|
||||||
needed_member_add: number;
|
modalEvents.fire_react("action_activate_tab", {
|
||||||
needed_member_remove: number;
|
tab: defaultTab,
|
||||||
};
|
activeChannelId: defaultTabValues?.channelId,
|
||||||
export type GroupUpdateEntry = {
|
activeGroupId: defaultTabValues?.groupId,
|
||||||
property: "name" | "icon" | "sort" | "save";
|
activeClientDatabaseId: defaultTabValues?.clientDatabaseId
|
||||||
value: any
|
});
|
||||||
};
|
modalEvents.fire_react("query_client_permissions");
|
||||||
export type ChannelInfo = {
|
|
||||||
id: number;
|
|
||||||
iconId: number;
|
|
||||||
|
|
||||||
name: string;
|
|
||||||
depth: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PermissionModalEvents {
|
|
||||||
action_activate_tab: {
|
|
||||||
tab: PermissionEditorTab,
|
|
||||||
|
|
||||||
activeGroupId?: number;
|
|
||||||
activeChannelId?: number;
|
|
||||||
activeClientDatabaseId?: number;
|
|
||||||
},
|
|
||||||
|
|
||||||
action_select_group: {
|
|
||||||
target: "server" | "channel",
|
|
||||||
id: number
|
|
||||||
},
|
|
||||||
|
|
||||||
action_select_channel: {
|
|
||||||
target: "channel" | "client-channel";
|
|
||||||
id: number
|
|
||||||
},
|
|
||||||
|
|
||||||
action_select_client: {
|
|
||||||
target: "client" | "client-channel";
|
|
||||||
id: number | string | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
action_set_permission_editor_subject: {
|
|
||||||
mode: PermissionEditorSubject | undefined;
|
|
||||||
|
|
||||||
groupId?: number;
|
|
||||||
channelId?: number;
|
|
||||||
clientDatabaseId?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
action_create_group: { target: "server" | "channel", sourceGroup?: number },
|
|
||||||
|
|
||||||
action_rename_group: { target: "server" | "channel", id: number | "selected", newName: string },
|
|
||||||
action_rename_group_result: {
|
|
||||||
target: "server" | "channel";
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
status: "success" | "error";
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
action_delete_group: { target: "server" | "channel", id: number | "selected", mode: "ask" | "force" },
|
|
||||||
action_delete_group_result: {
|
|
||||||
target: "server" | "channel";
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
status: "success" | "error";
|
|
||||||
error?: string;
|
|
||||||
},
|
|
||||||
|
|
||||||
action_group_copy_permissions: { target: "server" | "channel", sourceGroup: number },
|
|
||||||
|
|
||||||
action_server_group_add_client: {
|
|
||||||
id: number;
|
|
||||||
client: number | string; /* string would be the unique id */
|
|
||||||
},
|
|
||||||
action_server_group_add_client_result: {
|
|
||||||
id: number;
|
|
||||||
client: number | string;
|
|
||||||
|
|
||||||
status: "success" | "error" | "no-permissions";
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
action_server_group_remove_client: {
|
|
||||||
id: number;
|
|
||||||
client: number;
|
|
||||||
},
|
|
||||||
action_server_group_remove_client_result: {
|
|
||||||
id: number;
|
|
||||||
client: number;
|
|
||||||
|
|
||||||
status: "success" | "error" | "no-permissions";
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
query_groups: {
|
|
||||||
target: "server" | "channel",
|
|
||||||
},
|
|
||||||
query_group_clients: {
|
|
||||||
id: number
|
|
||||||
},
|
|
||||||
query_channels: {},
|
|
||||||
query_client_permissions: {},
|
|
||||||
query_client_info: {
|
|
||||||
client: number | string; /* client database id or unique id */
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
notify_channels: {
|
|
||||||
channels: ChannelInfo[]
|
|
||||||
},
|
|
||||||
notify_client_info: {
|
|
||||||
client: number | string;
|
|
||||||
state: "success" | "error" | "no-such-client" | "no-permission";
|
|
||||||
|
|
||||||
error?: string;
|
|
||||||
info?: { name: string, uniqueId: string, databaseId: number },
|
|
||||||
failedPermission?: string;
|
|
||||||
},
|
|
||||||
notify_group_updated: {
|
|
||||||
target: "server" | "channel";
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
properties: GroupUpdateEntry[];
|
|
||||||
},
|
|
||||||
notify_groups_created: {
|
|
||||||
target: "server" | "channel";
|
|
||||||
groups: GroupProperties[]
|
|
||||||
},
|
|
||||||
notify_groups_deleted: {
|
|
||||||
target: "server" | "channel";
|
|
||||||
groups: number[]
|
|
||||||
},
|
|
||||||
notify_group_clients: {
|
|
||||||
id: number,
|
|
||||||
status: "success" | "error" | "no-permissions",
|
|
||||||
error?: string;
|
|
||||||
clients?: {
|
|
||||||
name: string;
|
|
||||||
databaseId: number;
|
|
||||||
uniqueId: string;
|
|
||||||
}[]
|
|
||||||
},
|
|
||||||
notify_groups_reset: {},
|
|
||||||
notify_groups: {
|
|
||||||
target: "server" | "channel",
|
|
||||||
groups: GroupProperties[]
|
|
||||||
},
|
|
||||||
|
|
||||||
notify_client_permissions: {
|
|
||||||
permissionModifyPower: number;
|
|
||||||
|
|
||||||
serverGroupCreate: boolean,
|
|
||||||
channelGroupCreate: boolean,
|
|
||||||
|
|
||||||
serverGroupModifyPower: number,
|
|
||||||
channelGroupModifyPower: number,
|
|
||||||
|
|
||||||
modifyQueryGroups: boolean,
|
|
||||||
modifyTemplateGroups: boolean
|
|
||||||
|
|
||||||
serverGroupMemberAddPower: number,
|
|
||||||
serverGroupMemberRemovePower: number,
|
|
||||||
|
|
||||||
serverGroupPermissionList: boolean,
|
|
||||||
channelGroupPermissionList: boolean,
|
|
||||||
channelPermissionList: boolean,
|
|
||||||
clientPermissionList: boolean,
|
|
||||||
clientChannelPermissionList: boolean
|
|
||||||
},
|
|
||||||
|
|
||||||
notify_client_list_toggled: { visible: boolean },
|
|
||||||
notify_channel_updated: { id: number, property: "name" | "icon", value: any },
|
|
||||||
|
|
||||||
notify_destroy: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ActiveTabInfo = (props: { events: Registry<PermissionModalEvents> }) => {
|
|
||||||
const [activeTab, setActiveTab] = useState<PermissionEditorTab>("groups-server");
|
|
||||||
props.events.reactUse("action_activate_tab", event => setActiveTab(event.tab));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cssStyle.header + " " + cssStyle.activeTabInfo}>
|
|
||||||
<div className={cssStyle.entry}>
|
|
||||||
<a title={PermissionTabName[activeTab].useTranslate()} key={"tab-" + activeTab}>
|
|
||||||
{PermissionTabName[activeTab].renderTranslate()}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TabSelectorEntry = (props: { events: Registry<PermissionModalEvents>, entry: PermissionEditorTab }) => {
|
|
||||||
const [active, setActive] = useState(props.entry === "groups-server");
|
|
||||||
|
|
||||||
props.events.reactUse("action_activate_tab", event => setActive(event.tab === props.entry));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cssStyle.entry + " " + (active ? cssStyle.selected : "")}
|
|
||||||
onClick={() => !active && props.events.fire("action_activate_tab", {tab: props.entry})}>
|
|
||||||
<a title={PermissionTabName[props.entry].useTranslate()}>
|
|
||||||
{PermissionTabName[props.entry].renderTranslate()}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TabSelector = (props: { events: Registry<PermissionModalEvents> }) => {
|
|
||||||
return (
|
|
||||||
<div className={cssStyle.header + " " + cssStyle.tabSelector}>
|
|
||||||
<TabSelectorEntry events={props.events} entry={"groups-server"}/>
|
|
||||||
<TabSelectorEntry events={props.events} entry={"groups-channel"}/>
|
|
||||||
<TabSelectorEntry events={props.events} entry={"channel"}/>
|
|
||||||
<TabSelectorEntry events={props.events} entry={"client"}/>
|
|
||||||
<TabSelectorEntry events={props.events} entry={"client-channel"}/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DefaultTabValues = { groupId?: number, channelId?: number, clientDatabaseId?: number };
|
|
||||||
|
|
||||||
class PermissionEditorModal extends InternalModal {
|
|
||||||
readonly modalEvents = new Registry<PermissionModalEvents>();
|
|
||||||
readonly editorEvents = new Registry<PermissionEditorEvents>();
|
|
||||||
|
|
||||||
readonly connection: ConnectionHandler;
|
|
||||||
|
|
||||||
readonly defaultTab: PermissionEditorTab;
|
|
||||||
readonly defaultTabValues: DefaultTabValues;
|
|
||||||
|
|
||||||
constructor(connection: ConnectionHandler, defaultTab: PermissionEditorTab, defaultTabValues?: DefaultTabValues) {
|
|
||||||
super();
|
|
||||||
this.defaultTab = defaultTab;
|
|
||||||
this.defaultTabValues = defaultTabValues || {};
|
|
||||||
|
|
||||||
this.modalEvents.enableDebug("modal-permissions");
|
|
||||||
this.editorEvents.enableDebug("permissions-editor");
|
|
||||||
|
|
||||||
this.connection = connection;
|
|
||||||
initializePermissionModalResultHandlers(this.modalEvents);
|
|
||||||
initializePermissionModalController(connection, this.modalEvents);
|
|
||||||
initializePermissionEditor(connection, this.modalEvents, this.editorEvents);
|
|
||||||
|
|
||||||
this.modalEvents.on("action_activate_tab", event => this.editorEvents.fire("action_toggle_client_button", {visible: event.tab === "groups-server"}));
|
|
||||||
this.editorEvents.on("action_toggle_client_list", event => this.modalEvents.fire("notify_client_list_toggled", {visible: event.visible}));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onInitialize() {
|
|
||||||
this.modalEvents.fire_later("action_activate_tab", {
|
|
||||||
tab: this.defaultTab,
|
|
||||||
activeChannelId: this.defaultTabValues?.channelId,
|
|
||||||
activeGroupId: this.defaultTabValues?.groupId,
|
|
||||||
activeClientDatabaseId: this.defaultTabValues?.clientDatabaseId
|
|
||||||
});
|
});
|
||||||
this.modalEvents.fire_later("query_client_permissions");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onDestroy() {
|
const modal = spawnModal("modal-permission-edit", [
|
||||||
this.modalEvents.fire("notify_destroy");
|
{
|
||||||
this.modalEvents.destroy();
|
serverUniqueId: connection.getCurrentServerUniqueId(),
|
||||||
}
|
handlerId: connection.handlerId
|
||||||
|
},
|
||||||
|
modalEvents.generateIpcDescription(),
|
||||||
|
editorEvents.generateIpcDescription()
|
||||||
|
], {
|
||||||
|
popoutable: true
|
||||||
|
});
|
||||||
|
|
||||||
renderBody() {
|
modalEvents.on("notify_destroy", connection.events().on("notify_connection_state_changed", event => {
|
||||||
return (
|
if(event.newState !== ConnectionState.CONNECTED) {
|
||||||
<div className={cssStyle.container}>
|
modal.destroy();
|
||||||
<div className={cssStyle.contextContainer + " " + cssStyle.left}>
|
|
||||||
<ActiveTabInfo events={this.modalEvents}/>
|
|
||||||
<SideBar modalEvents={this.modalEvents} editorEvents={this.editorEvents}
|
|
||||||
connection={this.connection}/>
|
|
||||||
</div>
|
|
||||||
<ContextDivider id={"permission-editor"} defaultValue={25} direction={"horizontal"} />
|
|
||||||
<div className={cssStyle.contextContainer + " " + cssStyle.right}>
|
|
||||||
<TabSelector events={this.modalEvents}/>
|
|
||||||
<PermissionEditor events={this.editorEvents} connection={this.connection}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
renderTitle(): React.ReactElement<Translatable> {
|
modal.getEvents().on("destroy", () => {
|
||||||
return <Translatable>Server permissions</Translatable>;
|
modalEvents.fire("notify_destroy");
|
||||||
}
|
modalEvents.destroy();
|
||||||
|
editorEvents.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
}
|
modal.show().then(undefined);
|
||||||
|
|
||||||
export function spawnPermissionEditorModal(connection: ConnectionHandler, defaultTab: PermissionEditorTab = "groups-server", values?: DefaultTabValues) {
|
|
||||||
const modal = spawnReactModal(PermissionEditorModal, connection, defaultTab, values);
|
|
||||||
modal.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializePermissionModalResultHandlers(events: Registry<PermissionModalEvents>) {
|
function initializePermissionModalResultHandlers(events: Registry<PermissionModalEvents>) {
|
|
@ -0,0 +1,196 @@
|
||||||
|
export type PermissionEditorTab = "groups-server" | "groups-channel" | "channel" | "client" | "client-channel";
|
||||||
|
|
||||||
|
export type PermissionEditorSubject =
|
||||||
|
"groups-server"
|
||||||
|
| "groups-channel"
|
||||||
|
| "channel"
|
||||||
|
| "client"
|
||||||
|
| "client-channel"
|
||||||
|
| "none";
|
||||||
|
|
||||||
|
export type GroupProperties = {
|
||||||
|
id: number,
|
||||||
|
type: "query" | "template" | "normal";
|
||||||
|
|
||||||
|
name: string,
|
||||||
|
iconId: number,
|
||||||
|
|
||||||
|
sortId: number;
|
||||||
|
saveDB: boolean;
|
||||||
|
|
||||||
|
needed_modify_power: number;
|
||||||
|
needed_member_add: number;
|
||||||
|
needed_member_remove: number;
|
||||||
|
};
|
||||||
|
export type GroupUpdateEntry = {
|
||||||
|
property: "name" | "icon" | "sort" | "save";
|
||||||
|
value: any
|
||||||
|
};
|
||||||
|
export type ChannelInfo = {
|
||||||
|
id: number;
|
||||||
|
iconId: number;
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
depth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PermissionModalEvents {
|
||||||
|
action_activate_tab: {
|
||||||
|
tab: PermissionEditorTab,
|
||||||
|
|
||||||
|
activeGroupId?: number;
|
||||||
|
activeChannelId?: number;
|
||||||
|
activeClientDatabaseId?: number;
|
||||||
|
},
|
||||||
|
|
||||||
|
action_select_group: {
|
||||||
|
target: "server" | "channel",
|
||||||
|
id: number
|
||||||
|
},
|
||||||
|
|
||||||
|
action_select_channel: {
|
||||||
|
target: "channel" | "client-channel";
|
||||||
|
id: number
|
||||||
|
},
|
||||||
|
|
||||||
|
action_select_client: {
|
||||||
|
target: "client" | "client-channel";
|
||||||
|
id: number | string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
action_set_permission_editor_subject: {
|
||||||
|
mode: PermissionEditorSubject | undefined;
|
||||||
|
|
||||||
|
groupId?: number;
|
||||||
|
channelId?: number;
|
||||||
|
clientDatabaseId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
action_create_group: { target: "server" | "channel", sourceGroup?: number },
|
||||||
|
|
||||||
|
action_rename_group: { target: "server" | "channel", id: number | "selected", newName: string },
|
||||||
|
action_rename_group_result: {
|
||||||
|
target: "server" | "channel";
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
status: "success" | "error";
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
action_delete_group: { target: "server" | "channel", id: number | "selected", mode: "ask" | "force" },
|
||||||
|
action_delete_group_result: {
|
||||||
|
target: "server" | "channel";
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
status: "success" | "error";
|
||||||
|
error?: string;
|
||||||
|
},
|
||||||
|
|
||||||
|
action_group_copy_permissions: { target: "server" | "channel", sourceGroup: number },
|
||||||
|
|
||||||
|
action_server_group_add_client: {
|
||||||
|
id: number;
|
||||||
|
client: number | string; /* string would be the unique id */
|
||||||
|
},
|
||||||
|
action_server_group_add_client_result: {
|
||||||
|
id: number;
|
||||||
|
client: number | string;
|
||||||
|
|
||||||
|
status: "success" | "error" | "no-permissions";
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
action_server_group_remove_client: {
|
||||||
|
id: number;
|
||||||
|
client: number;
|
||||||
|
},
|
||||||
|
action_server_group_remove_client_result: {
|
||||||
|
id: number;
|
||||||
|
client: number;
|
||||||
|
|
||||||
|
status: "success" | "error" | "no-permissions";
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
query_groups: {
|
||||||
|
target: "server" | "channel",
|
||||||
|
},
|
||||||
|
query_group_clients: {
|
||||||
|
id: number
|
||||||
|
},
|
||||||
|
query_channels: {},
|
||||||
|
query_client_permissions: {},
|
||||||
|
query_client_info: {
|
||||||
|
client: number | string; /* client database id or unique id */
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
notify_channels: {
|
||||||
|
channels: ChannelInfo[]
|
||||||
|
},
|
||||||
|
notify_client_info: {
|
||||||
|
client: number | string;
|
||||||
|
state: "success" | "error" | "no-such-client" | "no-permission";
|
||||||
|
|
||||||
|
error?: string;
|
||||||
|
info?: { name: string, uniqueId: string, databaseId: number },
|
||||||
|
failedPermission?: string;
|
||||||
|
},
|
||||||
|
notify_group_updated: {
|
||||||
|
target: "server" | "channel";
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
properties: GroupUpdateEntry[];
|
||||||
|
},
|
||||||
|
notify_groups_created: {
|
||||||
|
target: "server" | "channel";
|
||||||
|
groups: GroupProperties[]
|
||||||
|
},
|
||||||
|
notify_groups_deleted: {
|
||||||
|
target: "server" | "channel";
|
||||||
|
groups: number[]
|
||||||
|
},
|
||||||
|
notify_group_clients: {
|
||||||
|
id: number,
|
||||||
|
status: "success" | "error" | "no-permissions",
|
||||||
|
error?: string;
|
||||||
|
clients?: {
|
||||||
|
name: string;
|
||||||
|
databaseId: number;
|
||||||
|
uniqueId: string;
|
||||||
|
}[]
|
||||||
|
},
|
||||||
|
notify_groups_reset: {},
|
||||||
|
notify_groups: {
|
||||||
|
target: "server" | "channel",
|
||||||
|
groups: GroupProperties[]
|
||||||
|
},
|
||||||
|
|
||||||
|
notify_client_permissions: {
|
||||||
|
permissionModifyPower: number;
|
||||||
|
|
||||||
|
serverGroupCreate: boolean,
|
||||||
|
channelGroupCreate: boolean,
|
||||||
|
|
||||||
|
serverGroupModifyPower: number,
|
||||||
|
channelGroupModifyPower: number,
|
||||||
|
|
||||||
|
modifyQueryGroups: boolean,
|
||||||
|
modifyTemplateGroups: boolean
|
||||||
|
|
||||||
|
serverGroupMemberAddPower: number,
|
||||||
|
serverGroupMemberRemovePower: number,
|
||||||
|
|
||||||
|
serverGroupPermissionList: boolean,
|
||||||
|
channelGroupPermissionList: boolean,
|
||||||
|
channelPermissionList: boolean,
|
||||||
|
clientPermissionList: boolean,
|
||||||
|
clientChannelPermissionList: boolean
|
||||||
|
},
|
||||||
|
|
||||||
|
notify_client_list_toggled: { visible: boolean },
|
||||||
|
notify_channel_updated: { id: number, property: "name" | "icon", value: any },
|
||||||
|
|
||||||
|
notify_initial_rendered: {},
|
||||||
|
notify_destroy: {}
|
||||||
|
}
|
|
@ -1,194 +0,0 @@
|
||||||
@import "../../../../css/static/mixin";
|
|
||||||
@import "../../../../css/static/properties";
|
|
||||||
|
|
||||||
html:root {
|
|
||||||
--modal-permissions-header-text: #e1e1e1;
|
|
||||||
--modal-permissions-header-background: #19191b;
|
|
||||||
--modal-permissions-header-hover: #4e4e4e;
|
|
||||||
--modal-permissions-header-selected: #0073d4;
|
|
||||||
|
|
||||||
--modal-permission-right: #303036;
|
|
||||||
--modal-permission-left: #222226;
|
|
||||||
|
|
||||||
--modal-permissions-entry-hover: #28282c;
|
|
||||||
--modal-permissions-entry-selected: #111111;
|
|
||||||
--modal-permissions-current-group: #101012;
|
|
||||||
|
|
||||||
--modal-permissions-buttons-background: #0f0f0f;
|
|
||||||
--modal-permissions-buttons-hover: #262626;
|
|
||||||
--modal-permissions-buttons-disabled: #1b1b1b;
|
|
||||||
|
|
||||||
--modal-permissions-seperator: #1e1e1e; /* the seperator for the "enter a unique id" and "client info" part */
|
|
||||||
--modal-permissions-container-seperator: #222224; /* the seperator between left and right */
|
|
||||||
|
|
||||||
--modal-permissions-icon-select: #121213;
|
|
||||||
--modal-permissions-icon-select-border: #0d0d0d;
|
|
||||||
--modal-permissions-icon-select-hover: #17171a;
|
|
||||||
--modal-permissions-icon-select-hover-border: #333333;
|
|
||||||
|
|
||||||
--modal-permission-no-permnissions: #18171c;
|
|
||||||
--modal-permissions-table-border: #1e2025;
|
|
||||||
|
|
||||||
--modal-permissions-table-header: #303036;
|
|
||||||
--modal-permissions-table-row-odd: #303036;
|
|
||||||
--modal-permissions-table-row-even: #25252a;
|
|
||||||
--modal-permissions-table-row-hover: #343a47;
|
|
||||||
|
|
||||||
--modal-permissions-table-header-text: #e1e1e1;
|
|
||||||
--modal-permissions-table-row-text: #535455;
|
|
||||||
--modal-permissions-table-entry-active-text: #e1e1e1;
|
|
||||||
--modal-permissions-table-entry-group-text: #e1e1e1;
|
|
||||||
|
|
||||||
--modal-permissions-table-input: #e1e1e1;
|
|
||||||
--modal-permissions-table-input-focus: #3f7dbf;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
@include user-select(none);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: stretch;
|
|
||||||
|
|
||||||
width: 1000em;
|
|
||||||
min-width: 20em;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
min-height: 20em;
|
|
||||||
|
|
||||||
flex-shrink: 1;
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
.contextContainer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: stretch;
|
|
||||||
|
|
||||||
&.left {
|
|
||||||
min-width: 10em;
|
|
||||||
min-height: 10em;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: var(--modal-permission-left);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.right {
|
|
||||||
min-width: 30em;
|
|
||||||
background-color: var(--modal-permission-right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
|
|
||||||
height: 4em;
|
|
||||||
background-color: var(--modal-permissions-header-background);
|
|
||||||
color: var(--modal-permissions-header-text);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: stretch;
|
|
||||||
|
|
||||||
.entry {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
padding-left: .5em;
|
|
||||||
padding-right: .5em;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.tabSelector {
|
|
||||||
min-width: 8em;
|
|
||||||
|
|
||||||
.entry {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
padding-bottom: 2px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border: none;
|
|
||||||
border-bottom: 2px solid var(--modal-permissions-header-hover);
|
|
||||||
|
|
||||||
padding-bottom: 0;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
|
|
||||||
margin-right: -10em;
|
|
||||||
margin-left: -10em;
|
|
||||||
margin-bottom: -.2em;
|
|
||||||
bottom: 0;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
width: calc(100% + 20em);
|
|
||||||
|
|
||||||
box-shadow: inset 0px -1.2em 3em -20px var(--modal-permissions-header-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
border: none;
|
|
||||||
border-bottom: 2px solid var(--modal-permissions-header-selected);
|
|
||||||
|
|
||||||
padding-bottom: 0;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
|
|
||||||
margin-right: -10em;
|
|
||||||
margin-left: -10em;
|
|
||||||
margin-bottom: -.2em;
|
|
||||||
bottom: 0;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
width: calc(100% + 20em);
|
|
||||||
|
|
||||||
box-shadow: inset 0px -1.2em 3em -20px var(--modal-permissions-header-selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.activeTabInfo {
|
|
||||||
min-width: 6em;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
.entry {
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
a {
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> * {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,199 @@
|
||||||
@import "../../../../css/static/mixin";
|
@import "../../../../css/static/mixin";
|
||||||
@import "../../../../css/static/properties";
|
@import "../../../../css/static/properties";
|
||||||
|
|
||||||
|
html:root {
|
||||||
|
--modal-permissions-header-text: #e1e1e1;
|
||||||
|
--modal-permissions-header-background: #19191b;
|
||||||
|
--modal-permissions-header-hover: #4e4e4e;
|
||||||
|
--modal-permissions-header-selected: #0073d4;
|
||||||
|
|
||||||
|
--modal-permission-right: #303036;
|
||||||
|
--modal-permission-left: #222226;
|
||||||
|
|
||||||
|
--modal-permissions-entry-hover: #28282c;
|
||||||
|
--modal-permissions-entry-selected: #111111;
|
||||||
|
--modal-permissions-current-group: #101012;
|
||||||
|
|
||||||
|
--modal-permissions-buttons-background: #0f0f0f;
|
||||||
|
--modal-permissions-buttons-hover: #262626;
|
||||||
|
--modal-permissions-buttons-disabled: #1b1b1b;
|
||||||
|
|
||||||
|
--modal-permissions-seperator: #1e1e1e; /* the seperator for the "enter a unique id" and "client info" part */
|
||||||
|
--modal-permissions-container-seperator: #222224; /* the seperator between left and right */
|
||||||
|
|
||||||
|
--modal-permissions-icon-select: #121213;
|
||||||
|
--modal-permissions-icon-select-border: #0d0d0d;
|
||||||
|
--modal-permissions-icon-select-hover: #17171a;
|
||||||
|
--modal-permissions-icon-select-hover-border: #333333;
|
||||||
|
|
||||||
|
--modal-permission-no-permnissions: #18171c;
|
||||||
|
--modal-permissions-table-border: #1e2025;
|
||||||
|
|
||||||
|
--modal-permissions-table-header: #303036;
|
||||||
|
--modal-permissions-table-row-odd: #303036;
|
||||||
|
--modal-permissions-table-row-even: #25252a;
|
||||||
|
--modal-permissions-table-row-hover: #343a47;
|
||||||
|
|
||||||
|
--modal-permissions-table-header-text: #e1e1e1;
|
||||||
|
--modal-permissions-table-row-text: #535455;
|
||||||
|
--modal-permissions-table-entry-active-text: #e1e1e1;
|
||||||
|
--modal-permissions-table-entry-group-text: #e1e1e1;
|
||||||
|
|
||||||
|
--modal-permissions-table-input: #e1e1e1;
|
||||||
|
--modal-permissions-table-input-focus: #3f7dbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@include user-select(none);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
width: 1000em;
|
||||||
|
min-width: 20em;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
min-height: 20em;
|
||||||
|
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.contextContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
min-width: 10em;
|
||||||
|
min-height: 10em;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--modal-permission-left);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
min-width: 30em;
|
||||||
|
background-color: var(--modal-permission-right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
|
||||||
|
height: 4em;
|
||||||
|
background-color: var(--modal-permissions-header-background);
|
||||||
|
color: var(--modal-permissions-header-text);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
padding-left: .5em;
|
||||||
|
padding-right: .5em;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tabSelector {
|
||||||
|
min-width: 8em;
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid var(--modal-permissions-header-hover);
|
||||||
|
|
||||||
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
|
||||||
|
margin-right: -10em;
|
||||||
|
margin-left: -10em;
|
||||||
|
margin-bottom: -.2em;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: calc(100% + 20em);
|
||||||
|
|
||||||
|
box-shadow: inset 0px -1.2em 3em -20px var(--modal-permissions-header-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid var(--modal-permissions-header-selected);
|
||||||
|
|
||||||
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
|
||||||
|
margin-right: -10em;
|
||||||
|
margin-left: -10em;
|
||||||
|
margin-bottom: -.2em;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: calc(100% + 20em);
|
||||||
|
|
||||||
|
box-shadow: inset 0px -1.2em 3em -20px var(--modal-permissions-header-selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.activeTabInfo {
|
||||||
|
min-width: 6em;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
a {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.containerList {
|
.containerList {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {useRef, useState} from "react";
|
import {useContext, useEffect, useRef, useState} from "react";
|
||||||
import {EventHandler, ReactEventHandler, Registry} from "tc-shared/events";
|
import {EventHandler, IpcRegistryDescription, ReactEventHandler, Registry} from "tc-shared/events";
|
||||||
import {ChannelInfo, GroupProperties, PermissionModalEvents} from "tc-shared/ui/modal/permission/ModalPermissionEditor";
|
|
||||||
import {PermissionEditorEvents} from "tc-shared/ui/modal/permission/PermissionEditor";
|
|
||||||
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
|
||||||
import {RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon";
|
import {RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon";
|
||||||
import {createInputModal} from "tc-shared/ui/elements/Modal";
|
import {createInputModal} from "tc-shared/ui/elements/Modal";
|
||||||
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
||||||
|
@ -15,23 +12,26 @@ import {FlatInputField} from "tc-shared/ui/react-elements/InputField";
|
||||||
import {arrayBufferBase64} from "tc-shared/utils/buffers";
|
import {arrayBufferBase64} from "tc-shared/utils/buffers";
|
||||||
import {tra} from "tc-shared/i18n/localize";
|
import {tra} from "tc-shared/i18n/localize";
|
||||||
import {getIconManager} from "tc-shared/file/Icons";
|
import {getIconManager} from "tc-shared/file/Icons";
|
||||||
|
import {PermissionEditorEvents} from "tc-shared/ui/modal/permission/EditorDefinitions";
|
||||||
|
import {
|
||||||
|
ChannelInfo,
|
||||||
|
GroupProperties,
|
||||||
|
PermissionEditorTab,
|
||||||
|
PermissionModalEvents
|
||||||
|
} from "tc-shared/ui/modal/permission/ModalDefinitions";
|
||||||
|
import {useTr} from "tc-shared/ui/react-elements/Helper";
|
||||||
|
import {AbstractModal} from "tc-shared/ui/react-elements/modal/Definitions";
|
||||||
|
import {ContextDivider} from "tc-shared/ui/react-elements/ContextDivider";
|
||||||
|
import {EditorRenderer} from "tc-shared/ui/modal/permission/EditorRenderer";
|
||||||
|
import {spawnContextMenu} from "tc-shared/ui/ContextMenu";
|
||||||
|
import {ClientIcon} from "svg-sprites/client-icons";
|
||||||
|
|
||||||
const cssStyle = require("./TabHandler.scss");
|
const cssStyle = require("./ModalRenderer.scss");
|
||||||
|
|
||||||
export class SideBar extends React.Component<{ connection: ConnectionHandler, modalEvents: Registry<PermissionModalEvents>, editorEvents: Registry<PermissionEditorEvents> }, {}> {
|
export type PermissionEditorServerInfo = { handlerId: string, serverUniqueId: string };
|
||||||
render() {
|
const ModalEventContext = React.createContext<Registry<PermissionModalEvents>>(undefined);
|
||||||
return [
|
const EditorEventContext = React.createContext<Registry<PermissionEditorEvents>>(undefined);
|
||||||
<ServerGroupsSideBar key={"server-groups"} connection={this.props.connection}
|
const ServerInfoContext = React.createContext<PermissionEditorServerInfo>(undefined);
|
||||||
modalEvents={this.props.modalEvents}/>,
|
|
||||||
<ChannelGroupsSideBar key={"channel-groups"} connection={this.props.connection}
|
|
||||||
modalEvents={this.props.modalEvents}/>,
|
|
||||||
<ChannelSideBar key={"channel"} connection={this.props.connection} modalEvents={this.props.modalEvents}/>,
|
|
||||||
<ClientSideBar key={"client"} connection={this.props.connection} modalEvents={this.props.modalEvents}/>,
|
|
||||||
<ClientChannelSideBar key={"client-channel"} connection={this.props.connection}
|
|
||||||
modalEvents={this.props.modalEvents}/>
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const GroupsButton = (props: { image: string, alt: string, onClick?: () => void, disabled: boolean }) => {
|
const GroupsButton = (props: { image: string, alt: string, onClick?: () => void, disabled: boolean }) => {
|
||||||
return (
|
return (
|
||||||
|
@ -43,7 +43,9 @@ const GroupsButton = (props: { image: string, alt: string, onClick?: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const GroupsListEntry = (props: { connection: ConnectionHandler, group: GroupProperties, selected: boolean, callbackSelect: () => void, onContextMenu: (event: React.MouseEvent) => void }) => {
|
const GroupsListEntry = React.memo((props: { group: GroupProperties, selected: boolean, callbackSelect: () => void, onContextMenu: (event: React.MouseEvent) => void }) => {
|
||||||
|
const serverInfo = useContext(ServerInfoContext);
|
||||||
|
|
||||||
let groupTypePrefix = "";
|
let groupTypePrefix = "";
|
||||||
switch (props.group.type) {
|
switch (props.group.type) {
|
||||||
case "query":
|
case "query":
|
||||||
|
@ -57,14 +59,14 @@ const GroupsListEntry = (props: { connection: ConnectionHandler, group: GroupPro
|
||||||
return (
|
return (
|
||||||
<div className={cssStyle.entry + " " + (props.selected ? cssStyle.selected : "")} onClick={props.callbackSelect}
|
<div className={cssStyle.entry + " " + (props.selected ? cssStyle.selected : "")} onClick={props.callbackSelect}
|
||||||
onContextMenu={props.onContextMenu}>
|
onContextMenu={props.onContextMenu}>
|
||||||
<RemoteIconRenderer icon={getIconManager().resolveIcon(props.group.iconId, props.connection.getCurrentServerUniqueId(), props.connection.handlerId)} />
|
<RemoteIconRenderer icon={getIconManager().resolveIcon(props.group.iconId, serverInfo.serverUniqueId, serverInfo.handlerId)} />
|
||||||
<div className={cssStyle.name}>{groupTypePrefix + props.group.name + " (" + props.group.id + ")"}</div>
|
<div className={cssStyle.name}>{groupTypePrefix + props.group.name + " (" + props.group.id + ")"}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
};
|
});
|
||||||
|
|
||||||
@ReactEventHandler<GroupsList>(e => e.props.events)
|
@ReactEventHandler<GroupsList>(e => e.props.events)
|
||||||
class GroupsList extends React.Component<{ connection: ConnectionHandler, events: Registry<PermissionModalEvents>, target: "server" | "channel" }, {
|
class GroupsList extends React.PureComponent<{ events: Registry<PermissionModalEvents>, target: "server" | "channel" }, {
|
||||||
selectedGroupId: number,
|
selectedGroupId: number,
|
||||||
showQueryGroups: boolean,
|
showQueryGroups: boolean,
|
||||||
showTemplateGroups: boolean,
|
showTemplateGroups: boolean,
|
||||||
|
@ -118,8 +120,8 @@ class GroupsList extends React.Component<{ connection: ConnectionHandler, events
|
||||||
});
|
});
|
||||||
}}>
|
}}>
|
||||||
<div className={cssStyle.entries}>
|
<div className={cssStyle.entries}>
|
||||||
{this.visibleGroups.map(e => <GroupsListEntry key={"group-" + e.id}
|
{this.visibleGroups.map(e => (
|
||||||
connection={this.props.connection}
|
<GroupsListEntry key={"group-" + e.id}
|
||||||
group={e}
|
group={e}
|
||||||
selected={e.id === this.state.selectedGroupId}
|
selected={e.id === this.state.selectedGroupId}
|
||||||
callbackSelect={() => this.props.events.fire("action_select_group", {
|
callbackSelect={() => this.props.events.fire("action_select_group", {
|
||||||
|
@ -132,39 +134,45 @@ class GroupsList extends React.Component<{ connection: ConnectionHandler, events
|
||||||
target: this.props.target,
|
target: this.props.target,
|
||||||
id: e.id
|
id: e.id
|
||||||
});
|
});
|
||||||
spawn_context_menu(event.pageX, event.pageY, {
|
|
||||||
name: tr("Rename group"),
|
spawnContextMenu({ pageX: event.pageX, pageY: event.pageY }, [
|
||||||
type: MenuEntryType.ENTRY,
|
{
|
||||||
callback: () => this.onGroupRename(),
|
type: "normal",
|
||||||
icon_class: "client-change_nickname",
|
label: tr("Rename group"),
|
||||||
invalidPermission: this.state.disableGroupRename
|
click: () => this.onGroupRename(),
|
||||||
|
icon: ClientIcon.ChangeNickname,
|
||||||
|
enabled: !this.state.disableGroupRename
|
||||||
}, {
|
}, {
|
||||||
name: tr("Copy permissions"),
|
type: "normal",
|
||||||
type: MenuEntryType.ENTRY,
|
label: tr("Copy permissions"),
|
||||||
icon_class: "client-copy",
|
icon: ClientIcon.Copy,
|
||||||
callback: () => this.props.events.fire("action_group_copy_permissions", {
|
click: () => this.props.events.fire("action_group_copy_permissions", {
|
||||||
target: this.props.target,
|
target: this.props.target,
|
||||||
sourceGroup: e.id
|
sourceGroup: e.id
|
||||||
}),
|
}),
|
||||||
invalidPermission: this.state.disablePermissionCopy
|
enabled: !this.state.disablePermissionCopy
|
||||||
}, {
|
}, {
|
||||||
name: tr("Delete group"),
|
type: "normal",
|
||||||
type: MenuEntryType.ENTRY,
|
label: tr("Delete group"),
|
||||||
icon_class: "client-delete",
|
click: () => this.onGroupDelete(),
|
||||||
callback: () => this.onGroupDelete(),
|
icon: ClientIcon.Delete,
|
||||||
invalidPermission: this.state.disableDelete
|
enabled: !this.state.disableDelete
|
||||||
}, contextmenu.Entry.HR(), {
|
}, {
|
||||||
name: tr("Add group"),
|
type: "separator"
|
||||||
icon_class: "client-add",
|
}, {
|
||||||
type: MenuEntryType.ENTRY,
|
type: "normal",
|
||||||
callback: () => this.props.events.fire("action_create_group", {
|
label: tr("Add group"),
|
||||||
|
click: () => this.props.events.fire("action_create_group", {
|
||||||
target: this.props.target,
|
target: this.props.target,
|
||||||
sourceGroup: e.id
|
sourceGroup: e.id
|
||||||
}),
|
}),
|
||||||
invalidPermission: this.state.disableGroupAdd
|
icon: ClientIcon.Add,
|
||||||
});
|
enabled: !this.state.disableGroupAdd
|
||||||
|
}
|
||||||
|
]);
|
||||||
}}
|
}}
|
||||||
/>)}
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
<div key={"buttons"} className={cssStyle.buttons}>
|
<div key={"buttons"} className={cssStyle.buttons}>
|
||||||
|
@ -426,7 +434,7 @@ class GroupsList extends React.Component<{ connection: ConnectionHandler, events
|
||||||
|
|
||||||
|
|
||||||
@ReactEventHandler<GroupsList>(e => e.props.events)
|
@ReactEventHandler<GroupsList>(e => e.props.events)
|
||||||
class ServerClientList extends React.Component<{ connection: ConnectionHandler, events: Registry<PermissionModalEvents> }, {
|
class ServerClientList extends React.Component<{ events: Registry<PermissionModalEvents> }, {
|
||||||
selectedGroupId: number,
|
selectedGroupId: number,
|
||||||
selectedClientId: number,
|
selectedClientId: number,
|
||||||
|
|
||||||
|
@ -475,16 +483,18 @@ class ServerClientList extends React.Component<{ connection: ConnectionHandler,
|
||||||
return [
|
return [
|
||||||
<div key={"list"} className={cssStyle.list + " " + cssStyle.containerList}
|
<div key={"list"} className={cssStyle.list + " " + cssStyle.containerList}
|
||||||
onContextMenu={e => this.onListContextMenu(e)}>
|
onContextMenu={e => this.onListContextMenu(e)}>
|
||||||
{selectedGroup ?
|
{selectedGroup ? (
|
||||||
|
<ServerInfoContext.Consumer>
|
||||||
|
{serverInfo => (
|
||||||
<div key={"selected-group"} className={cssStyle.entry + " " + cssStyle.selectedGroup}>
|
<div key={"selected-group"} className={cssStyle.entry + " " + cssStyle.selectedGroup}>
|
||||||
<div className={cssStyle.icon}>
|
<div className={cssStyle.icon}>
|
||||||
<RemoteIconRenderer icon={getIconManager().resolveIcon(selectedGroup.iconId, this.props.connection.getCurrentServerUniqueId(), this.props.connection.handlerId)} />
|
<RemoteIconRenderer icon={getIconManager().resolveIcon(selectedGroup.iconId, serverInfo.serverUniqueId, serverInfo.handlerId)} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className={cssStyle.name}>{groupTypePrefix + selectedGroup.name + " (" + selectedGroup.id + ")"}</div>
|
||||||
className={cssStyle.name}>{groupTypePrefix + selectedGroup.name + " (" + selectedGroup.id + ")"}</div>
|
|
||||||
</div>
|
</div>
|
||||||
: undefined
|
)}
|
||||||
}
|
</ServerInfoContext.Consumer>
|
||||||
|
) : undefined}
|
||||||
<div className={cssStyle.entries}>
|
<div className={cssStyle.entries}>
|
||||||
{this.clients.map(client => <div
|
{this.clients.map(client => <div
|
||||||
key={"client-" + client.databaseId}
|
key={"client-" + client.databaseId}
|
||||||
|
@ -781,41 +791,43 @@ class ServerClientList extends React.Component<{ connection: ConnectionHandler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ServerGroupsSideBar = (props: { connection: ConnectionHandler, modalEvents: Registry<PermissionModalEvents> }) => {
|
const ServerGroupsSideBar = React.memo(() => {
|
||||||
|
const events = useContext(ModalEventContext);
|
||||||
const [active, setActive] = useState(true);
|
const [active, setActive] = useState(true);
|
||||||
const [clientList, setClientList] = useState(false);
|
const [clientList, setClientList] = useState(false);
|
||||||
|
|
||||||
props.modalEvents.reactUse("action_activate_tab", event => setActive(event.tab === "groups-server"));
|
events.reactUse("action_activate_tab", event => setActive(event.tab === "groups-server"));
|
||||||
props.modalEvents.reactUse("notify_client_list_toggled", event => setClientList(event.visible));
|
events.reactUse("notify_client_list_toggled", event => setClientList(event.visible));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cssStyle.sideContainer + " " + cssStyle.containerServerGroups + " " + (active ? "" : cssStyle.hidden)}>
|
className={cssStyle.sideContainer + " " + cssStyle.containerServerGroups + " " + (active ? "" : cssStyle.hidden)}>
|
||||||
<div className={cssStyle.containerGroupList + " " + (!clientList ? "" : cssStyle.hidden)}>
|
<div className={cssStyle.containerGroupList + " " + (!clientList ? "" : cssStyle.hidden)}>
|
||||||
<GroupsList connection={props.connection} events={props.modalEvents} target={"server"}/>
|
<GroupsList events={events} target={"server"}/>
|
||||||
</div>
|
</div>
|
||||||
<div className={cssStyle.containerClientList + " " + (clientList ? "" : cssStyle.hidden)}>
|
<div className={cssStyle.containerClientList + " " + (clientList ? "" : cssStyle.hidden)}>
|
||||||
<ServerClientList connection={props.connection} events={props.modalEvents}/>
|
<ServerClientList events={events} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const ChannelGroupsSideBar = (props: { connection: ConnectionHandler, modalEvents: Registry<PermissionModalEvents> }) => {
|
const ChannelGroupsSideBar = React.memo(() => {
|
||||||
|
const events = useContext(ModalEventContext);
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
|
|
||||||
props.modalEvents.reactUse("action_activate_tab", event => setActive(event.tab === "groups-channel"));
|
events.reactUse("action_activate_tab", event => setActive(event.tab === "groups-channel"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cssStyle.sideContainer + " " + cssStyle.containerChannelGroups + " " + (active ? "" : cssStyle.hidden)}>
|
className={cssStyle.sideContainer + " " + cssStyle.containerChannelGroups + " " + (active ? "" : cssStyle.hidden)}>
|
||||||
<GroupsList connection={props.connection} events={props.modalEvents} target={"channel"}/>
|
<GroupsList events={events} target={"channel"}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
@ReactEventHandler<ChannelList>(e => e.props.events)
|
@ReactEventHandler<ChannelList>(e => e.props.events)
|
||||||
class ChannelList extends React.Component<{ connection: ConnectionHandler, events: Registry<PermissionModalEvents>, tabTarget: "channel" | "client-channel" }, { selectedChanelId: number }> {
|
class ChannelList extends React.Component<{ serverInfo: PermissionEditorServerInfo, events: Registry<PermissionModalEvents>, tabTarget: "channel" | "client-channel" }, { selectedChanelId: number }> {
|
||||||
private channels: ChannelInfo[] = [];
|
private channels: ChannelInfo[] = [];
|
||||||
private isActiveTab = false;
|
private isActiveTab = false;
|
||||||
|
|
||||||
|
@ -849,7 +861,7 @@ class ChannelList extends React.Component<{ connection: ConnectionHandler, event
|
||||||
id: e.id
|
id: e.id
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<RemoteIconRenderer icon={getIconManager().resolveIcon(e.iconId, this.props.connection.getCurrentServerUniqueId(), this.props.connection.handlerId)} />
|
<RemoteIconRenderer icon={getIconManager().resolveIcon(e.iconId, this.props.serverInfo.serverUniqueId, this.props.serverInfo.handlerId)} />
|
||||||
<a className={cssStyle.name}>{e.name + " (" + e.id + ")"}</a>
|
<a className={cssStyle.name}>{e.name + " (" + e.id + ")"}</a>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -925,20 +937,24 @@ class ChannelList extends React.Component<{ connection: ConnectionHandler, event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChannelSideBar = (props: { connection: ConnectionHandler, modalEvents: Registry<PermissionModalEvents> }) => {
|
const ChannelSideBar = React.memo(() => {
|
||||||
|
const serverInfo = useContext(ServerInfoContext);
|
||||||
|
const events = useContext(ModalEventContext);
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
|
|
||||||
props.modalEvents.reactUse("action_activate_tab", event => setActive(event.tab === "channel"));
|
events.reactUse("action_activate_tab", event => setActive(event.tab === "channel"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cssStyle.sideContainer + " " + cssStyle.containerChannels + " " + (active ? "" : cssStyle.hidden)}>
|
className={cssStyle.sideContainer + " " + cssStyle.containerChannels + " " + (active ? "" : cssStyle.hidden)}>
|
||||||
<ChannelList connection={props.connection} events={props.modalEvents} tabTarget={"channel"}/>
|
<ChannelList serverInfo={serverInfo} events={events} tabTarget={"channel"}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
const ClientSelect = React.memo((props: { tabTarget: "client" | "client-channel" }) => {
|
||||||
|
const events = React.useContext(ModalEventContext);
|
||||||
|
|
||||||
const ClientSelect = (props: { events: Registry<PermissionModalEvents>, tabTarget: "client" | "client-channel" }) => {
|
|
||||||
const [clientIdentifier, setClientIdentifier] = useState<number | string | undefined>(undefined);
|
const [clientIdentifier, setClientIdentifier] = useState<number | string | undefined>(undefined);
|
||||||
const [clientInfo, setClientInfo] = useState<{ name: string, uniqueId: string, databaseId: number }>(undefined);
|
const [clientInfo, setClientInfo] = useState<{ name: string, uniqueId: string, databaseId: number }>(undefined);
|
||||||
|
|
||||||
|
@ -947,24 +963,24 @@ const ClientSelect = (props: { events: Registry<PermissionModalEvents>, tabTarge
|
||||||
const refUniqueIdentifier = useRef<FlatInputField>();
|
const refUniqueIdentifier = useRef<FlatInputField>();
|
||||||
const refDatabaseId = useRef<FlatInputField>();
|
const refDatabaseId = useRef<FlatInputField>();
|
||||||
|
|
||||||
props.events.reactUse("action_activate_tab", event => {
|
events.reactUse("action_activate_tab", event => {
|
||||||
if (event.tab !== props.tabTarget) {
|
if (event.tab !== props.tabTarget) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof event.activeClientDatabaseId !== "undefined") {
|
if (typeof event.activeClientDatabaseId !== "undefined") {
|
||||||
props.events.fire("action_select_client", {
|
events.fire("action_select_client", {
|
||||||
target: props.tabTarget,
|
target: props.tabTarget,
|
||||||
id: event.activeClientDatabaseId === 0 ? "undefined" : event.activeClientDatabaseId
|
id: event.activeClientDatabaseId === 0 ? "undefined" : event.activeClientDatabaseId
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (clientInfo && clientInfo.databaseId) {
|
if (clientInfo && clientInfo.databaseId) {
|
||||||
props.events.fire("action_set_permission_editor_subject", {
|
events.fire("action_set_permission_editor_subject", {
|
||||||
mode: props.tabTarget,
|
mode: props.tabTarget,
|
||||||
clientDatabaseId: clientInfo.databaseId
|
clientDatabaseId: clientInfo.databaseId
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
props.events.fire("action_set_permission_editor_subject", {mode: props.tabTarget, clientDatabaseId: 0});
|
events.fire("action_set_permission_editor_subject", {mode: props.tabTarget, clientDatabaseId: 0});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -979,16 +995,16 @@ const ClientSelect = (props: { events: Registry<PermissionModalEvents>, tabTarge
|
||||||
refDatabaseId.current?.setState({placeholder: placeholder});
|
refDatabaseId.current?.setState({placeholder: placeholder});
|
||||||
};
|
};
|
||||||
|
|
||||||
props.events.reactUse("query_client_info", event => {
|
events.reactUse("query_client_info", event => {
|
||||||
if (event.client !== clientIdentifier)
|
if (event.client !== clientIdentifier)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
refInput.current?.setState({disabled: true});
|
refInput.current?.setState({disabled: true});
|
||||||
resetInfoFields(tr("loading..."));
|
resetInfoFields(tr("loading..."));
|
||||||
props.events.fire("action_set_permission_editor_subject", {mode: props.tabTarget, clientDatabaseId: 0});
|
events.fire("action_set_permission_editor_subject", {mode: props.tabTarget, clientDatabaseId: 0});
|
||||||
});
|
});
|
||||||
|
|
||||||
props.events.reactUse("notify_client_info", event => {
|
events.reactUse("notify_client_info", event => {
|
||||||
if (event.client !== clientIdentifier)
|
if (event.client !== clientIdentifier)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -999,7 +1015,7 @@ const ClientSelect = (props: { events: Registry<PermissionModalEvents>, tabTarge
|
||||||
refNickname.current?.setValue(event.info.name);
|
refNickname.current?.setValue(event.info.name);
|
||||||
refUniqueIdentifier.current?.setValue(event.info.uniqueId);
|
refUniqueIdentifier.current?.setValue(event.info.uniqueId);
|
||||||
refDatabaseId.current?.setValue(event.info.databaseId + "");
|
refDatabaseId.current?.setValue(event.info.databaseId + "");
|
||||||
props.events.fire("action_set_permission_editor_subject", {
|
events.fire("action_set_permission_editor_subject", {
|
||||||
mode: props.tabTarget,
|
mode: props.tabTarget,
|
||||||
clientDatabaseId: event.info.databaseId
|
clientDatabaseId: event.info.databaseId
|
||||||
});
|
});
|
||||||
|
@ -1022,7 +1038,7 @@ const ClientSelect = (props: { events: Registry<PermissionModalEvents>, tabTarge
|
||||||
refDatabaseId.current?.setState({placeholder: undefined});
|
refDatabaseId.current?.setState({placeholder: undefined});
|
||||||
});
|
});
|
||||||
|
|
||||||
props.events.reactUse("action_select_client", event => {
|
events.reactUse("action_select_client", event => {
|
||||||
if (event.target !== props.tabTarget)
|
if (event.target !== props.tabTarget)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -1030,11 +1046,11 @@ const ClientSelect = (props: { events: Registry<PermissionModalEvents>, tabTarge
|
||||||
refInput.current.setValue(typeof event.id === "undefined" ? "" : event.id.toString());
|
refInput.current.setValue(typeof event.id === "undefined" ? "" : event.id.toString());
|
||||||
if (typeof event.id === "number" || typeof event.id === "string") {
|
if (typeof event.id === "number" || typeof event.id === "string") {
|
||||||
/* first do the state update */
|
/* first do the state update */
|
||||||
props.events.fire_react("query_client_info", {client: event.id});
|
events.fire_react("query_client_info", {client: event.id});
|
||||||
} else {
|
} else {
|
||||||
refInput.current?.setValue(undefined);
|
refInput.current?.setValue(undefined);
|
||||||
resetInfoFields(undefined);
|
resetInfoFields(undefined);
|
||||||
props.events.fire("action_set_permission_editor_subject", {mode: props.tabTarget, clientDatabaseId: 0});
|
events.fire("action_set_permission_editor_subject", {mode: props.tabTarget, clientDatabaseId: 0});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1086,7 +1102,7 @@ const ClientSelect = (props: { events: Registry<PermissionModalEvents>, tabTarge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refInput.current?.setState({isInvalid: false});
|
refInput.current?.setState({isInvalid: false});
|
||||||
props.events.fire("action_select_client", {id: client, target: props.tabTarget});
|
events.fire("action_select_client", {id: client, target: props.tabTarget});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<hr/>
|
<hr/>
|
||||||
|
@ -1097,31 +1113,147 @@ const ClientSelect = (props: { events: Registry<PermissionModalEvents>, tabTarge
|
||||||
disabled={true}/>
|
disabled={true}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const ClientSideBar = (props: { connection: ConnectionHandler, modalEvents: Registry<PermissionModalEvents> }) => {
|
const ClientSideBar = React.memo(() => {
|
||||||
|
const events = useContext(ModalEventContext);
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
|
|
||||||
props.modalEvents.reactUse("action_activate_tab", event => setActive(event.tab === "client"));
|
events.reactUse("action_activate_tab", event => setActive(event.tab === "client"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cssStyle.sideContainer + " " + cssStyle.containerClient + " " + (active ? "" : cssStyle.hidden)}>
|
className={cssStyle.sideContainer + " " + cssStyle.containerClient + " " + (active ? "" : cssStyle.hidden)}>
|
||||||
<ClientSelect events={props.modalEvents} tabTarget={"client"}/>
|
<ClientSelect tabTarget={"client"}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const ClientChannelSideBar = (props: { connection: ConnectionHandler, modalEvents: Registry<PermissionModalEvents> }) => {
|
const ClientChannelSideBar = React.memo(() => {
|
||||||
|
const serverInfo = useContext(ServerInfoContext);
|
||||||
|
const events = useContext(ModalEventContext);
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
|
|
||||||
props.modalEvents.reactUse("action_activate_tab", event => setActive(event.tab === "client-channel"));
|
events.reactUse("action_activate_tab", event => setActive(event.tab === "client-channel"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cssStyle.sideContainer + " " + cssStyle.containerChannelClient + " " + (active ? "" : cssStyle.hidden)}>
|
className={cssStyle.sideContainer + " " + cssStyle.containerChannelClient + " " + (active ? "" : cssStyle.hidden)}>
|
||||||
<ClientSelect events={props.modalEvents} tabTarget={"client-channel"}/>
|
<ClientSelect tabTarget={"client-channel"}/>
|
||||||
<ChannelList connection={props.connection} events={props.modalEvents} tabTarget={"client-channel"}/>
|
<ChannelList serverInfo={serverInfo} events={events} tabTarget={"client-channel"}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PermissionTabName: { [T in PermissionEditorTab]: { name: string, useTranslate: () => string, renderTranslate: () => React.ReactNode } } = {
|
||||||
|
"groups-server": {name: "Server Groups", useTranslate: () => useTr("Server Groups"), renderTranslate: () => <Translatable>Server Groups</Translatable>},
|
||||||
|
"groups-channel": {name: "Channel Groups", useTranslate: () => useTr("Channel Groups"), renderTranslate: () => <Translatable>Channel Groups</Translatable>},
|
||||||
|
"channel": {name: "Channel Permissions", useTranslate: () => useTr("Channel Permissions"), renderTranslate: () => <Translatable>Channel Permissions</Translatable>},
|
||||||
|
"client": {name: "Client Permissions", useTranslate: () => useTr("Client Permissions"), renderTranslate: () => <Translatable>Client Permissions</Translatable>},
|
||||||
|
"client-channel": {name: "Client Channel Permissions", useTranslate: () => useTr("Client Channel Permissions"), renderTranslate: () => <Translatable>Client Channel Permissions</Translatable>},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ActiveTabInfo = React.memo(() => {
|
||||||
|
const events = useContext(ModalEventContext);
|
||||||
|
const [activeTab, setActiveTab] = useState<PermissionEditorTab>("groups-server");
|
||||||
|
events.reactUse("action_activate_tab", event => setActiveTab(event.tab));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cssStyle.header + " " + cssStyle.activeTabInfo}>
|
||||||
|
<div className={cssStyle.entry}>
|
||||||
|
<a title={PermissionTabName[activeTab].useTranslate()} key={"tab-" + activeTab}>
|
||||||
|
{PermissionTabName[activeTab].renderTranslate()}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const TabSelectorEntry = React.memo((props: { entry: PermissionEditorTab }) => {
|
||||||
|
const events = useContext(ModalEventContext);
|
||||||
|
const [active, setActive] = useState(props.entry === "groups-server");
|
||||||
|
|
||||||
|
events.reactUse("action_activate_tab", event => setActive(event.tab === props.entry));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cssStyle.entry + " " + (active ? cssStyle.selected : "")}
|
||||||
|
onClick={() => !active && events.fire("action_activate_tab", {tab: props.entry})}>
|
||||||
|
<a title={PermissionTabName[props.entry].useTranslate()}>
|
||||||
|
{PermissionTabName[props.entry].renderTranslate()}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const TabSelector = React.memo(() => {
|
||||||
|
return (
|
||||||
|
<div className={cssStyle.header + " " + cssStyle.tabSelector}>
|
||||||
|
<TabSelectorEntry entry={"groups-server"}/>
|
||||||
|
<TabSelectorEntry entry={"groups-channel"}/>
|
||||||
|
<TabSelectorEntry entry={"channel"}/>
|
||||||
|
<TabSelectorEntry entry={"client"}/>
|
||||||
|
<TabSelectorEntry entry={"client-channel"}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const InitialRendererTrigger = React.memo(() => {
|
||||||
|
const events = useContext(ModalEventContext);
|
||||||
|
useEffect(() => events.fire("notify_initial_rendered"), []);
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
|
||||||
|
export type DefaultTabValues = { groupId?: number, channelId?: number, clientDatabaseId?: number };
|
||||||
|
export class PermissionEditorModal extends AbstractModal {
|
||||||
|
readonly serverInfo: PermissionEditorServerInfo;
|
||||||
|
readonly modalEvents: Registry<PermissionModalEvents>;
|
||||||
|
readonly editorEvents: Registry<PermissionEditorEvents>;
|
||||||
|
|
||||||
|
constructor(serverInfo: PermissionEditorServerInfo, modalEvents: IpcRegistryDescription<PermissionModalEvents>, editorEvents: IpcRegistryDescription<PermissionEditorEvents>) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.serverInfo = serverInfo;
|
||||||
|
this.modalEvents = Registry.fromIpcDescription(modalEvents);
|
||||||
|
this.editorEvents = Registry.fromIpcDescription(editorEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
this.modalEvents.destroy();
|
||||||
|
this.editorEvents.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBody() {
|
||||||
|
return (
|
||||||
|
<ModalEventContext.Provider value={this.modalEvents}>
|
||||||
|
<EditorEventContext.Provider value={this.editorEvents}>
|
||||||
|
<ServerInfoContext.Provider value={this.serverInfo}>
|
||||||
|
<div className={cssStyle.container}>
|
||||||
|
<div className={cssStyle.contextContainer + " " + cssStyle.left}>
|
||||||
|
<ActiveTabInfo />
|
||||||
|
<ServerGroupsSideBar key={"server-groups"} />
|
||||||
|
<ChannelGroupsSideBar key={"channel-groups"} />
|
||||||
|
<ChannelSideBar key={"channel"} />
|
||||||
|
<ClientSideBar key={"client"} />
|
||||||
|
<ClientChannelSideBar key={"client-channel"} />
|
||||||
|
</div>
|
||||||
|
<ContextDivider id={"permission-editor"} defaultValue={25} direction={"horizontal"} />
|
||||||
|
<div className={cssStyle.contextContainer + " " + cssStyle.right}>
|
||||||
|
<TabSelector />
|
||||||
|
<EditorRenderer events={this.editorEvents} handlerId={this.serverInfo.handlerId} serverUniqueId={this.serverInfo.serverUniqueId} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<InitialRendererTrigger />
|
||||||
|
</ServerInfoContext.Provider>
|
||||||
|
</EditorEventContext.Provider>
|
||||||
|
</ModalEventContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTitle(): React.ReactElement<Translatable> {
|
||||||
|
return <Translatable>Server permission editor</Translatable>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PermissionEditorModal;
|
|
@ -0,0 +1,32 @@
|
||||||
|
.arrow {
|
||||||
|
display: inline-block;
|
||||||
|
border: solid black;
|
||||||
|
//border-width: 0 3px 3px 0;
|
||||||
|
//padding: 3px;
|
||||||
|
//height: 10px;
|
||||||
|
|
||||||
|
border-width: 0 .2em .2em 0;
|
||||||
|
padding: .21em;
|
||||||
|
height: .5em;
|
||||||
|
width: .5em;
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
-webkit-transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
transform: rotate(135deg);
|
||||||
|
-webkit-transform: rotate(135deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.up {
|
||||||
|
transform: rotate(-135deg);
|
||||||
|
-webkit-transform: rotate(-135deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.down {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import {joinClassList} from "tc-shared/ui/react-elements/Helper";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const cssStyle = require("./Arrow.scss");
|
||||||
|
|
||||||
|
export const Arrow = (props: {
|
||||||
|
direction: "up" | "down" | "left" | "right",
|
||||||
|
className?: string,
|
||||||
|
onClick?: () => void
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className={joinClassList(
|
||||||
|
cssStyle.arrow,
|
||||||
|
cssStyle[props.direction],
|
||||||
|
props.className
|
||||||
|
)}
|
||||||
|
onClick={props.onClick}
|
||||||
|
/>
|
||||||
|
)
|
|
@ -16,6 +16,9 @@ import {
|
||||||
ModalClientGroupAssignmentVariables
|
ModalClientGroupAssignmentVariables
|
||||||
} from "tc-shared/ui/modal/group-assignment/Definitions";
|
} from "tc-shared/ui/modal/group-assignment/Definitions";
|
||||||
import {VideoViewerEvents} from "tc-shared/ui/modal/video-viewer/Definitions";
|
import {VideoViewerEvents} from "tc-shared/ui/modal/video-viewer/Definitions";
|
||||||
|
import {PermissionModalEvents} from "tc-shared/ui/modal/permission/ModalDefinitions";
|
||||||
|
import {PermissionEditorEvents} from "tc-shared/ui/modal/permission/EditorDefinitions";
|
||||||
|
import {PermissionEditorServerInfo} from "tc-shared/ui/modal/permission/ModalRenderer";
|
||||||
|
|
||||||
export type ModalType = "error" | "warning" | "info" | "none";
|
export type ModalType = "error" | "warning" | "info" | "none";
|
||||||
export type ModalRenderType = "page" | "dialog";
|
export type ModalRenderType = "page" | "dialog";
|
||||||
|
@ -133,6 +136,7 @@ export abstract class AbstractModal {
|
||||||
color() : "none" | "blue" { return "none"; }
|
color() : "none" | "blue" { return "none"; }
|
||||||
verticalAlignment() : "top" | "center" | "bottom" { return "center"; }
|
verticalAlignment() : "top" | "center" | "bottom" { return "center"; }
|
||||||
|
|
||||||
|
/** @deprecated */
|
||||||
protected onInitialize() {}
|
protected onInitialize() {}
|
||||||
protected onDestroy() {}
|
protected onDestroy() {}
|
||||||
|
|
||||||
|
@ -196,4 +200,9 @@ export interface ModalConstructorArguments {
|
||||||
/* events */ IpcRegistryDescription<ModalClientGroupAssignmentEvents>,
|
/* events */ IpcRegistryDescription<ModalClientGroupAssignmentEvents>,
|
||||||
/* variables */ IpcVariableDescriptor<ModalClientGroupAssignmentVariables>,
|
/* variables */ IpcVariableDescriptor<ModalClientGroupAssignmentVariables>,
|
||||||
],
|
],
|
||||||
|
"modal-permission-edit": [
|
||||||
|
/* serverInfo */ PermissionEditorServerInfo,
|
||||||
|
/* modalEvents */ IpcRegistryDescription<PermissionModalEvents>,
|
||||||
|
/* editorEvents */ IpcRegistryDescription<PermissionEditorEvents>
|
||||||
|
]
|
||||||
}
|
}
|
|
@ -97,4 +97,10 @@ registerModal({
|
||||||
popoutSupported: true
|
popoutSupported: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerModal({
|
||||||
|
modalId: "modal-permission-edit",
|
||||||
|
classLoader: async () => await import("tc-shared/ui/modal/permission/ModalRenderer"),
|
||||||
|
popoutSupported: true
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,9 @@ async function initializeModalRenderer(taskId) {
|
||||||
loader.setCurrentTaskName(taskId, tr("initializing modal class"));
|
loader.setCurrentTaskName(taskId, tr("initializing modal class"));
|
||||||
try {
|
try {
|
||||||
mainModalInstance = constructAbstractModalClass(modalClass, { windowed: true }, result.constructorArguments);
|
mainModalInstance = constructAbstractModalClass(modalClass, { windowed: true }, result.constructorArguments);
|
||||||
|
mainModalInstance["onInitialize"]();
|
||||||
mainModalRenderer.renderModal(mainModalInstance);
|
mainModalRenderer.renderModal(mainModalInstance);
|
||||||
|
mainModalInstance["onOpen"]();
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
loader.critical_error("Failed to invoker modal", "Lookup the console for more detail");
|
loader.critical_error("Failed to invoker modal", "Lookup the console for more detail");
|
||||||
logError(LogCategory.GENERAL,tr("Failed to load modal: %o"), error);
|
logError(LogCategory.GENERAL,tr("Failed to load modal: %o"), error);
|
||||||
|
|
|
@ -55,6 +55,7 @@ export class InternalModalInstance implements ModalInstanceController {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.modalInstance = constructAbstractModalClass(modalClass.default, { windowed: false }, this.constructorArguments);
|
this.modalInstance = constructAbstractModalClass(modalClass.default, { windowed: false }, this.constructorArguments);
|
||||||
|
this.modalInstance["onInitialize"]();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(LogCategory.GENERAL, tr("Failed to create new modal of instance type %s: %o"), this.modalKlass.modalId, error);
|
logError(LogCategory.GENERAL, tr("Failed to create new modal of instance type %s: %o"), this.modalKlass.modalId, error);
|
||||||
throw tr("failed to create new modal instance");
|
throw tr("failed to create new modal instance");
|
||||||
|
@ -153,11 +154,11 @@ export class InternalModalInstance implements ModalInstanceController {
|
||||||
this.events.destroy();
|
this.events.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCloseCallback() {
|
protected getCloseCallback() {
|
||||||
return () => this.events.fire("action_close");
|
return () => this.events.fire("action_close");
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPopoutCallback() {
|
protected getPopoutCallback() {
|
||||||
if(!this.modalKlass.popoutSupported) {
|
if(!this.modalKlass.popoutSupported) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -169,7 +170,7 @@ export class InternalModalInstance implements ModalInstanceController {
|
||||||
return () => this.events.fire("action_popout");
|
return () => this.events.fire("action_popout");
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMinimizeCallback() {
|
protected getMinimizeCallback() {
|
||||||
/* We can't minimize any windows */
|
/* We can't minimize any windows */
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,15 @@ export const ServerTag = React.memo((props: {
|
||||||
serverName: string,
|
serverName: string,
|
||||||
handlerId: string,
|
handlerId: string,
|
||||||
serverUniqueId?: string,
|
serverUniqueId?: string,
|
||||||
className?: string
|
className?: string,
|
||||||
|
|
||||||
|
style?: EntryTagStyle
|
||||||
}) => {
|
}) => {
|
||||||
|
let style = props.style || "normal";
|
||||||
|
if(style === "text-only") {
|
||||||
|
return <React.Fragment key={"text-only"}>{props.serverName}</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cssStyle.tag + (props.className ? ` ${props.className}` : ``)}
|
className={cssStyle.tag + (props.className ? ` ${props.className}` : ``)}
|
||||||
|
|
|
@ -62,22 +62,22 @@ class ManifestGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const module of compilation.chunkGraph.getChunkModules(chunk)) {
|
for(const module of compilation.chunkGraph.getChunkModules(chunk)) {
|
||||||
const moduleId = compilation.chunkGraph.getModuleId(module);
|
const identifier = module.identifier();
|
||||||
if(typeof moduleId === "string" && moduleId.startsWith("svg-sprites/")) {
|
if(typeof identifier === "string" && identifier.startsWith("svg-sprites/")) {
|
||||||
/* custom svg sprite handler */
|
/* custom svg sprite handler */
|
||||||
modules.push({
|
modules.push({
|
||||||
id: module.id,
|
id: module.id,
|
||||||
context: "svg-sprites",
|
context: "svg-sprites",
|
||||||
resource: moduleId.substring("svg-sprites/".length)
|
resource: identifier.substring("svg-sprites/".length)
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!module.type.startsWith("javascript/")) {
|
if(!module.context) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!module.context) {
|
if(!module.type.startsWith("javascript/")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +94,7 @@ class ManifestGenerator {
|
||||||
throw "invalid context/resource relation (" + module.context + " <-> " + path.dirname(module.resource) + ")";
|
throw "invalid context/resource relation (" + module.context + " <-> " + path.dirname(module.resource) + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const moduleId = compilation.chunkGraph.getModuleId(module);
|
||||||
modules.push({
|
modules.push({
|
||||||
id: moduleId,
|
id: moduleId,
|
||||||
context: path.relative(this.options.context, module.context).replace(/\\/g, "/"),
|
context: path.relative(this.options.context, module.context).replace(/\\/g, "/"),
|
||||||
|
|
Loading…
Reference in New Issue