import {spawnReactModal} from "tc-shared/ui/react-elements/Modal"; import {ConnectionHandler} from "tc-shared/ConnectionHandler"; import {Registry} from "tc-shared/events"; import {FlatInputField, Select} from "tc-shared/ui/react-elements/InputField"; import * as React from "react"; import {useEffect, useRef, useState} from "react"; import {GroupType} from "tc-shared/permission/GroupManager"; import {Translatable} from "tc-shared/ui/react-elements/i18n"; import {Button} from "tc-shared/ui/react-elements/Button"; import PermissionType from "tc-shared/permission/PermissionType"; import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration"; import {createErrorModal, createInfoModal} from "tc-shared/ui/elements/Modal"; import {tra} from "tc-shared/i18n/localize"; import {InternalModal} from "tc-shared/ui/react-elements/internal-modal/Controller"; import {ErrorCode} from "tc-shared/connection/ErrorCode"; const cssStyle = require("./ModalGroupCreate.scss"); export type GroupInfo = { id: number, name: string, type: "query" | "template" | "normal" }; export interface GroupCreateModalEvents { action_set_name: { name: string | undefined }, action_set_type: { target: "query" | "template" | "normal" }, action_set_source: { group: number }, action_cancel: {}, action_create: { name: string, target: "query" | "template" | "normal", source: number /* if zero than no template */ } query_available_groups: { }, query_available_groups_result: { groups: GroupInfo[] }, query_client_permissions: {}, notify_client_permissions: { createTemplateGroup: boolean, createQueryGroup: boolean }, notify_destroy: {} } const GroupNameInput = (props: { events: Registry, defaultSource: number }) => { const [ initialLoad, setInitialLoad ] = useState(true); const [ existingGroups, setExistingGroups ] = useState<"loading" | GroupInfo[]>("loading"); const [ selectedType, setSelectedType ] = useState<"query" | "template" | "normal" | "loading">("loading"); const refInput = useRef(); useEffect(() => { if(!initialLoad || !refInput.current) return; if(selectedType === "loading" || existingGroups === "loading") return; setInitialLoad(false); refInput.current.focus(); const defaultGroup = existingGroups.find(e => e.id === props.defaultSource); if(defaultGroup) { let name = defaultGroup.name + " (" + tr("Copy") + ")"; let index = 1; while(existingGroups.findIndex(e => e.name === name) !== -1) name = defaultGroup.name + " (" + tr("Copy")+ " " + index++ + ")"; refInput.current.setValue(name); props.events.fire("action_set_name", { name: updateGroupNameState(name) ? name : undefined }); } }); const updateGroupNameState = (input: string) => { if(!refInput.current) return false; if(input.length === 0 || input.length > 30) { refInput.current.setState({ isInvalid: true, invalidMessage: tr("Invalid group name length") }); return false; } if(existingGroups === "loading") return false; if(existingGroups.findIndex(e => e.name === input && e.type === selectedType) !== -1) { refInput.current.setState({ isInvalid: true, invalidMessage: tr("A group with this name already exists") }); return false; } refInput.current.setState({ isInvalid: false }); return true; }; props.events.reactUse("query_available_groups_result", event => setExistingGroups(event.groups)); props.events.reactUse("action_set_type", event => setSelectedType(event.target)); return ( Group name} finishOnEnter={true} disabled={existingGroups === "loading" || selectedType === "loading"} placeholder={existingGroups === "loading" || selectedType === "loading" ? tr("loading data...") : undefined} onInput={() => props.events.fire("action_set_name", { name: updateGroupNameState(refInput.current.value()) ? refInput.current.value() : undefined })} onBlur={() => props.events.fire("action_set_name", { name: updateGroupNameState(refInput.current.value()) ? refInput.current.value() : undefined })} /> ) }; const GroupTypeSelector = (props: { events: Registry }) => { const [ selectedType, setSelectedType ] = useState<"query" | "template" | "normal" | "loading">("loading"); const [ permissions, setPermissions ] = useState<"loading" | { createTemplate, createQuery }>("loading"); const refSelect = useRefTarget group type} className={cssStyle.groupType} disabled={permissions === "loading"} value={selectedType} onChange={event => event.target.value !== "loading" && props.events.fire("action_set_type", { target: event.target.value as any })} > ) }; const SourceGroupSelector = (props: { events: Registry, defaultSource: number }) => { const [ selectedGroup, setSelectedGroup ] = useState(undefined); const [ permissions, setPermissions ] = useState<"loading" | { createTemplate, createQuery }>("loading"); const [ exitingGroups, setExitingGroups ] = useState<"loading" | GroupInfo[]>("loading"); const refSelect = useRefCreate group using this template} className={cssStyle.groupSource} disabled={isLoading} value={isLoading || selectedGroup === undefined ? "-1" : selectedGroup.toString()} onChange={event => props.events.fire("action_set_source", { group: parseInt(event.target.value) })} > {exitingGroups === "loading" ? undefined : exitingGroups.filter(e => e.type === "query").map(e => ( )) } {exitingGroups === "loading" ? undefined : exitingGroups.filter(e => e.type === "template").map(e => ( )) } {exitingGroups === "loading" ? undefined : exitingGroups.filter(e => e.type === "normal").map(e => ( )) } ) }; const CreateButton = (props: { events: Registry }) => { const [ sourceGroup, setSourceGroup ] = useState(undefined); const [ groupType, setGroupType ] = useState<"query" | "template" | "normal" | undefined>(undefined); const [ groupName, setGroupName ] = useState(undefined); props.events.reactUse("action_set_name", event => setGroupName(event.name)); props.events.reactUse("action_set_type", event => setGroupType(event.target)); props.events.reactUse("action_set_source", event => setSourceGroup(event.group)); return }; class ModalGroupCreate extends InternalModal { readonly target: "server" | "channel"; readonly events: Registry; readonly defaultSourceGroup: number; constructor(connection: ConnectionHandler, events: Registry, target: "server" | "channel", defaultSourceGroup: number) { super(); this.events = events; this.defaultSourceGroup = defaultSourceGroup; this.target = target; initializeGroupCreateController(connection, this.events, this.target); } protected onInitialize() { this.events.fire_react("query_available_groups"); this.events.fire_react("query_client_permissions"); } protected onDestroy() { this.events.fire("notify_destroy"); } renderBody() { return
; } title() { return this.target === "server" ? Create a new server group : Create a new channel group; } } export function spawnGroupCreate(connection: ConnectionHandler, target: "server" | "channel", sourceGroup: number = 0) { const events = new Registry(); events.enableDebug("group-create"); const modal = spawnReactModal(ModalGroupCreate, connection, events, target, sourceGroup); modal.show(); events.on(["action_cancel", "action_create"], () => modal.destroy()); } const stringifyError = error => { if(error instanceof CommandResult) { if(error.id === ErrorCode.SERVER_INSUFFICIENT_PERMISSIONS) return tr("insufficient permissions"); else return error.message + (error.extra_message ? " (" + error.extra_message + ")" : ""); } else if(error instanceof Error) { return error.message; } else if(typeof error !== "string") { return tr("Lookup the console"); } return error; }; function initializeGroupCreateController(connection: ConnectionHandler, events: Registry, target: "server" | "channel") { events.on("query_available_groups", event => { const groups = target === "server" ? connection.groups.serverGroups : connection.groups.channelGroups; events.fire_react("query_available_groups_result", { groups: groups.map(e => { return { name: e.name, id: e.id, type: e.type === GroupType.TEMPLATE ? "template" : e.type === GroupType.QUERY ? "query" : "normal" } }) }); }); const notifyClientPermissions = () => events.fire_react("notify_client_permissions", { createQueryGroup: connection.permissions.neededPermission(PermissionType.B_SERVERINSTANCE_MODIFY_QUERYGROUP).granted(1), createTemplateGroup: connection.permissions.neededPermission(PermissionType.B_SERVERINSTANCE_MODIFY_TEMPLATES).granted(1) }); events.on("query_client_permissions", notifyClientPermissions); events.on("notify_destroy", connection.permissions.events.on("client_permissions_changed", notifyClientPermissions)); events.on("action_create", event => { let promise: Promise; if(event.source <= 0) { /* real group create */ promise = connection.serverConnection.send_command("servergroupadd", { name: event.name, type: event.target === "query" ? 2 : event.target === "template" ? 0 : 1 }); } else { /* group copy */ promise = connection.serverConnection.send_command("servergroupcopy", { ssgid: event.source, name: event.name, type: event.target === "query" ? 2 : event.target === "template" ? 0 : 1 }); } promise.then(() => { createInfoModal(tr("Group has been created"), tr("The group has been successfully created.")).open(); }).catch(error => { if(error instanceof CommandResult && error.id === ErrorCode.SERVER_INSUFFICIENT_PERMISSIONS) { createErrorModal(tr("Failed to create group"), tra("Failed to create group.\nMissing permission {}", connection.permissions.resolveInfo(parseInt(error.json["failed_permid"]))?.name || tr("unknwon"))).open(); return; } console.warn(tr("Failed to create group: %o"), error); createErrorModal(tr("Failed to create group"), tra("Failed to create group.\n{}", stringifyError(error))).open(); }); }); }