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 {CommandResult} from "../connection/ServerConnectionDeclaration";
|
||||
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 {spawnGlobalSettingsEditor} from "tc-shared/ui/modal/global-settings-editor/Controller";
|
||||
import {spawnModalCssVariableEditor} from "tc-shared/ui/modal/css-editor/Controller";
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {ConnectionHandler} from "../ConnectionHandler";
|
||||
import {Registry} from "../events";
|
||||
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 {
|
||||
/* open a basic window */
|
||||
action_open_window: {
|
||||
|
|
|
@ -20,7 +20,7 @@ import {spawnYesNo} from "../ui/modal/ModalYesNo";
|
|||
import * as hex from "../crypto/hex";
|
||||
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "./ChannelTreeEntry";
|
||||
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 {ClientIcon} from "svg-sprites/client-icons";
|
||||
import {VoiceClient} from "../voice/VoiceClient";
|
||||
|
@ -465,7 +465,10 @@ export class ClientEntry<Events extends ClientEvents = ClientEvents> extends Cha
|
|||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-permission_client",
|
||||
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 {
|
||||
text-align: center;
|
||||
|
||||
:global(.arrow) {
|
||||
.arrow {
|
||||
border-color: var(--text);
|
||||
}
|
||||
}
|
||||
|
@ -271,7 +271,7 @@ html:root {
|
|||
color: var(--modal-permissions-table-entry-group-text) !important;
|
||||
font-weight: bold;
|
||||
|
||||
:global(.arrow) {
|
||||
.arrow {
|
||||
cursor: pointer;
|
||||
border-color: var(--modal-permissions-table-entry-active-text);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
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 {Translatable} from "tc-shared/ui/react-elements/i18n";
|
||||
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 {Button} from "tc-shared/ui/react-elements/Button";
|
||||
import {IconRenderer, RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon";
|
||||
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
|
||||
import {copyToClipboard} from "tc-shared/utils/helpers";
|
||||
import {createInfoModal} from "tc-shared/ui/elements/Modal";
|
||||
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 {
|
||||
groupId: string,
|
||||
groupName: string,
|
||||
permissions: {
|
||||
id: number,
|
||||
name: string;
|
||||
description: string;
|
||||
}[],
|
||||
children: EditorGroupedPermissions[]
|
||||
}
|
||||
const EventContext = React.createContext<Registry<PermissionEditorEvents>>(undefined);
|
||||
const ServerInfoContext = React.createContext<{ handlerId: string, serverUniqueId: string }>(undefined);
|
||||
|
||||
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 [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);
|
||||
if (!iconPermission || !iconPermission.success) return;
|
||||
|
||||
|
@ -137,7 +44,7 @@ const ButtonIconPreview = (props: { events: Registry<PermissionEditorEvents>, co
|
|||
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);
|
||||
if (!iconPermission) return;
|
||||
|
||||
|
@ -145,7 +52,7 @@ const ButtonIconPreview = (props: { events: Registry<PermissionEditorEvents>, co
|
|||
setIconId(iconPermission.newValue);
|
||||
});
|
||||
|
||||
props.events.reactUse("query_permission_values_result", event => {
|
||||
events.reactUse("query_permission_values_result", event => {
|
||||
if (event.status !== "success") {
|
||||
setIconId(0);
|
||||
return;
|
||||
|
@ -166,63 +73,73 @@ const ButtonIconPreview = (props: { events: Registry<PermissionEditorEvents>, co
|
|||
|
||||
let icon;
|
||||
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 (
|
||||
<div className={cssStyle.containerIconSelect}>
|
||||
<div className={cssStyle.preview}
|
||||
onClick={() => props.events.fire("action_open_icon_select", {iconId: iconId})}>
|
||||
onClick={() => events.fire("action_open_icon_select", {iconId: iconId})}>
|
||||
{icon}
|
||||
</div>
|
||||
<div className={cssStyle.containerDropdown}>
|
||||
<div className={cssStyle.button}>
|
||||
<div className="arrow down"/>
|
||||
<Arrow direction={"down"} className={cssStyle.arrow} />
|
||||
</div>
|
||||
<div className={cssStyle.dropdown}>
|
||||
{iconId ? <div className={cssStyle.entry} key={"edit-icon"}
|
||||
onClick={() => props.events.fire("action_open_icon_select", {iconId: iconId})}>
|
||||
{iconId ? (
|
||||
<div className={cssStyle.entry} key={"edit-icon"}
|
||||
onClick={() => events.fire("action_open_icon_select", {iconId: iconId})}>
|
||||
<Translatable>Edit icon</Translatable>
|
||||
</div> : undefined}
|
||||
{iconId ? <div className={cssStyle.entry} key={"remove-icon"}
|
||||
onClick={() => props.events.fire("action_remove_permissions", {
|
||||
</div>
|
||||
) : undefined}
|
||||
{iconId ? (
|
||||
<div className={cssStyle.entry} key={"remove-icon"}
|
||||
onClick={() => events.fire("action_remove_permissions", {
|
||||
permissions: [{
|
||||
name: PermissionType.I_ICON_ID,
|
||||
mode: "value"
|
||||
}]
|
||||
})}>
|
||||
<Translatable>Remove icon</Translatable>
|
||||
</div> : undefined}
|
||||
{!iconId ? <div className={cssStyle.entry} key={"add-icon"}
|
||||
onClick={() => props.events.fire("action_open_icon_select", {iconId: 0})}>
|
||||
</div>
|
||||
) : undefined}
|
||||
{!iconId ? (
|
||||
<div className={cssStyle.entry} key={"add-icon"}
|
||||
onClick={() => events.fire("action_open_icon_select", {iconId: 0})}>
|
||||
<Translatable>Add icon</Translatable>
|
||||
</div> : undefined}
|
||||
</div>
|
||||
) : undefined}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
const ClientListButton = (props: { events: Registry<PermissionEditorEvents> }) => {
|
||||
const ClientListButton = () => {
|
||||
const events = useContext(EventContext);
|
||||
const [visible, setVisible] = useState(true);
|
||||
const [toggled, setToggled] = useState(false);
|
||||
|
||||
props.events.reactUse("action_toggle_client_button", event => setVisible(event.visible));
|
||||
props.events.reactUse("action_toggle_client_list", event => setToggled(event.visible));
|
||||
events.reactUse("action_toggle_client_button", event => setVisible(event.visible));
|
||||
events.reactUse("action_toggle_client_list", event => setToggled(event.visible));
|
||||
|
||||
return <Button
|
||||
key={"button-clients"}
|
||||
className={cssStyle.clients + " " + (visible ? "" : cssStyle.hidden)}
|
||||
color={"green"}
|
||||
onClick={() => props.events.fire("action_toggle_client_list", {visible: !toggled})}>
|
||||
onClick={() => events.fire("action_toggle_client_list", {visible: !toggled})}>
|
||||
{toggled ? <Translatable key={"hide"}>Hide clients in group</Translatable> :
|
||||
<Translatable key={"show"}>Show clients in group</Translatable>}
|
||||
</Button>
|
||||
};
|
||||
|
||||
const MenuBar = (props: { events: Registry<PermissionEditorEvents>, connection: ConnectionHandler }) => {
|
||||
return <div className={cssStyle.containerMenuBar}>
|
||||
<ClientListButton events={props.events}/>
|
||||
const MenuBar = React.memo(() => {
|
||||
const events = useContext(EventContext);
|
||||
|
||||
return (
|
||||
<div className={cssStyle.containerMenuBar}>
|
||||
<ClientListButton />
|
||||
<FlatInputField
|
||||
className={cssStyle.filter}
|
||||
|
||||
|
@ -230,16 +147,20 @@ const MenuBar = (props: { events: Registry<PermissionEditorEvents>, connection:
|
|||
labelType={"floating"}
|
||||
labelClassName={cssStyle.label}
|
||||
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}>
|
||||
<Switch initialState={false} label={<Translatable>Assigned only</Translatable>}
|
||||
onChange={state => props.events.fire("action_set_assigned_only", {value: state})}/>
|
||||
<Switch
|
||||
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>} /> */}
|
||||
</div>
|
||||
<ButtonIconPreview events={props.events} connection={props.connection}/>
|
||||
</div>;
|
||||
};
|
||||
<ButtonIconPreview />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
interface LinkedGroupedPermissions {
|
||||
groupId: string;
|
||||
|
@ -266,8 +187,7 @@ interface LinkedGroupedPermissions {
|
|||
elementVisible: boolean;
|
||||
}
|
||||
|
||||
const PermissionEntryRow = (props: {
|
||||
events: Registry<PermissionEditorEvents>,
|
||||
const PermissionEntryRow = React.memo((props: {
|
||||
groupId: string,
|
||||
permission: string,
|
||||
value: PermissionValue,
|
||||
|
@ -277,6 +197,8 @@ const PermissionEntryRow = (props: {
|
|||
defaultValue: number,
|
||||
description: string
|
||||
}) => {
|
||||
const events = useContext(EventContext);
|
||||
|
||||
const [defaultValue, setDefaultValue] = useState(props.defaultValue);
|
||||
const [value, setValue] = useState<number>(props.value.value);
|
||||
const [forceValueUpdate, setForceValueUpdate] = useState(false);
|
||||
|
@ -306,7 +228,7 @@ const PermissionEntryRow = (props: {
|
|||
if (isBoolPermission) {
|
||||
valueElement = <Switch ref={refValueB} key={"value-b"} initialState={value >= 1} disabled={valueApplying}
|
||||
onChange={flag => {
|
||||
props.events.fire("action_set_permissions", {
|
||||
events.fire("action_set_permissions", {
|
||||
permissions: [{
|
||||
name: props.permission,
|
||||
mode: "value",
|
||||
|
@ -337,7 +259,7 @@ const PermissionEntryRow = (props: {
|
|||
}
|
||||
|
||||
setForceValueUpdate(false);
|
||||
props.events.fire("action_remove_permissions", {
|
||||
events.fire("action_remove_permissions", {
|
||||
permissions: [{
|
||||
name: props.permission,
|
||||
mode: "value"
|
||||
|
@ -352,7 +274,7 @@ const PermissionEntryRow = (props: {
|
|||
}
|
||||
|
||||
setForceValueUpdate(false);
|
||||
props.events.fire("action_set_permissions", {
|
||||
events.fire("action_set_permissions", {
|
||||
permissions: [{
|
||||
name: props.permission,
|
||||
mode: "value",
|
||||
|
@ -367,7 +289,7 @@ const PermissionEntryRow = (props: {
|
|||
}
|
||||
|
||||
skipElement = <Switch key={"skip"} initialState={flagSkip} disabled={valueApplying} onChange={flag => {
|
||||
props.events.fire("action_set_permissions", {
|
||||
events.fire("action_set_permissions", {
|
||||
permissions: [{
|
||||
name: props.permission,
|
||||
mode: "value",
|
||||
|
@ -378,7 +300,7 @@ const PermissionEntryRow = (props: {
|
|||
});
|
||||
}}/>;
|
||||
negateElement = <Switch key={"negate"} initialState={flagNegated} disabled={valueApplying} onChange={flag => {
|
||||
props.events.fire("action_set_permissions", {
|
||||
events.fire("action_set_permissions", {
|
||||
permissions: [{
|
||||
name: props.permission,
|
||||
mode: "value",
|
||||
|
@ -392,12 +314,24 @@ const PermissionEntryRow = (props: {
|
|||
|
||||
if (typeof granted === "number") {
|
||||
if (grantedApplying) {
|
||||
grantedElement =
|
||||
<input key={"grant-applying"} className={cssStyle.applying} type="number" placeholder={tr("applying")}
|
||||
readOnly={true} onChange={() => {
|
||||
}}/>;
|
||||
grantedElement = (
|
||||
<input
|
||||
key={"grant-applying"}
|
||||
className={cssStyle.applying}
|
||||
type="number"
|
||||
placeholder={tr("applying")}
|
||||
readOnly={true}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
grantedElement = <input ref={refGranted} key={"grant"} type="number" defaultValue={granted} onBlur={() => {
|
||||
grantedElement = (
|
||||
<input
|
||||
ref={refGranted}
|
||||
key={"grant"}
|
||||
type="number"
|
||||
defaultValue={granted}
|
||||
onBlur={() => {
|
||||
setGrantedEditing(false);
|
||||
if (!refGranted.current)
|
||||
return;
|
||||
|
@ -408,7 +342,7 @@ const PermissionEntryRow = (props: {
|
|||
return;
|
||||
|
||||
setForceGrantedUpdate(true);
|
||||
props.events.fire("action_remove_permissions", {
|
||||
events.fire("action_remove_permissions", {
|
||||
permissions: [{
|
||||
name: props.permission,
|
||||
mode: "grant"
|
||||
|
@ -423,7 +357,7 @@ const PermissionEntryRow = (props: {
|
|||
}
|
||||
|
||||
setForceGrantedUpdate(true);
|
||||
props.events.fire("action_set_permissions", {
|
||||
events.fire("action_set_permissions", {
|
||||
permissions: [{
|
||||
name: props.permission,
|
||||
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)
|
||||
return;
|
||||
|
||||
|
@ -447,7 +385,7 @@ const PermissionEntryRow = (props: {
|
|||
} else {
|
||||
if (isBoolPermission && typeof value === "undefined") {
|
||||
setValue(event.defaultValue >= 1 ? 1 : 0);
|
||||
props.events.fire("action_set_permissions", {
|
||||
events.fire("action_set_permissions", {
|
||||
permissions: [{
|
||||
name: props.permission,
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
if (!modes) return;
|
||||
|
||||
|
@ -533,7 +471,7 @@ const PermissionEntryRow = (props: {
|
|||
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);
|
||||
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(() => {
|
||||
if (grantedEditing)
|
||||
|
@ -573,7 +511,7 @@ const PermissionEntryRow = (props: {
|
|||
if (e.isDefaultPrevented())
|
||||
return;
|
||||
|
||||
props.events.fire("action_start_permission_edit", {
|
||||
events.fire("action_start_permission_edit", {
|
||||
permission: props.permission,
|
||||
target: "value",
|
||||
defaultValue: defaultValue
|
||||
|
@ -583,12 +521,12 @@ const PermissionEntryRow = (props: {
|
|||
onContextMenu={e => {
|
||||
e.preventDefault();
|
||||
|
||||
let entries: contextmenu.MenuEntry[] = [];
|
||||
let entries: ContextMenuEntry[] = [];
|
||||
if (typeof value === "undefined") {
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Add permission"),
|
||||
callback: () => props.events.fire("action_start_permission_edit", {
|
||||
type: "normal",
|
||||
label: tr("Add permission"),
|
||||
click: () => events.fire("action_start_permission_edit", {
|
||||
permission: props.permission,
|
||||
target: "value",
|
||||
defaultValue: defaultValue
|
||||
|
@ -596,9 +534,9 @@ const PermissionEntryRow = (props: {
|
|||
});
|
||||
} else {
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Remove permission"),
|
||||
callback: () => props.events.fire("action_remove_permissions", {
|
||||
type: "normal",
|
||||
label: tr("Remove permission"),
|
||||
click: () => events.fire("action_remove_permissions", {
|
||||
permissions: [{
|
||||
name: props.permission,
|
||||
mode: "value"
|
||||
|
@ -609,9 +547,9 @@ const PermissionEntryRow = (props: {
|
|||
|
||||
if (typeof granted === "undefined") {
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Add grant permission"),
|
||||
callback: () => props.events.fire("action_start_permission_edit", {
|
||||
type: "normal",
|
||||
label: tr("Add grant permission"),
|
||||
click: () => events.fire("action_start_permission_edit", {
|
||||
permission: props.permission,
|
||||
target: "grant",
|
||||
defaultValue: defaultValue
|
||||
|
@ -619,9 +557,9 @@ const PermissionEntryRow = (props: {
|
|||
});
|
||||
} else {
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Remove grant permission"),
|
||||
callback: () => props.events.fire("action_remove_permissions", {
|
||||
type: "normal",
|
||||
label: tr("Remove grant permission"),
|
||||
click: () => events.fire("action_remove_permissions", {
|
||||
permissions: [{
|
||||
name: props.permission,
|
||||
mode: "grant"
|
||||
|
@ -629,28 +567,27 @@ const PermissionEntryRow = (props: {
|
|||
})
|
||||
});
|
||||
}
|
||||
|
||||
entries.push(contextmenu.Entry.HR());
|
||||
entries.push({ type: "separator" });
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Collapse group"),
|
||||
callback: () => props.events.fire("action_toggle_group", {groupId: props.groupId, collapsed: true})
|
||||
type: "normal",
|
||||
label: tr("Collapse group"),
|
||||
click: () => events.fire("action_toggle_group", {groupId: props.groupId, collapsed: true})
|
||||
});
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Expend all"),
|
||||
callback: () => props.events.fire("action_toggle_group", {groupId: null, collapsed: false})
|
||||
type: "normal",
|
||||
label: tr("Expend all"),
|
||||
click: () => events.fire("action_toggle_group", {groupId: null, collapsed: false})
|
||||
});
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Collapse all"),
|
||||
callback: () => props.events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
||||
type: "normal",
|
||||
label: tr("Collapse all"),
|
||||
click: () => events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
||||
});
|
||||
entries.push(contextmenu.Entry.HR());
|
||||
entries.push({ type: "separator" });
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Show permission description"),
|
||||
callback: () => {
|
||||
type: "normal",
|
||||
label: tr("Show permission description"),
|
||||
click: () => {
|
||||
createInfoModal(
|
||||
tr("Permission description"),
|
||||
tr("Permission description for permission ") + props.permission + ": <br>" + props.description
|
||||
|
@ -658,12 +595,12 @@ const PermissionEntryRow = (props: {
|
|||
}
|
||||
});
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Copy permission name"),
|
||||
callback: () => copyToClipboard(props.permission)
|
||||
type: "normal",
|
||||
label: tr("Copy permission name"),
|
||||
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}>
|
||||
|
@ -673,7 +610,7 @@ const PermissionEntryRow = (props: {
|
|||
<div className={cssStyle.columnSkip}>{skipElement}</div>
|
||||
<div className={cssStyle.columnNegate}>{negateElement}</div>
|
||||
<div className={cssStyle.columnGranted} onDoubleClick={e => {
|
||||
props.events.fire("action_start_permission_edit", {
|
||||
events.fire("action_start_permission_edit", {
|
||||
permission: props.permission,
|
||||
target: "grant",
|
||||
defaultValue: defaultValue
|
||||
|
@ -682,12 +619,13 @@ const PermissionEntryRow = (props: {
|
|||
}}>{grantedElement}</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);
|
||||
|
||||
props.events.reactUse("action_toggle_group", event => {
|
||||
events.reactUse("action_toggle_group", event => {
|
||||
if (event.groupId !== null && event.groupId !== props.group.groupId)
|
||||
return;
|
||||
|
||||
|
@ -699,82 +637,86 @@ const PermissionGroupRow = (props: { events: Registry<PermissionEditorEvents>, g
|
|||
style={{paddingLeft: props.group.depth + "em", top: props.offsetTop}} onContextMenu={e => {
|
||||
e.preventDefault();
|
||||
|
||||
let entries = [];
|
||||
let entries: ContextMenuEntry[] = [];
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Add permissions to this group"),
|
||||
callback: () => props.events.fire("action_add_permission_group", {
|
||||
type: "normal",
|
||||
label: tr("Add permissions to this group"),
|
||||
click: () => events.fire("action_add_permission_group", {
|
||||
groupId: props.group.groupId,
|
||||
mode: "value"
|
||||
})
|
||||
});
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Remove permissions from this group"),
|
||||
callback: () => props.events.fire("action_remove_permission_group", {
|
||||
type: "normal",
|
||||
label: tr("Remove permissions from this group"),
|
||||
click: () => events.fire("action_remove_permission_group", {
|
||||
groupId: props.group.groupId,
|
||||
mode: "value"
|
||||
})
|
||||
});
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Add granted permissions to this group"),
|
||||
callback: () => props.events.fire("action_add_permission_group", {
|
||||
type: "normal",
|
||||
label: tr("Add granted permissions to this group"),
|
||||
click: () => events.fire("action_add_permission_group", {
|
||||
groupId: props.group.groupId,
|
||||
mode: "grant"
|
||||
})
|
||||
});
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Remove granted permissions from this group"),
|
||||
callback: () => props.events.fire("action_remove_permission_group", {
|
||||
type: "normal",
|
||||
label: tr("Remove granted permissions from this group"),
|
||||
click: () => events.fire("action_remove_permission_group", {
|
||||
groupId: props.group.groupId,
|
||||
mode: "grant"
|
||||
})
|
||||
});
|
||||
entries.push(contextmenu.Entry.HR());
|
||||
entries.push({ type: "separator" });
|
||||
if (collapsed) {
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Expend group"),
|
||||
callback: () => props.events.fire("action_toggle_group", {
|
||||
type: "normal",
|
||||
label: tr("Expend group"),
|
||||
click: () => events.fire("action_toggle_group", {
|
||||
groupId: props.group.groupId,
|
||||
collapsed: false
|
||||
})
|
||||
});
|
||||
} else {
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Collapse group"),
|
||||
callback: () => props.events.fire("action_toggle_group", {
|
||||
type: "normal",
|
||||
label: tr("Collapse group"),
|
||||
click: () => events.fire("action_toggle_group", {
|
||||
groupId: props.group.groupId,
|
||||
collapsed: true
|
||||
})
|
||||
});
|
||||
}
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Expend all"),
|
||||
callback: () => props.events.fire("action_toggle_group", {groupId: null, collapsed: false})
|
||||
type: "normal",
|
||||
label: tr("Expend all"),
|
||||
click: () => events.fire("action_toggle_group", {groupId: null, collapsed: false})
|
||||
});
|
||||
entries.push({
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Collapse all"),
|
||||
callback: () => props.events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
||||
type: "normal",
|
||||
label: tr("Collapse all"),
|
||||
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,
|
||||
groupId: props.group.groupId
|
||||
})}
|
||||
>
|
||||
<div className={cssStyle.columnName}>
|
||||
<div className={"arrow " + (collapsed ? "right" : "down")}
|
||||
onClick={() => props.events.fire("action_toggle_group", {
|
||||
<Arrow
|
||||
className={cssStyle.arrow}
|
||||
direction={collapsed ? "right" : "down"}
|
||||
onClick={() => events.fire("action_toggle_group", {
|
||||
collapsed: !collapsed,
|
||||
groupId: props.group.groupId
|
||||
})}/>
|
||||
})}
|
||||
/>
|
||||
<div className={cssStyle.groupName} title={/* @tr-ignore */ tr(props.group.groupName)}>
|
||||
<Translatable trIgnore={true}>{props.group.groupName}</Translatable>
|
||||
</div>
|
||||
|
@ -785,7 +727,7 @@ const PermissionGroupRow = (props: { events: Registry<PermissionEditorEvents>, g
|
|||
<div className={cssStyle.columnGranted}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
type PermissionValue = { value?: number, flagNegate?: boolean, flagSkip?: boolean, granted?: number };
|
||||
|
||||
|
@ -835,18 +777,20 @@ class PermissionList extends React.Component<{ events: Registry<PermissionEditor
|
|||
return;
|
||||
|
||||
e.preventDefault();
|
||||
contextmenu.spawn_context_menu(e.pageX, e.pageY, {
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Expend all"),
|
||||
callback: () => this.props.events.fire("action_toggle_group", {
|
||||
spawnContextMenu({ pageX: e.pageX, pageY: e.pageY }, [
|
||||
{
|
||||
type: "normal",
|
||||
label: tr("Expend all"),
|
||||
click: () => this.props.events.fire("action_toggle_group", {
|
||||
groupId: null,
|
||||
collapsed: false
|
||||
})
|
||||
}, {
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Collapse all"),
|
||||
callback: () => this.props.events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
||||
});
|
||||
type: "normal",
|
||||
label: tr("Collapse all"),
|
||||
click: () => this.props.events.fire("action_toggle_group", {groupId: null, collapsed: true})
|
||||
}
|
||||
]);
|
||||
}}>
|
||||
{elements}
|
||||
<div key={"space"} className={cssStyle.spaceAllocator}
|
||||
|
@ -1225,7 +1169,7 @@ class PermissionList extends React.Component<{ events: Registry<PermissionEditor
|
|||
while (currentGroup) {
|
||||
if (currentGroup.elementVisible) {
|
||||
this.currentListElements.push(<PermissionGroupRow key={"group-" + currentGroup.groupId}
|
||||
events={this.props.events} group={currentGroup}
|
||||
group={currentGroup}
|
||||
isOdd={index % 2 === 1}
|
||||
offsetTop={this.heightPerElement * index}/>);
|
||||
index++;
|
||||
|
@ -1245,7 +1189,6 @@ class PermissionList extends React.Component<{ events: Registry<PermissionEditor
|
|||
|
||||
this.currentListElements.push(<PermissionEntryRow
|
||||
key={"permission-" + e.name + " - " + Math.random()} /* force a update of this */
|
||||
events={this.props.events}
|
||||
permission={e.name}
|
||||
groupId={currentGroup.groupId}
|
||||
isOdd={index % 2 === 1}
|
||||
|
@ -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 [failedPermission, setFailedPermission] = useState(undefined);
|
||||
|
||||
props.events.reactUse("action_set_mode", event => {
|
||||
events.reactUse("action_set_mode", event => {
|
||||
setMode(event.mode);
|
||||
setFailedPermission(event.failedPermission);
|
||||
});
|
||||
|
@ -1295,25 +1239,28 @@ const PermissionTable = (props: { events: Registry<PermissionEditorEvents> }) =>
|
|||
</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.noPermissions + " " + (mode === "no-permissions" ? "" : cssStyle.hidden)}>
|
||||
<a><Translatable>You don't have the permissions to view this
|
||||
permissions</Translatable><br/>({failedPermission})</a>
|
||||
<a>
|
||||
<Translatable>You don't have the permissions to view this permissions</Translatable><br/>
|
||||
({failedPermission})
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
const RefreshButton = (props: { events: Registry<PermissionEditorEvents> }) => {
|
||||
const RefreshButton = React.memo(() => {
|
||||
const events = useContext(EventContext);
|
||||
const [unset, setUnset] = useState(true);
|
||||
const [nextTime, setNextTime] = useState(0);
|
||||
const refButton = useRef<Button>();
|
||||
|
||||
props.events.reactUse("action_set_mode", event => setUnset(event.mode !== "normal" && event.mode !== "no-permissions"));
|
||||
props.events.reactUse("query_permission_values", () => {
|
||||
events.reactUse("action_set_mode", event => setUnset(event.mode !== "normal" && event.mode !== "no-permissions"));
|
||||
events.reactUse("query_permission_values", () => {
|
||||
setNextTime(Date.now() + 5000);
|
||||
refButton.current?.setState({disabled: true});
|
||||
});
|
||||
|
@ -1331,14 +1278,15 @@ const RefreshButton = (props: { events: Registry<PermissionEditorEvents> }) => {
|
|||
return <Button
|
||||
ref={refButton}
|
||||
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>
|
||||
</Button>
|
||||
};
|
||||
});
|
||||
|
||||
interface PermissionEditorProperties {
|
||||
connection: ConnectionHandler;
|
||||
handlerId: string;
|
||||
serverUniqueId: string;
|
||||
events: Registry<PermissionEditorEvents>;
|
||||
}
|
||||
|
||||
|
@ -1346,15 +1294,19 @@ interface PermissionEditorState {
|
|||
state: "no-permissions" | "unset" | "normal";
|
||||
}
|
||||
|
||||
export class PermissionEditor extends React.Component<PermissionEditorProperties, PermissionEditorState> {
|
||||
export class EditorRenderer extends React.Component<PermissionEditorProperties, PermissionEditorState> {
|
||||
render() {
|
||||
return [
|
||||
<MenuBar key={"menu-bar"} events={this.props.events} connection={this.props.connection}/>,
|
||||
<PermissionTable key={"table"} events={this.props.events}/>,
|
||||
return (
|
||||
<EventContext.Provider value={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}>
|
||||
<RefreshButton events={this.props.events}/>
|
||||
<RefreshButton />
|
||||
</div>
|
||||
];
|
||||
</ServerInfoContext.Provider>
|
||||
</EventContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
|
@ -1,16 +1,7 @@
|
|||
import {spawnReactModal} from "tc-shared/ui/react-elements/modal";
|
||||
import {ConnectionHandler} 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 {spawnModal} from "tc-shared/ui/react-elements/modal";
|
||||
import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler";
|
||||
import {Registry} from "tc-shared/events";
|
||||
import {
|
||||
EditorGroupedPermissions,
|
||||
PermissionEditor,
|
||||
PermissionEditorEvents
|
||||
} from "tc-shared/ui/modal/permission/PermissionEditor";
|
||||
import {SideBar} from "tc-shared/ui/modal/permission/TabHandler";
|
||||
import {DefaultTabValues} from "tc-shared/ui/modal/permission/ModalRenderer";
|
||||
import {Group, GroupTarget, GroupType} from "tc-shared/permission/GroupManager";
|
||||
import {createErrorModal, createInfoModal} from "tc-shared/ui/elements/Modal";
|
||||
import {ClientNameInfo, CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||
|
@ -31,325 +22,65 @@ import {
|
|||
import {spawnGroupCreate} from "tc-shared/ui/modal/ModalGroupCreate";
|
||||
import {spawnModalGroupPermissionCopy} from "tc-shared/ui/modal/ModalGroupPermissionCopy";
|
||||
import {ErrorCode} from "tc-shared/connection/ErrorCode";
|
||||
import {PermissionEditorTab} from "tc-shared/events/GlobalEvents";
|
||||
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 =
|
||||
"groups-server"
|
||||
| "groups-channel"
|
||||
| "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 function spawnPermissionEditorModal(connection: ConnectionHandler, defaultTab: PermissionEditorTab = "groups-server", defaultTabValues?: DefaultTabValues) {
|
||||
const modalEvents = new Registry<PermissionModalEvents>();
|
||||
const editorEvents = new Registry<PermissionEditorEvents>();
|
||||
|
||||
export type GroupProperties = {
|
||||
id: number,
|
||||
type: "query" | "template" | "normal";
|
||||
modalEvents.enableDebug("modal-permissions");
|
||||
editorEvents.enableDebug("permissions-editor");
|
||||
|
||||
name: string,
|
||||
iconId: number,
|
||||
initializePermissionModalResultHandlers(modalEvents);
|
||||
initializePermissionModalController(connection, modalEvents);
|
||||
initializePermissionEditor(connection, modalEvents, editorEvents);
|
||||
|
||||
sortId: number;
|
||||
saveDB: boolean;
|
||||
modalEvents.on("action_activate_tab", event => editorEvents.fire("action_toggle_client_button", { visible: event.tab === "groups-server" }));
|
||||
editorEvents.on("action_toggle_client_list", event => modalEvents.fire("notify_client_list_toggled", { visible: event.visible }));
|
||||
|
||||
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_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
|
||||
modalEvents.on("notify_initial_rendered", () => {
|
||||
modalEvents.fire_react("action_activate_tab", {
|
||||
tab: defaultTab,
|
||||
activeChannelId: defaultTabValues?.channelId,
|
||||
activeGroupId: defaultTabValues?.groupId,
|
||||
activeClientDatabaseId: defaultTabValues?.clientDatabaseId
|
||||
});
|
||||
modalEvents.fire_react("query_client_permissions");
|
||||
});
|
||||
this.modalEvents.fire_later("query_client_permissions");
|
||||
}
|
||||
|
||||
protected onDestroy() {
|
||||
this.modalEvents.fire("notify_destroy");
|
||||
this.modalEvents.destroy();
|
||||
}
|
||||
const modal = spawnModal("modal-permission-edit", [
|
||||
{
|
||||
serverUniqueId: connection.getCurrentServerUniqueId(),
|
||||
handlerId: connection.handlerId
|
||||
},
|
||||
modalEvents.generateIpcDescription(),
|
||||
editorEvents.generateIpcDescription()
|
||||
], {
|
||||
popoutable: true
|
||||
});
|
||||
|
||||
renderBody() {
|
||||
return (
|
||||
<div className={cssStyle.container}>
|
||||
<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>
|
||||
);
|
||||
modalEvents.on("notify_destroy", connection.events().on("notify_connection_state_changed", event => {
|
||||
if(event.newState !== ConnectionState.CONNECTED) {
|
||||
modal.destroy();
|
||||
}
|
||||
}));
|
||||
|
||||
renderTitle(): React.ReactElement<Translatable> {
|
||||
return <Translatable>Server permissions</Translatable>;
|
||||
}
|
||||
modal.getEvents().on("destroy", () => {
|
||||
modalEvents.fire("notify_destroy");
|
||||
modalEvents.destroy();
|
||||
editorEvents.destroy();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function spawnPermissionEditorModal(connection: ConnectionHandler, defaultTab: PermissionEditorTab = "groups-server", values?: DefaultTabValues) {
|
||||
const modal = spawnReactModal(PermissionEditorModal, connection, defaultTab, values);
|
||||
modal.show();
|
||||
modal.show().then(undefined);
|
||||
}
|
||||
|
||||
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/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 {
|
||||
color: var(--text);
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
import * as React from "react";
|
||||
import {useRef, useState} from "react";
|
||||
import {EventHandler, 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 {useContext, useEffect, useRef, useState} from "react";
|
||||
import {EventHandler, IpcRegistryDescription, ReactEventHandler, Registry} from "tc-shared/events";
|
||||
import {RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon";
|
||||
import {createInputModal} from "tc-shared/ui/elements/Modal";
|
||||
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 {tra} from "tc-shared/i18n/localize";
|
||||
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> }, {}> {
|
||||
render() {
|
||||
return [
|
||||
<ServerGroupsSideBar key={"server-groups"} connection={this.props.connection}
|
||||
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}/>
|
||||
];
|
||||
}
|
||||
}
|
||||
export type PermissionEditorServerInfo = { handlerId: string, serverUniqueId: string };
|
||||
const ModalEventContext = React.createContext<Registry<PermissionModalEvents>>(undefined);
|
||||
const EditorEventContext = React.createContext<Registry<PermissionEditorEvents>>(undefined);
|
||||
const ServerInfoContext = React.createContext<PermissionEditorServerInfo>(undefined);
|
||||
|
||||
const GroupsButton = (props: { image: string, alt: string, onClick?: () => void, disabled: boolean }) => {
|
||||
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 = "";
|
||||
switch (props.group.type) {
|
||||
case "query":
|
||||
|
@ -57,14 +59,14 @@ const GroupsListEntry = (props: { connection: ConnectionHandler, group: GroupPro
|
|||
return (
|
||||
<div className={cssStyle.entry + " " + (props.selected ? cssStyle.selected : "")} onClick={props.callbackSelect}
|
||||
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>
|
||||
)
|
||||
};
|
||||
});
|
||||
|
||||
@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,
|
||||
showQueryGroups: boolean,
|
||||
showTemplateGroups: boolean,
|
||||
|
@ -118,8 +120,8 @@ class GroupsList extends React.Component<{ connection: ConnectionHandler, events
|
|||
});
|
||||
}}>
|
||||
<div className={cssStyle.entries}>
|
||||
{this.visibleGroups.map(e => <GroupsListEntry key={"group-" + e.id}
|
||||
connection={this.props.connection}
|
||||
{this.visibleGroups.map(e => (
|
||||
<GroupsListEntry key={"group-" + e.id}
|
||||
group={e}
|
||||
selected={e.id === this.state.selectedGroupId}
|
||||
callbackSelect={() => this.props.events.fire("action_select_group", {
|
||||
|
@ -132,39 +134,45 @@ class GroupsList extends React.Component<{ connection: ConnectionHandler, events
|
|||
target: this.props.target,
|
||||
id: e.id
|
||||
});
|
||||
spawn_context_menu(event.pageX, event.pageY, {
|
||||
name: tr("Rename group"),
|
||||
type: MenuEntryType.ENTRY,
|
||||
callback: () => this.onGroupRename(),
|
||||
icon_class: "client-change_nickname",
|
||||
invalidPermission: this.state.disableGroupRename
|
||||
|
||||
spawnContextMenu({ pageX: event.pageX, pageY: event.pageY }, [
|
||||
{
|
||||
type: "normal",
|
||||
label: tr("Rename group"),
|
||||
click: () => this.onGroupRename(),
|
||||
icon: ClientIcon.ChangeNickname,
|
||||
enabled: !this.state.disableGroupRename
|
||||
}, {
|
||||
name: tr("Copy permissions"),
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon_class: "client-copy",
|
||||
callback: () => this.props.events.fire("action_group_copy_permissions", {
|
||||
type: "normal",
|
||||
label: tr("Copy permissions"),
|
||||
icon: ClientIcon.Copy,
|
||||
click: () => this.props.events.fire("action_group_copy_permissions", {
|
||||
target: this.props.target,
|
||||
sourceGroup: e.id
|
||||
}),
|
||||
invalidPermission: this.state.disablePermissionCopy
|
||||
enabled: !this.state.disablePermissionCopy
|
||||
}, {
|
||||
name: tr("Delete group"),
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon_class: "client-delete",
|
||||
callback: () => this.onGroupDelete(),
|
||||
invalidPermission: this.state.disableDelete
|
||||
}, contextmenu.Entry.HR(), {
|
||||
name: tr("Add group"),
|
||||
icon_class: "client-add",
|
||||
type: MenuEntryType.ENTRY,
|
||||
callback: () => this.props.events.fire("action_create_group", {
|
||||
type: "normal",
|
||||
label: tr("Delete group"),
|
||||
click: () => this.onGroupDelete(),
|
||||
icon: ClientIcon.Delete,
|
||||
enabled: !this.state.disableDelete
|
||||
}, {
|
||||
type: "separator"
|
||||
}, {
|
||||
type: "normal",
|
||||
label: tr("Add group"),
|
||||
click: () => this.props.events.fire("action_create_group", {
|
||||
target: this.props.target,
|
||||
sourceGroup: e.id
|
||||
}),
|
||||
invalidPermission: this.state.disableGroupAdd
|
||||
});
|
||||
icon: ClientIcon.Add,
|
||||
enabled: !this.state.disableGroupAdd
|
||||
}
|
||||
]);
|
||||
}}
|
||||
/>)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>,
|
||||
<div key={"buttons"} className={cssStyle.buttons}>
|
||||
|
@ -426,7 +434,7 @@ class GroupsList extends React.Component<{ connection: ConnectionHandler, 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,
|
||||
selectedClientId: number,
|
||||
|
||||
|
@ -475,16 +483,18 @@ class ServerClientList extends React.Component<{ connection: ConnectionHandler,
|
|||
return [
|
||||
<div key={"list"} className={cssStyle.list + " " + cssStyle.containerList}
|
||||
onContextMenu={e => this.onListContextMenu(e)}>
|
||||
{selectedGroup ?
|
||||
{selectedGroup ? (
|
||||
<ServerInfoContext.Consumer>
|
||||
{serverInfo => (
|
||||
<div key={"selected-group"} className={cssStyle.entry + " " + cssStyle.selectedGroup}>
|
||||
<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
|
||||
className={cssStyle.name}>{groupTypePrefix + selectedGroup.name + " (" + selectedGroup.id + ")"}</div>
|
||||
<div className={cssStyle.name}>{groupTypePrefix + selectedGroup.name + " (" + selectedGroup.id + ")"}</div>
|
||||
</div>
|
||||
: undefined
|
||||
}
|
||||
)}
|
||||
</ServerInfoContext.Consumer>
|
||||
) : undefined}
|
||||
<div className={cssStyle.entries}>
|
||||
{this.clients.map(client => <div
|
||||
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 [clientList, setClientList] = useState(false);
|
||||
|
||||
props.modalEvents.reactUse("action_activate_tab", event => setActive(event.tab === "groups-server"));
|
||||
props.modalEvents.reactUse("notify_client_list_toggled", event => setClientList(event.visible));
|
||||
events.reactUse("action_activate_tab", event => setActive(event.tab === "groups-server"));
|
||||
events.reactUse("notify_client_list_toggled", event => setClientList(event.visible));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cssStyle.sideContainer + " " + cssStyle.containerServerGroups + " " + (active ? "" : 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 className={cssStyle.containerClientList + " " + (clientList ? "" : cssStyle.hidden)}>
|
||||
<ServerClientList connection={props.connection} events={props.modalEvents}/>
|
||||
<ServerClientList events={events} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
const ChannelGroupsSideBar = (props: { connection: ConnectionHandler, modalEvents: Registry<PermissionModalEvents> }) => {
|
||||
const ChannelGroupsSideBar = React.memo(() => {
|
||||
const events = useContext(ModalEventContext);
|
||||
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 (
|
||||
<div
|
||||
className={cssStyle.sideContainer + " " + cssStyle.containerChannelGroups + " " + (active ? "" : cssStyle.hidden)}>
|
||||
<GroupsList connection={props.connection} events={props.modalEvents} target={"channel"}/>
|
||||
<GroupsList events={events} target={"channel"}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@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 isActiveTab = false;
|
||||
|
||||
|
@ -849,7 +861,7 @@ class ChannelList extends React.Component<{ connection: ConnectionHandler, event
|
|||
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>
|
||||
</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);
|
||||
|
||||
props.modalEvents.reactUse("action_activate_tab", event => setActive(event.tab === "channel"));
|
||||
events.reactUse("action_activate_tab", event => setActive(event.tab === "channel"));
|
||||
|
||||
return (
|
||||
<div
|
||||
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>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
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 [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 refDatabaseId = useRef<FlatInputField>();
|
||||
|
||||
props.events.reactUse("action_activate_tab", event => {
|
||||
events.reactUse("action_activate_tab", event => {
|
||||
if (event.tab !== props.tabTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof event.activeClientDatabaseId !== "undefined") {
|
||||
props.events.fire("action_select_client", {
|
||||
events.fire("action_select_client", {
|
||||
target: props.tabTarget,
|
||||
id: event.activeClientDatabaseId === 0 ? "undefined" : event.activeClientDatabaseId
|
||||
});
|
||||
} else {
|
||||
if (clientInfo && clientInfo.databaseId) {
|
||||
props.events.fire("action_set_permission_editor_subject", {
|
||||
events.fire("action_set_permission_editor_subject", {
|
||||
mode: props.tabTarget,
|
||||
clientDatabaseId: clientInfo.databaseId
|
||||
});
|
||||
} 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});
|
||||
};
|
||||
|
||||
props.events.reactUse("query_client_info", event => {
|
||||
events.reactUse("query_client_info", event => {
|
||||
if (event.client !== clientIdentifier)
|
||||
return;
|
||||
|
||||
refInput.current?.setState({disabled: true});
|
||||
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)
|
||||
return;
|
||||
|
||||
|
@ -999,7 +1015,7 @@ const ClientSelect = (props: { events: Registry<PermissionModalEvents>, tabTarge
|
|||
refNickname.current?.setValue(event.info.name);
|
||||
refUniqueIdentifier.current?.setValue(event.info.uniqueId);
|
||||
refDatabaseId.current?.setValue(event.info.databaseId + "");
|
||||
props.events.fire("action_set_permission_editor_subject", {
|
||||
events.fire("action_set_permission_editor_subject", {
|
||||
mode: props.tabTarget,
|
||||
clientDatabaseId: event.info.databaseId
|
||||
});
|
||||
|
@ -1022,7 +1038,7 @@ const ClientSelect = (props: { events: Registry<PermissionModalEvents>, tabTarge
|
|||
refDatabaseId.current?.setState({placeholder: undefined});
|
||||
});
|
||||
|
||||
props.events.reactUse("action_select_client", event => {
|
||||
events.reactUse("action_select_client", event => {
|
||||
if (event.target !== props.tabTarget)
|
||||
return;
|
||||
|
||||
|
@ -1030,11 +1046,11 @@ const ClientSelect = (props: { events: Registry<PermissionModalEvents>, tabTarge
|
|||
refInput.current.setValue(typeof event.id === "undefined" ? "" : event.id.toString());
|
||||
if (typeof event.id === "number" || typeof event.id === "string") {
|
||||
/* 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 {
|
||||
refInput.current?.setValue(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});
|
||||
props.events.fire("action_select_client", {id: client, target: props.tabTarget});
|
||||
events.fire("action_select_client", {id: client, target: props.tabTarget});
|
||||
}}
|
||||
/>
|
||||
<hr/>
|
||||
|
@ -1097,31 +1113,147 @@ const ClientSelect = (props: { events: Registry<PermissionModalEvents>, tabTarge
|
|||
disabled={true}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
const ClientSideBar = (props: { connection: ConnectionHandler, modalEvents: Registry<PermissionModalEvents> }) => {
|
||||
const ClientSideBar = React.memo(() => {
|
||||
const events = useContext(ModalEventContext);
|
||||
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 (
|
||||
<div
|
||||
className={cssStyle.sideContainer + " " + cssStyle.containerClient + " " + (active ? "" : cssStyle.hidden)}>
|
||||
<ClientSelect events={props.modalEvents} tabTarget={"client"}/>
|
||||
<ClientSelect tabTarget={"client"}/>
|
||||
</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);
|
||||
|
||||
props.modalEvents.reactUse("action_activate_tab", event => setActive(event.tab === "client-channel"));
|
||||
events.reactUse("action_activate_tab", event => setActive(event.tab === "client-channel"));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cssStyle.sideContainer + " " + cssStyle.containerChannelClient + " " + (active ? "" : cssStyle.hidden)}>
|
||||
<ClientSelect events={props.modalEvents} tabTarget={"client-channel"}/>
|
||||
<ChannelList connection={props.connection} events={props.modalEvents} tabTarget={"client-channel"}/>
|
||||
<ClientSelect tabTarget={"client-channel"}/>
|
||||
<ChannelList serverInfo={serverInfo} events={events} tabTarget={"client-channel"}/>
|
||||
</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
|
||||
} from "tc-shared/ui/modal/group-assignment/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 ModalRenderType = "page" | "dialog";
|
||||
|
@ -133,6 +136,7 @@ export abstract class AbstractModal {
|
|||
color() : "none" | "blue" { return "none"; }
|
||||
verticalAlignment() : "top" | "center" | "bottom" { return "center"; }
|
||||
|
||||
/** @deprecated */
|
||||
protected onInitialize() {}
|
||||
protected onDestroy() {}
|
||||
|
||||
|
@ -196,4 +200,9 @@ export interface ModalConstructorArguments {
|
|||
/* events */ IpcRegistryDescription<ModalClientGroupAssignmentEvents>,
|
||||
/* variables */ IpcVariableDescriptor<ModalClientGroupAssignmentVariables>,
|
||||
],
|
||||
"modal-permission-edit": [
|
||||
/* serverInfo */ PermissionEditorServerInfo,
|
||||
/* modalEvents */ IpcRegistryDescription<PermissionModalEvents>,
|
||||
/* editorEvents */ IpcRegistryDescription<PermissionEditorEvents>
|
||||
]
|
||||
}
|
|
@ -97,4 +97,10 @@ registerModal({
|
|||
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"));
|
||||
try {
|
||||
mainModalInstance = constructAbstractModalClass(modalClass, { windowed: true }, result.constructorArguments);
|
||||
mainModalInstance["onInitialize"]();
|
||||
mainModalRenderer.renderModal(mainModalInstance);
|
||||
mainModalInstance["onOpen"]();
|
||||
} catch(error) {
|
||||
loader.critical_error("Failed to invoker modal", "Lookup the console for more detail");
|
||||
logError(LogCategory.GENERAL,tr("Failed to load modal: %o"), error);
|
||||
|
|
|
@ -55,6 +55,7 @@ export class InternalModalInstance implements ModalInstanceController {
|
|||
|
||||
try {
|
||||
this.modalInstance = constructAbstractModalClass(modalClass.default, { windowed: false }, this.constructorArguments);
|
||||
this.modalInstance["onInitialize"]();
|
||||
} catch (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");
|
||||
|
@ -153,11 +154,11 @@ export class InternalModalInstance implements ModalInstanceController {
|
|||
this.events.destroy();
|
||||
}
|
||||
|
||||
private getCloseCallback() {
|
||||
protected getCloseCallback() {
|
||||
return () => this.events.fire("action_close");
|
||||
}
|
||||
|
||||
private getPopoutCallback() {
|
||||
protected getPopoutCallback() {
|
||||
if(!this.modalKlass.popoutSupported) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -169,7 +170,7 @@ export class InternalModalInstance implements ModalInstanceController {
|
|||
return () => this.events.fire("action_popout");
|
||||
}
|
||||
|
||||
private getMinimizeCallback() {
|
||||
protected getMinimizeCallback() {
|
||||
/* We can't minimize any windows */
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -15,8 +15,15 @@ export const ServerTag = React.memo((props: {
|
|||
serverName: string,
|
||||
handlerId: 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 (
|
||||
<div
|
||||
className={cssStyle.tag + (props.className ? ` ${props.className}` : ``)}
|
||||
|
|
|
@ -62,22 +62,22 @@ class ManifestGenerator {
|
|||
}
|
||||
|
||||
for(const module of compilation.chunkGraph.getChunkModules(chunk)) {
|
||||
const moduleId = compilation.chunkGraph.getModuleId(module);
|
||||
if(typeof moduleId === "string" && moduleId.startsWith("svg-sprites/")) {
|
||||
const identifier = module.identifier();
|
||||
if(typeof identifier === "string" && identifier.startsWith("svg-sprites/")) {
|
||||
/* custom svg sprite handler */
|
||||
modules.push({
|
||||
id: module.id,
|
||||
context: "svg-sprites",
|
||||
resource: moduleId.substring("svg-sprites/".length)
|
||||
resource: identifier.substring("svg-sprites/".length)
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!module.type.startsWith("javascript/")) {
|
||||
if(!module.context) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!module.context) {
|
||||
if(!module.type.startsWith("javascript/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -94,6 +94,7 @@ class ManifestGenerator {
|
|||
throw "invalid context/resource relation (" + module.context + " <-> " + path.dirname(module.resource) + ")";
|
||||
}
|
||||
|
||||
const moduleId = compilation.chunkGraph.getModuleId(module);
|
||||
modules.push({
|
||||
id: moduleId,
|
||||
context: path.relative(this.options.context, module.context).replace(/\\/g, "/"),
|
||||
|
|
Loading…
Reference in New Issue