diff --git a/shared/js/events/ClientGlobalControlHandler.ts b/shared/js/events/ClientGlobalControlHandler.ts index 3281df62..80326bf7 100644 --- a/shared/js/events/ClientGlobalControlHandler.ts +++ b/shared/js/events/ClientGlobalControlHandler.ts @@ -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"; diff --git a/shared/js/events/GlobalEvents.ts b/shared/js/events/GlobalEvents.ts index 98f43a5b..0569e1bd 100644 --- a/shared/js/events/GlobalEvents.ts +++ b/shared/js/events/GlobalEvents.ts @@ -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: { diff --git a/shared/js/tree/Client.ts b/shared/js/tree/Client.ts index cfe22dd1..e430f85e 100644 --- a/shared/js/tree/Client.ts +++ b/shared/js/tree/Client.ts @@ -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 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 + }) } ] }]; diff --git a/shared/js/ui/modal/permission/EditorDefinitions.ts b/shared/js/ui/modal/permission/EditorDefinitions.ts new file mode 100644 index 00000000..7a0ebf04 --- /dev/null +++ b/shared/js/ui/modal/permission/EditorDefinitions.ts @@ -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; + }[] + } +} \ No newline at end of file diff --git a/shared/js/ui/modal/permission/PermissionEditor.scss b/shared/js/ui/modal/permission/EditorRenderer.scss similarity index 99% rename from shared/js/ui/modal/permission/PermissionEditor.scss rename to shared/js/ui/modal/permission/EditorRenderer.scss index 1b2fbbcf..b67b59d4 100644 --- a/shared/js/ui/modal/permission/PermissionEditor.scss +++ b/shared/js/ui/modal/permission/EditorRenderer.scss @@ -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); } diff --git a/shared/js/ui/modal/permission/PermissionEditor.tsx b/shared/js/ui/modal/permission/EditorRenderer.tsx similarity index 72% rename from shared/js/ui/modal/permission/PermissionEditor.tsx rename to shared/js/ui/modal/permission/EditorRenderer.tsx index ae3efcf5..7612d02c 100644 --- a/shared/js/ui/modal/permission/PermissionEditor.tsx +++ b/shared/js/ui/modal/permission/EditorRenderer.tsx @@ -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>(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, 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, 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, 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,80 +73,94 @@ const ButtonIconPreview = (props: { events: Registry, co let icon; if (!unset && iconId > 0) { - icon = ; + icon = ; } return (
props.events.fire("action_open_icon_select", {iconId: iconId})}> + onClick={() => events.fire("action_open_icon_select", {iconId: iconId})}> {icon}
-
+
- {iconId ?
props.events.fire("action_open_icon_select", {iconId: iconId})}> - Edit icon -
: undefined} - {iconId ?
props.events.fire("action_remove_permissions", { - permissions: [{ - name: PermissionType.I_ICON_ID, - mode: "value" - }] - })}> - Remove icon -
: undefined} - {!iconId ?
props.events.fire("action_open_icon_select", {iconId: 0})}> - Add icon -
: undefined} + {iconId ? ( +
events.fire("action_open_icon_select", {iconId: iconId})}> + Edit icon +
+ ) : undefined} + {iconId ? ( +
events.fire("action_remove_permissions", { + permissions: [{ + name: PermissionType.I_ICON_ID, + mode: "value" + }] + })}> + Remove icon +
+ ) : undefined} + {!iconId ? ( +
events.fire("action_open_icon_select", {iconId: 0})}> + Add icon +
+ ) : undefined}
); -}; +}); -const ClientListButton = (props: { events: Registry }) => { +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 }; -const MenuBar = (props: { events: Registry, connection: ConnectionHandler }) => { - return
- - { + const events = useContext(EventContext); - label={Filter permissions} - labelType={"floating"} - labelClassName={cssStyle.label} - labelFloatingClassName={cssStyle.labelFloating} - onInput={text => props.events.fire("action_set_filter", {filter: text})} - /> -
- Assigned only} - onChange={state => props.events.fire("action_set_assigned_only", {value: state})}/> - { /* Editable only} /> */} + return ( +
+ + Filter permissions} + labelType={"floating"} + labelClassName={cssStyle.label} + labelFloatingClassName={cssStyle.labelFloating} + onInput={text => events.fire("action_set_filter", {filter: text})} + /> +
+ Assigned only} + onChange={state => events.fire("action_set_assigned_only", {value: state})} + /> + { /* Editable only} /> */} +
+
- -
; -}; + ); +}); interface LinkedGroupedPermissions { groupId: string; @@ -266,8 +187,7 @@ interface LinkedGroupedPermissions { elementVisible: boolean; } -const PermissionEntryRow = (props: { - events: Registry, +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(props.value.value); const [forceValueUpdate, setForceValueUpdate] = useState(false); @@ -306,7 +228,7 @@ const PermissionEntryRow = (props: { if (isBoolPermission) { valueElement = = 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 = { - 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 = { - props.events.fire("action_set_permissions", { + events.fire("action_set_permissions", { permissions: [{ name: props.permission, mode: "value", @@ -392,51 +314,67 @@ const PermissionEntryRow = (props: { if (typeof granted === "number") { if (grantedApplying) { - grantedElement = - { - }}/>; + grantedElement = ( + {}} + /> + ); } else { - grantedElement = { - setGrantedEditing(false); - if (!refGranted.current) - return; + grantedElement = ( + { + setGrantedEditing(false); + if (!refGranted.current) + return; - const newValue = refGranted.current.value; - if (newValue === "") { - if (typeof granted === "undefined") - return; + const newValue = refGranted.current.value; + if (newValue === "") { + if (typeof granted === "undefined") + return; - setForceGrantedUpdate(true); - props.events.fire("action_remove_permissions", { - permissions: [{ - name: props.permission, - mode: "grant" - }] - }); - } else { - const numberValue = parseInt(newValue); - if (isNaN(numberValue)) return; - if (numberValue === granted && !forceGrantedUpdate) { - /* no change */ - return; - } + setForceGrantedUpdate(true); + events.fire("action_remove_permissions", { + permissions: [{ + name: props.permission, + mode: "grant" + }] + }); + } else { + const numberValue = parseInt(newValue); + if (isNaN(numberValue)) return; + if (numberValue === granted && !forceGrantedUpdate) { + /* no change */ + return; + } - setForceGrantedUpdate(true); - props.events.fire("action_set_permissions", { - permissions: [{ - name: props.permission, - mode: "grant", - value: numberValue - }] - }); - } - }} onChange={() => { - }} onKeyPress={e => e.key === "Enter" && e.currentTarget.blur()}/>; + setForceGrantedUpdate(true); + events.fire("action_set_permissions", { + permissions: [{ + name: props.permission, + mode: "grant", + value: numberValue + }] + }); + } + }} + onChange={() => { + }} + onKeyPress={e => e.key === "Enter" && e.currentTarget.blur()} + /> + ); } } - props.events.reactUse("action_start_permission_edit", event => { + 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 + ":
" + 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); }} >
@@ -673,7 +610,7 @@ const PermissionEntryRow = (props: {
{skipElement}
{negateElement}
{ - 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}
); -}; +}); -const PermissionGroupRow = (props: { events: Registry, 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, 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 })} >
-
props.events.fire("action_toggle_group", { - collapsed: !collapsed, - groupId: props.group.groupId - })}/> + events.fire("action_toggle_group", { + collapsed: !collapsed, + groupId: props.group.groupId + })} + />
{props.group.groupName}
@@ -785,7 +727,7 @@ const PermissionGroupRow = (props: { events: Registry, g
); -}; +}); type PermissionValue = { value?: number, flagNegate?: boolean, flagSkip?: boolean, granted?: number }; @@ -835,18 +777,20 @@ class PermissionList extends React.Component<{ events: Registry 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}) - }); + 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: "normal", + label: tr("Collapse all"), + click: () => this.props.events.fire("action_toggle_group", {groupId: null, collapsed: true}) + } + ]); }}> {elements}
); index++; @@ -1245,7 +1189,6 @@ class PermissionList extends React.Component<{ events: Registry }) => { +const PermissionTable = React.memo(() => { + const events = useContext(EventContext); const [mode, setMode] = useState("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 }) =>
- + ); -}; +}); -const RefreshButton = (props: { events: Registry }) => { +const RefreshButton = React.memo(() => { + const events = useContext(EventContext); const [unset, setUnset] = useState(true); const [nextTime, setNextTime] = useState(0); const refButton = useRef -}; +}); interface PermissionEditorProperties { - connection: ConnectionHandler; + handlerId: string; + serverUniqueId: string; events: Registry; } @@ -1346,18 +1294,22 @@ interface PermissionEditorState { state: "no-permissions" | "unset" | "normal"; } -export class PermissionEditor extends React.Component { +export class EditorRenderer extends React.Component { render() { - return [ - , - , -
- -
- ]; + return ( + + + + +
+ +
+
+
+ ); } componentDidMount(): void { - this.props.events.fire("action_set_mode", {mode: "unset"}); + this.props.events.fire("action_set_mode", { mode: "unset" }); } } \ No newline at end of file diff --git a/shared/js/ui/modal/permission/ModalPermissionEditor.tsx b/shared/js/ui/modal/permission/ModalController.ts similarity index 76% rename from shared/js/ui/modal/permission/ModalPermissionEditor.tsx rename to shared/js/ui/modal/permission/ModalController.ts index 37e5cb07..7408355d 100644 --- a/shared/js/ui/modal/permission/ModalPermissionEditor.tsx +++ b/shared/js/ui/modal/permission/ModalController.ts @@ -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: () => Server Groups}, - "groups-channel": {name: "Channel Groups", useTranslate: () => useTr("Channel Groups"), renderTranslate: () => Channel Groups}, - "channel": {name: "Channel Permissions", useTranslate: () => useTr("Channel Permissions"), renderTranslate: () => Channel Permissions}, - "client": {name: "Client Permissions", useTranslate: () => useTr("Client Permissions"), renderTranslate: () => Client Permissions}, - "client-channel": {name: "Client Channel Permissions", useTranslate: () => useTr("Client Channel Permissions"), renderTranslate: () => Client Channel Permissions}, -}; +export function spawnPermissionEditorModal(connection: ConnectionHandler, defaultTab: PermissionEditorTab = "groups-server", defaultTabValues?: DefaultTabValues) { + const modalEvents = new Registry(); + const editorEvents = new Registry(); -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 }) => { - const [activeTab, setActiveTab] = useState("groups-server"); - props.events.reactUse("action_activate_tab", event => setActiveTab(event.tab)); - - return ( - - ); -}; - -const TabSelectorEntry = (props: { events: Registry, entry: PermissionEditorTab }) => { - const [active, setActive] = useState(props.entry === "groups-server"); - - props.events.reactUse("action_activate_tab", event => setActive(event.tab === props.entry)); - - return ( -
!active && props.events.fire("action_activate_tab", {tab: props.entry})}> - - {PermissionTabName[props.entry].renderTranslate()} - -
- ); -}; - -const TabSelector = (props: { events: Registry }) => { - return ( -
- - - - - -
- ); -}; - -export type DefaultTabValues = { groupId?: number, channelId?: number, clientDatabaseId?: number }; - -class PermissionEditorModal extends InternalModal { - readonly modalEvents = new Registry(); - readonly editorEvents = new Registry(); - - 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 }); - this.modalEvents.fire_later("query_client_permissions"); - } + modalEvents.fire_react("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 ( -
-
- - -
- -
- - -
-
- ); - } + modalEvents.on("notify_destroy", connection.events().on("notify_connection_state_changed", event => { + if(event.newState !== ConnectionState.CONNECTED) { + modal.destroy(); + } + })); - renderTitle(): React.ReactElement { - return Server permissions; - } + 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) { diff --git a/shared/js/ui/modal/permission/ModalDefinitions.ts b/shared/js/ui/modal/permission/ModalDefinitions.ts new file mode 100644 index 00000000..c5217d01 --- /dev/null +++ b/shared/js/ui/modal/permission/ModalDefinitions.ts @@ -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: {} +} \ No newline at end of file diff --git a/shared/js/ui/modal/permission/ModalPermissionEditor.scss b/shared/js/ui/modal/permission/ModalPermissionEditor.scss deleted file mode 100644 index a85ce63b..00000000 --- a/shared/js/ui/modal/permission/ModalPermissionEditor.scss +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/shared/js/ui/modal/permission/TabHandler.scss b/shared/js/ui/modal/permission/ModalRenderer.scss similarity index 54% rename from shared/js/ui/modal/permission/TabHandler.scss rename to shared/js/ui/modal/permission/ModalRenderer.scss index e218b440..22aca55e 100644 --- a/shared/js/ui/modal/permission/TabHandler.scss +++ b/shared/js/ui/modal/permission/ModalRenderer.scss @@ -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); diff --git a/shared/js/ui/modal/permission/TabHandler.tsx b/shared/js/ui/modal/permission/ModalRenderer.tsx similarity index 72% rename from shared/js/ui/modal/permission/TabHandler.tsx rename to shared/js/ui/modal/permission/ModalRenderer.tsx index decd8fea..bc3b0420 100644 --- a/shared/js/ui/modal/permission/TabHandler.tsx +++ b/shared/js/ui/modal/permission/ModalRenderer.tsx @@ -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, editorEvents: Registry }, {}> { - render() { - return [ - , - , - , - , - - ]; - } -} +export type PermissionEditorServerInfo = { handlerId: string, serverUniqueId: string }; +const ModalEventContext = React.createContext>(undefined); +const EditorEventContext = React.createContext>(undefined); +const ServerInfoContext = React.createContext(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 (
- +
{groupTypePrefix + props.group.name + " (" + props.group.id + ")"}
) -}; +}); @ReactEventHandler(e => e.props.events) -class GroupsList extends React.Component<{ connection: ConnectionHandler, events: Registry, target: "server" | "channel" }, { +class GroupsList extends React.PureComponent<{ events: Registry, target: "server" | "channel" }, { selectedGroupId: number, showQueryGroups: boolean, showTemplateGroups: boolean, @@ -118,53 +120,59 @@ class GroupsList extends React.Component<{ connection: ConnectionHandler, events }); }}>
- {this.visibleGroups.map(e => this.props.events.fire("action_select_group", { - id: e.id, - target: this.props.target - })} - onContextMenu={event => { - event.preventDefault(); - this.props.events.fire("action_select_group", { - 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 - }, { - name: tr("Copy permissions"), - type: MenuEntryType.ENTRY, - icon_class: "client-copy", - callback: () => this.props.events.fire("action_group_copy_permissions", { - target: this.props.target, - sourceGroup: e.id - }), - invalidPermission: 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", { - target: this.props.target, - sourceGroup: e.id - }), - invalidPermission: this.state.disableGroupAdd - }); - }} - />)} + {this.visibleGroups.map(e => ( + this.props.events.fire("action_select_group", { + id: e.id, + target: this.props.target + })} + onContextMenu={event => { + event.preventDefault(); + this.props.events.fire("action_select_group", { + target: this.props.target, + id: e.id + }); + + spawnContextMenu({ pageX: event.pageX, pageY: event.pageY }, [ + { + type: "normal", + label: tr("Rename group"), + click: () => this.onGroupRename(), + icon: ClientIcon.ChangeNickname, + enabled: !this.state.disableGroupRename + }, { + 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 + }), + enabled: !this.state.disablePermissionCopy + }, { + 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 + }), + icon: ClientIcon.Add, + enabled: !this.state.disableGroupAdd + } + ]); + }} + /> + ))}
,
@@ -426,7 +434,7 @@ class GroupsList extends React.Component<{ connection: ConnectionHandler, events @ReactEventHandler(e => e.props.events) -class ServerClientList extends React.Component<{ connection: ConnectionHandler, events: Registry }, { +class ServerClientList extends React.Component<{ events: Registry }, { selectedGroupId: number, selectedClientId: number, @@ -475,16 +483,18 @@ class ServerClientList extends React.Component<{ connection: ConnectionHandler, return [
this.onListContextMenu(e)}> - {selectedGroup ? -
-
- -
-
{groupTypePrefix + selectedGroup.name + " (" + selectedGroup.id + ")"}
-
- : undefined - } + {selectedGroup ? ( + + {serverInfo => ( +
+
+ +
+
{groupTypePrefix + selectedGroup.name + " (" + selectedGroup.id + ")"}
+
+ )} +
+ ) : undefined}
{this.clients.map(client =>
}) => { +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 (
- +
- +
); -}; +}); -const ChannelGroupsSideBar = (props: { connection: ConnectionHandler, modalEvents: Registry }) => { +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 (
- +
); -}; +}); @ReactEventHandler(e => e.props.events) -class ChannelList extends React.Component<{ connection: ConnectionHandler, events: Registry, tabTarget: "channel" | "client-channel" }, { selectedChanelId: number }> { +class ChannelList extends React.Component<{ serverInfo: PermissionEditorServerInfo, events: Registry, 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 })} > - + {e.name + " (" + e.id + ")"}
))} @@ -925,20 +937,24 @@ class ChannelList extends React.Component<{ connection: ConnectionHandler, event } } -const ChannelSideBar = (props: { connection: ConnectionHandler, modalEvents: Registry }) => { +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 (
- +
); -}; +}); + +const ClientSelect = React.memo((props: { tabTarget: "client" | "client-channel" }) => { + const events = React.useContext(ModalEventContext); -const ClientSelect = (props: { events: Registry, tabTarget: "client" | "client-channel" }) => { const [clientIdentifier, setClientIdentifier] = useState(undefined); const [clientInfo, setClientInfo] = useState<{ name: string, uniqueId: string, databaseId: number }>(undefined); @@ -947,24 +963,24 @@ const ClientSelect = (props: { events: Registry, tabTarge const refUniqueIdentifier = useRef(); const refDatabaseId = useRef(); - 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, 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, 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, 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, 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, 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}); }} />
@@ -1097,31 +1113,147 @@ const ClientSelect = (props: { events: Registry, tabTarge disabled={true}/>
); -}; +}); -const ClientSideBar = (props: { connection: ConnectionHandler, modalEvents: Registry }) => { +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 (
- +
); -}; +}); -const ClientChannelSideBar = (props: { connection: ConnectionHandler, modalEvents: Registry }) => { +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 (
- - + +
); -}; \ No newline at end of file +}); + +export const PermissionTabName: { [T in PermissionEditorTab]: { name: string, useTranslate: () => string, renderTranslate: () => React.ReactNode } } = { + "groups-server": {name: "Server Groups", useTranslate: () => useTr("Server Groups"), renderTranslate: () => Server Groups}, + "groups-channel": {name: "Channel Groups", useTranslate: () => useTr("Channel Groups"), renderTranslate: () => Channel Groups}, + "channel": {name: "Channel Permissions", useTranslate: () => useTr("Channel Permissions"), renderTranslate: () => Channel Permissions}, + "client": {name: "Client Permissions", useTranslate: () => useTr("Client Permissions"), renderTranslate: () => Client Permissions}, + "client-channel": {name: "Client Channel Permissions", useTranslate: () => useTr("Client Channel Permissions"), renderTranslate: () => Client Channel Permissions}, +}; + +const ActiveTabInfo = React.memo(() => { + const events = useContext(ModalEventContext); + const [activeTab, setActiveTab] = useState("groups-server"); + events.reactUse("action_activate_tab", event => setActiveTab(event.tab)); + + return ( + + ); +}); + +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 ( +
!active && events.fire("action_activate_tab", {tab: props.entry})}> + + {PermissionTabName[props.entry].renderTranslate()} + +
+ ); +}); + +const TabSelector = React.memo(() => { + return ( +
+ + + + + +
+ ); +}); + +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; + readonly editorEvents: Registry; + + constructor(serverInfo: PermissionEditorServerInfo, modalEvents: IpcRegistryDescription, editorEvents: IpcRegistryDescription) { + 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 ( + + + +
+
+ + + + + + +
+ +
+ + +
+
+ +
+
+
+ ); + } + + renderTitle(): React.ReactElement { + return Server permission editor; + } +} + +export default PermissionEditorModal; \ No newline at end of file diff --git a/shared/js/ui/react-elements/Arrow.scss b/shared/js/ui/react-elements/Arrow.scss new file mode 100644 index 00000000..c01b2c6e --- /dev/null +++ b/shared/js/ui/react-elements/Arrow.scss @@ -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); + } +} \ No newline at end of file diff --git a/shared/js/ui/react-elements/Arrow.tsx b/shared/js/ui/react-elements/Arrow.tsx new file mode 100644 index 00000000..2514923f --- /dev/null +++ b/shared/js/ui/react-elements/Arrow.tsx @@ -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 +}) => ( +
+) \ No newline at end of file diff --git a/shared/js/ui/react-elements/modal/Definitions.ts b/shared/js/ui/react-elements/modal/Definitions.ts index c68da018..c6ebc1d0 100644 --- a/shared/js/ui/react-elements/modal/Definitions.ts +++ b/shared/js/ui/react-elements/modal/Definitions.ts @@ -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, /* variables */ IpcVariableDescriptor, ], + "modal-permission-edit": [ + /* serverInfo */ PermissionEditorServerInfo, + /* modalEvents */ IpcRegistryDescription, + /* editorEvents */ IpcRegistryDescription + ] } \ No newline at end of file diff --git a/shared/js/ui/react-elements/modal/Registry.ts b/shared/js/ui/react-elements/modal/Registry.ts index debad67d..30023487 100644 --- a/shared/js/ui/react-elements/modal/Registry.ts +++ b/shared/js/ui/react-elements/modal/Registry.ts @@ -97,4 +97,10 @@ registerModal({ popoutSupported: true }); +registerModal({ + modalId: "modal-permission-edit", + classLoader: async () => await import("tc-shared/ui/modal/permission/ModalRenderer"), + popoutSupported: true +}); + diff --git a/shared/js/ui/react-elements/modal/external/renderer/EntryPoint.ts b/shared/js/ui/react-elements/modal/external/renderer/EntryPoint.ts index 795f42a9..2334293c 100644 --- a/shared/js/ui/react-elements/modal/external/renderer/EntryPoint.ts +++ b/shared/js/ui/react-elements/modal/external/renderer/EntryPoint.ts @@ -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); diff --git a/shared/js/ui/react-elements/modal/internal/index.tsx b/shared/js/ui/react-elements/modal/internal/index.tsx index 8627e6f9..dfa516dc 100644 --- a/shared/js/ui/react-elements/modal/internal/index.tsx +++ b/shared/js/ui/react-elements/modal/internal/index.tsx @@ -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; } diff --git a/shared/js/ui/tree/EntryTags.tsx b/shared/js/ui/tree/EntryTags.tsx index 5b252152..26bbe47e 100644 --- a/shared/js/ui/tree/EntryTags.tsx +++ b/shared/js/ui/tree/EntryTags.tsx @@ -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 {props.serverName}; + } + return (
" + path.dirname(module.resource) + ")"; } + const moduleId = compilation.chunkGraph.getModuleId(module); modules.push({ id: moduleId, context: path.relative(this.options.context, module.context).replace(/\\/g, "/"),