1241 lines
No EOL
50 KiB
TypeScript
1241 lines
No EOL
50 KiB
TypeScript
import * as React from "react";
|
|
import {useContext, useEffect, useRef, useState} from "react";
|
|
import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
|
|
import {IpcRegistryDescription, Registry} from "tc-shared/events";
|
|
import {
|
|
ChannelEditablePermissions,
|
|
ChannelEditablePermissionValue,
|
|
ChannelEditableProperty,
|
|
ChannelEditEvents,
|
|
ChannelEditPermissionsState,
|
|
ChannelPropertyPermission
|
|
} from "tc-shared/ui/modal/channel-edit/Definitions";
|
|
import {BoxedInputField, Select} from "tc-shared/ui/react-elements/InputField";
|
|
import {Switch} from "tc-shared/ui/react-elements/Switch";
|
|
import {Button} from "tc-shared/ui/react-elements/Button";
|
|
import {Tab, TabEntry} from "tc-shared/ui/react-elements/Tab";
|
|
import {Settings, settings} from "tc-shared/settings";
|
|
import {joinClassList, useTr} from "tc-shared/ui/react-elements/Helper";
|
|
import {IconTooltip} from "tc-shared/ui/react-elements/Tooltip";
|
|
import {RadioButton} from "tc-shared/ui/react-elements/RadioButton";
|
|
import {Slider} from "tc-shared/ui/react-elements/Slider";
|
|
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
|
|
import {RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon";
|
|
import {getIconManager} from "tc-shared/file/Icons";
|
|
import {AbstractModal} from "tc-shared/ui/react-elements/modal/Definitions";
|
|
import {ChannelNameAlignment, ChannelNameParser} from "tc-shared/tree/Channel";
|
|
|
|
const cssStyle = require("./Renderer.scss");
|
|
|
|
const ModalTypeContext = React.createContext<"channel-edit" | "channel-create">("channel-edit");
|
|
const EventContext = React.createContext<Registry<ChannelEditEvents>>(undefined);
|
|
|
|
type ChannelPropertyState<T extends keyof ChannelEditableProperty> = {
|
|
setPropertyValue: (value: ChannelEditableProperty[T], dontUpdateController?: boolean) => void
|
|
} & ({
|
|
propertyState: "loading",
|
|
propertyValue: undefined,
|
|
} | {
|
|
propertyState: "normal" | "applying",
|
|
propertyValue: ChannelEditableProperty[T],
|
|
})
|
|
|
|
const kPropertyLoading = "____loading_____";
|
|
function useProperty<T extends keyof ChannelEditableProperty>(property: T) : ChannelPropertyState<T> {
|
|
const events = useContext(EventContext);
|
|
|
|
const [ value, setValue ] = useState<ChannelEditableProperty[T] | typeof kPropertyLoading>(() => {
|
|
events.fire("query_property", { property: property });
|
|
return kPropertyLoading;
|
|
});
|
|
|
|
events.reactUse("notify_property", event => {
|
|
if(event.property !== property) {
|
|
return;
|
|
}
|
|
|
|
setValue(event.value as any);
|
|
}, undefined, []);
|
|
|
|
if(value === kPropertyLoading) {
|
|
return {
|
|
propertyState: "loading",
|
|
propertyValue: undefined,
|
|
setPropertyValue: _value => {}
|
|
};
|
|
} else {
|
|
return {
|
|
propertyState: "normal",
|
|
propertyValue: value,
|
|
setPropertyValue: (value, dontUpdateController?: boolean) => {
|
|
setValue(value);
|
|
if(!dontUpdateController) {
|
|
events.fire("action_change_property", { property: property, value: value });
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
function usePropertyPermission<T extends keyof ChannelPropertyPermission>(permission: T, defaultValue: ChannelPropertyPermission[T]) : ChannelPropertyPermission[T] {
|
|
const events = useContext(EventContext);
|
|
const [ value, setValue ] = useState<ChannelPropertyPermission[T]>(() => {
|
|
events.fire("query_property_permission", { permission: permission });
|
|
return defaultValue;
|
|
});
|
|
|
|
events.reactUse("notify_property_permission", event => event.permission === permission && setValue(event.value as any));
|
|
|
|
return value;
|
|
}
|
|
|
|
function usePermission(permission: ChannelEditablePermissions) : { permissionValue: ChannelEditablePermissionValue, setPermissionValue: (newValue: number, dontUpdateController?: boolean) => void } {
|
|
const events = useContext(EventContext);
|
|
const [ value, setValue ] = useState<ChannelEditablePermissionValue>(() => {
|
|
events.fire("query_permission", { permission: permission });
|
|
return { state: "loading" };
|
|
});
|
|
|
|
events.reactUse("notify_permission", event => event.permission === permission && setValue(event.value));
|
|
|
|
return {
|
|
permissionValue: value,
|
|
setPermissionValue: (newValue, dontUpdateController) => {
|
|
setValue({ value: newValue, state: "editable" });
|
|
if(!dontUpdateController) {
|
|
events.fire("action_change_permission", { permission: permission, value: newValue });
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
function useValidationState<T extends keyof ChannelEditableProperty>(property: T) : boolean {
|
|
const events = useContext(EventContext);
|
|
const [ valid, setValid ] = useState(() => {
|
|
events.fire("query_property_valid_state", { property: property });
|
|
return true;
|
|
});
|
|
|
|
events.reactUse("notify_property_validate_state", event => event.property === property && setValid(event.valid));
|
|
|
|
return valid;
|
|
}
|
|
|
|
const ChannelNameType = (props: { selected: ChannelNameAlignment }) => {
|
|
|
|
}
|
|
|
|
const ChannelName = React.memo(() => {
|
|
const modalType = useContext(ModalTypeContext);
|
|
|
|
const { propertyValue, propertyState, setPropertyValue } = useProperty("name");
|
|
const editable = usePropertyPermission("name", modalType === "channel-create");
|
|
const valid = useValidationState("name");
|
|
|
|
const refSelect = useRef<HTMLSelectElement>();
|
|
|
|
const setValue = (text: string | undefined, localOnly: boolean) => {
|
|
let rawName;
|
|
switch(propertyValue.hasParent ? "normal" : refSelect.current.value) {
|
|
case "center":
|
|
rawName = "[cspacer" + propertyValue.spacerUniqueId + "]" + text;
|
|
break;
|
|
|
|
case "left":
|
|
rawName = "[lspacer" + propertyValue.spacerUniqueId + "]" + text;
|
|
break;
|
|
|
|
case "right":
|
|
rawName = "[rspacer" + propertyValue.spacerUniqueId + "]" + text;
|
|
break;
|
|
|
|
case "repetitive":
|
|
rawName ="[*spacer" + propertyValue.spacerUniqueId + "]" + text;
|
|
break;
|
|
|
|
default:
|
|
case "normal":
|
|
rawName = text;
|
|
break;
|
|
}
|
|
|
|
setPropertyValue({
|
|
rawName,
|
|
parsedName: text,
|
|
|
|
hasParent: propertyValue.hasParent,
|
|
spacerUniqueId: propertyValue.spacerUniqueId,
|
|
maxNameLength: propertyValue.maxNameLength,
|
|
parsedAlignment: propertyValue.parsedAlignment
|
|
}, localOnly);
|
|
}
|
|
|
|
return (
|
|
<div className={joinClassList(cssStyle.channelName, propertyValue?.hasParent && cssStyle.hasParent)}>
|
|
<Select
|
|
value={propertyValue?.parsedAlignment || "loading"}
|
|
className={cssStyle.select}
|
|
onChange={() => setValue(propertyValue.parsedName, false)}
|
|
type={"boxed"}
|
|
title={useTr("Channel name mode")}
|
|
refSelect={refSelect}
|
|
disabled={!editable}
|
|
>
|
|
<option value={"loading"} style={{ display: "none" }}>{useTr("loading")}</option>
|
|
<option value={"normal"}>{useTr("Normal Name")}</option>
|
|
<option value={"center"}>{useTr("Centered")}</option>
|
|
<option value={"left"}>{useTr("Left Aligned")}</option>
|
|
<option value={"right"}>{useTr("Right Aligned")}</option>
|
|
<option value={"repetitive"}>{useTr("Repetitive")}</option>
|
|
</Select>
|
|
<BoxedInputField
|
|
className={cssStyle.input}
|
|
disabled={!editable || propertyState !== "normal"}
|
|
value={(propertyValue?.hasParent ? propertyValue?.rawName : propertyValue?.parsedName) || ""}
|
|
placeholder={propertyState === "normal" ? tr("Channel name") : tr("loading")}
|
|
onInput={value => setValue(value, true)}
|
|
onChange={value => setValue(value, false)}
|
|
isInvalid={!valid}
|
|
maxLength={propertyValue?.maxNameLength}
|
|
/>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const ChannelIcon = () => {
|
|
const events = useContext(EventContext);
|
|
const { propertyValue, propertyState, setPropertyValue } = useProperty("icon");
|
|
const permissionGranted = usePropertyPermission("icon", false);
|
|
|
|
const icon = getIconManager().resolveIcon(propertyValue ? propertyValue.iconId : 0, propertyValue?.remoteIcon.serverUniqueId, propertyValue?.remoteIcon.handlerId);
|
|
|
|
const enabled = permissionGranted && propertyState === "normal";
|
|
return (
|
|
<div className={cssStyle.iconContainer + " " + (enabled ? "" : cssStyle.disabled)}>
|
|
<div className={cssStyle.preview} onClick={() => enabled && events.fire("action_icon_select")}>
|
|
<RemoteIconRenderer icon={icon} key={"icon-" + icon.iconId} />
|
|
</div>
|
|
<div className={cssStyle.containerDropdown}>
|
|
<div className={cssStyle.button}>
|
|
<div className="arrow down" />
|
|
</div>
|
|
<div className={cssStyle.dropdown}>
|
|
<div className={cssStyle.entry} onClick={() => enabled && events.fire("action_icon_select")}><Translatable>Edit icon</Translatable></div>
|
|
<div className={cssStyle.entry} onClick={() => {
|
|
if(!enabled) {
|
|
return;
|
|
}
|
|
|
|
setPropertyValue({
|
|
iconId: 0,
|
|
remoteIcon: {
|
|
iconId: 0,
|
|
handlerId: propertyValue.remoteIcon.handlerId,
|
|
serverUniqueId: propertyValue.remoteIcon.serverUniqueId
|
|
}
|
|
});
|
|
}}><Translatable>Remove icon</Translatable></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const ChannelPassword = React.memo(() => {
|
|
const { propertyValue, propertyState, setPropertyValue } = useProperty("password");
|
|
const { editable } = usePropertyPermission("password", { enforced: false, editable: false });
|
|
const valid = useValidationState("password");
|
|
|
|
return (
|
|
<BoxedInputField
|
|
className={cssStyle.input}
|
|
disabled={!editable || propertyState !== "normal"}
|
|
value={propertyValue?.state === "set" ? propertyValue.password || "placeholder" : ""}
|
|
placeholder={propertyState === "normal" ? tr("Password") : tr("loading")}
|
|
onInput={value => {
|
|
if(value.length > 0) {
|
|
setPropertyValue({ state: "set", password: value }, true);
|
|
} else {
|
|
setPropertyValue({ state: "clear" }, true);
|
|
}
|
|
}}
|
|
onChange={value => {
|
|
if(value.length > 0) {
|
|
setPropertyValue({ state: "set", password: value });
|
|
} else {
|
|
setPropertyValue({ state: "clear" });
|
|
}
|
|
}}
|
|
isInvalid={!valid}
|
|
type={"password"}
|
|
/>
|
|
);
|
|
});
|
|
|
|
const ChannelTopic = React.memo(() => {
|
|
const { propertyValue, propertyState, setPropertyValue } = useProperty("topic");
|
|
const editable = usePropertyPermission("topic", false);
|
|
|
|
return (
|
|
<BoxedInputField
|
|
className={cssStyle.input}
|
|
disabled={!editable || propertyState !== "normal"}
|
|
value={propertyValue || ""}
|
|
placeholder={propertyState === "normal" ? tr("Topic") : tr("loading")}
|
|
onInput={value => setPropertyValue(value, true)}
|
|
onChange={value => setPropertyValue(value)}
|
|
/>
|
|
);
|
|
});
|
|
|
|
const BBCodeEditor = React.memo((props: { text: string, placeholder: string, disabled: boolean, callbackEdit: (value: string) => void }) => {
|
|
const refText = useRef<HTMLTextAreaElement>();
|
|
|
|
const insertTag = (open: string, close: string) => {
|
|
const element = refText.current;
|
|
if(!element) {
|
|
return;
|
|
}
|
|
|
|
if (element.selectionStart || element.selectionStart == 0) {
|
|
const startPos = element.selectionStart;
|
|
const endPos = element.selectionEnd;
|
|
element.value = element.value.substring(0, startPos) + open + element.value.substring(startPos, endPos) + close + element.value.substring(endPos);
|
|
element.selectionEnd = endPos + open.length;
|
|
element.selectionStart = element.selectionEnd;
|
|
} else {
|
|
element.value += open + close;
|
|
element.selectionEnd = element.value.length - close.length;
|
|
element.selectionStart = element.selectionEnd;
|
|
}
|
|
element.focus();
|
|
};
|
|
|
|
return (
|
|
<div className={cssStyle.bbcodeEditor}>
|
|
<div className={cssStyle.toolbar}>
|
|
<div className={cssStyle.button + " " + cssStyle.buttonBold} onClick={() => insertTag("[b]", "[/b]")}>B</div>
|
|
<div className={cssStyle.button + " " + cssStyle.buttonItalic} onClick={() => insertTag("[i]", "[/i]")}>I</div>
|
|
<div className={cssStyle.button + " " + cssStyle.buttonUnderline} onClick={() => insertTag("[u]", "[/u]")}>U</div>
|
|
<label className={cssStyle.button + " " + cssStyle.buttonColor}>
|
|
<input type="color" value="#FF0000" onInput={event => insertTag("[color=" + event.currentTarget.value + "]", "[/color]")} />
|
|
<a className={cssStyle.rainbowLetter}>C</a>
|
|
</label>
|
|
</div>
|
|
<textarea
|
|
ref={refText}
|
|
className={cssStyle.input + " input-boxed"}
|
|
placeholder={props.placeholder}
|
|
defaultValue={props.text}
|
|
disabled={props.disabled}
|
|
onChange={event => props.callbackEdit(event.target.value)}
|
|
/>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const ChannelDescription = React.memo(() => {
|
|
const { propertyValue, propertyState, setPropertyValue } = useProperty("description");
|
|
const editable = usePropertyPermission("description", false);
|
|
|
|
return (
|
|
<BBCodeEditor
|
|
text={propertyValue || ""}
|
|
disabled={!editable || propertyState !== "normal"}
|
|
placeholder={useTr("Description")}
|
|
callbackEdit={setPropertyValue}
|
|
/>
|
|
);
|
|
});
|
|
|
|
const BlockProperty = (props: { children: [React.ReactElement | string, React.ReactElement] }) => {
|
|
return (
|
|
<div className={cssStyle.blockProperty}>
|
|
<div className={cssStyle.title}>
|
|
{props.children[0]}
|
|
</div>
|
|
{props.children[1]}
|
|
</div>
|
|
)
|
|
};
|
|
|
|
const ChannelType = React.memo(() => {
|
|
const { propertyValue, propertyState, setPropertyValue } = useProperty("type");
|
|
const permissions = usePropertyPermission("channelType", { default: false, temporary: false, semiPermanent: false, permanent: false });
|
|
|
|
return (
|
|
<BlockProperty>
|
|
<Translatable>Channel Type</Translatable>
|
|
<Select
|
|
type={"boxed"}
|
|
value={propertyValue?.type || "loading"}
|
|
disabled={propertyState !== "normal" || (!permissions.temporary && !permissions.semiPermanent && !permissions.permanent) || propertyValue.originallyDefault}
|
|
onChange={event => setPropertyValue({ type: event.target.value as any })}
|
|
>
|
|
<option value={"temporary"} disabled={!permissions.temporary}>{useTr("Temporary")}</option>
|
|
<option value={"semi-permanent"} disabled={!permissions.semiPermanent}>{useTr("Semi-Permanent")}</option>
|
|
<option value={"permanent"} disabled={!permissions.permanent}>{useTr("Permanent")}</option>
|
|
<option value={"default"} disabled={!permissions.default}>{useTr("Default Channel")}</option>
|
|
<option value={"loading"} style={{ display: "none" }}>{useTr("loading")}</option>
|
|
</Select>
|
|
</BlockProperty>
|
|
);
|
|
});
|
|
|
|
const SidebarType = React.memo(() => {
|
|
const mode = useContext(ModalTypeContext);
|
|
let { propertyValue, propertyState, setPropertyValue } = useProperty("sideBar");
|
|
const permissionGranted = usePropertyPermission("sidebarMode", false);
|
|
|
|
if(propertyValue === "unknown" && mode === "channel-create") {
|
|
/* the default mode */
|
|
propertyValue = "conversation";
|
|
}
|
|
|
|
return (
|
|
<BlockProperty>
|
|
<Translatable>Sidebar Type</Translatable>
|
|
<Select
|
|
type={"boxed"}
|
|
value={propertyValue || "loading"}
|
|
disabled={propertyState !== "normal" || !permissionGranted || propertyValue === "not-supported"}
|
|
onChange={event => setPropertyValue(event.target.value as any)}
|
|
>
|
|
<option value={"unknown"} style={{ display: "none" }}>{useTr("Unknown mode")}</option>
|
|
<option value={"conversation"}>{useTr("Conversation")}</option>
|
|
<option value={"description"}>{useTr("Channel description")}</option>
|
|
<option value={"file-transfer"}>{useTr("File browser")}</option>
|
|
<option value={"not-supported"} style={{ display: "none" }}>{useTr("Option not supported")}</option>
|
|
<option value={"loading"} style={{ display: "none" }}>{useTr("loading")}</option>
|
|
</Select>
|
|
</BlockProperty>
|
|
);
|
|
});
|
|
|
|
type SimpleCodecQualityTemplate = { id: string, codec: number, quality: number, name: () => string };
|
|
const kCodecTemplates: SimpleCodecQualityTemplate[] = [
|
|
{
|
|
id: "mobile",
|
|
codec: 4,
|
|
quality: 4,
|
|
name: () => useTr("Mobile")
|
|
},
|
|
{
|
|
id: "voice",
|
|
codec: 4,
|
|
quality: 6,
|
|
name: () => useTr("Voice")
|
|
},
|
|
{
|
|
id: "music",
|
|
codec: 5,
|
|
quality: 6,
|
|
name: () => useTr("Music")
|
|
},
|
|
{
|
|
id: "loading",
|
|
codec: undefined,
|
|
quality: undefined,
|
|
name: () => useTr("loading")
|
|
}
|
|
];
|
|
|
|
const SimpleCodecQuality = React.memo(() => {
|
|
const { propertyValue, propertyState, setPropertyValue } = useProperty("codec");
|
|
const { opusMusic, opusVoice } = usePropertyPermission("codec", { opusVoice: false, opusMusic: false });
|
|
|
|
const value = kCodecTemplates.find(template => template.codec === propertyValue?.type && template.quality === propertyValue?.quality)?.id || "advanced";
|
|
const hasPermission = (codec: SimpleCodecQualityTemplate) => codec.codec === 4 ? opusMusic : codec.codec === 5 ? opusVoice : false;
|
|
|
|
return (
|
|
<BlockProperty>
|
|
<Translatable>Voice quality</Translatable>
|
|
<Select
|
|
type={"boxed"}
|
|
value={value}
|
|
disabled={propertyState !== "normal" || (!opusMusic || !opusVoice)}
|
|
onChange={event => {
|
|
const template = kCodecTemplates.find(template => template.id === event.target.value);
|
|
if(!template || !template.codec) {
|
|
return;
|
|
}
|
|
|
|
setPropertyValue({ type: template.codec, quality: template.quality });
|
|
}}
|
|
>
|
|
{
|
|
kCodecTemplates.map(template => (
|
|
<option style={{ display: template.id === "loading" ? "none" : undefined }} key={template.id} value={template.id} disabled={!hasPermission(template)}>{template.name()}</option>
|
|
))
|
|
}
|
|
<option style={{ display: "none" }} key={"advanced"} value={"advanced"}>{useTr("Custom (Advanced settings)")}</option>
|
|
</Select>
|
|
</BlockProperty>
|
|
);
|
|
});
|
|
|
|
const AdvancedCodecPresets = React.memo(() => {
|
|
const { propertyValue, propertyState, setPropertyValue } = useProperty("codec");
|
|
const { opusMusic, opusVoice } = usePropertyPermission("codec", { opusVoice: false, opusMusic: false });
|
|
|
|
const value = kCodecTemplates.find(template => template.codec === propertyValue?.type && template.quality === propertyValue?.quality)?.id || "advanced";
|
|
const hasPermission = (codec: SimpleCodecQualityTemplate) => codec.codec === 4 ? opusMusic : codec.codec === 5 ? opusVoice : false;
|
|
|
|
return (
|
|
<div className={cssStyle.radioSelect}>
|
|
{
|
|
kCodecTemplates.filter(template => !!template.codec).map(template => (
|
|
<RadioButton
|
|
key={template.id}
|
|
name={"advanced-codec"}
|
|
selected={template.id === value}
|
|
disabled={!hasPermission(template) || propertyState !== "normal"}
|
|
onChange={() => setPropertyValue({ quality: template.quality, type: template.codec})}
|
|
>
|
|
<div>{template.name()}</div>
|
|
</RadioButton>
|
|
))
|
|
}
|
|
|
|
<RadioButton key={"value"} name={"advanced-codec"} selected={value === "advanced"} onChange={() => {}} disabled={true}>
|
|
<div><Translatable>Custom</Translatable></div>
|
|
</RadioButton>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const CustomCodec = React.memo(() => {
|
|
const { propertyValue, propertyState, setPropertyValue } = useProperty("codec");
|
|
const { opusMusic, opusVoice } = usePropertyPermission("codec", { opusVoice: false, opusMusic: false });
|
|
|
|
return (
|
|
<BlockProperty>
|
|
<Translatable>Codec</Translatable>
|
|
<Select
|
|
type={"boxed"}
|
|
disabled={!opusVoice && !opusMusic || propertyState !== "normal"}
|
|
value={propertyValue?.type + "" || "loading"}
|
|
onChange={event => {
|
|
const value = parseInt(event.target.value);
|
|
if(!isNaN(value)) {
|
|
setPropertyValue({ type: value, quality: propertyValue.quality });
|
|
}
|
|
}}
|
|
>
|
|
<option value={"0"} style={{ display: "none" }}>{useTr("Unknown")}</option>
|
|
<option value={"4"}>{useTr("Opus Voice")}</option>
|
|
<option value={"5"}>{useTr("Opus Music")}</option>
|
|
<option value={"loading"} style={{ display: "none" }}>{useTr("loading")}</option>
|
|
</Select>
|
|
</BlockProperty>
|
|
);
|
|
});
|
|
|
|
const CustomQuality = React.memo(() => {
|
|
const { propertyValue, propertyState, setPropertyValue } = useProperty("codec");
|
|
const permissionGranted = usePropertyPermission("codecQuality", false);
|
|
|
|
const refQuality = useRef<HTMLDivElement>();
|
|
const refSlider = useRef<Slider>();
|
|
|
|
useEffect(() => {
|
|
if(propertyState === "normal") {
|
|
refSlider.current?.setState({ value: propertyValue.quality });
|
|
}
|
|
});
|
|
|
|
return (
|
|
<BlockProperty>
|
|
<div>
|
|
<Translatable>Quality:</Translatable>
|
|
<div style={{ display: "inline" }} ref={refQuality}>{propertyValue?.quality}</div>
|
|
</div>
|
|
<Slider
|
|
ref={refSlider}
|
|
minValue={1}
|
|
maxValue={10}
|
|
stepSize={1}
|
|
value={propertyValue?.quality || 1}
|
|
onInput={newValue => {
|
|
if(refQuality.current) {
|
|
refQuality.current.innerText = newValue + "";
|
|
}
|
|
}}
|
|
onChange={newValue => {
|
|
setPropertyValue({ quality: newValue, type: propertyValue.type });
|
|
}}
|
|
disabled={propertyState !== "normal" || !permissionGranted}
|
|
/>
|
|
</BlockProperty>
|
|
)
|
|
});
|
|
|
|
const kBandwidthInfo = [
|
|
/* SPEEX narrow */ [2.49, 2.69, 2.93, 3.17, 3.17, 3.56, 3.56, 4.05, 4.05, 4.44, 5.22],
|
|
/* SPEEX wide */ [2.69, 2.93, 3.17, 3.42, 3.76, 4.25, 4.74, 5.13, 5.62, 6.40, 7.37],
|
|
/* SPEEX ultra */ [2.73, 3.12, 3.37, 3.61, 4.00, 4.49, 4.93, 5.32, 5.81, 6.59, 7.57],
|
|
/* CELT */ [6.10, 6.10, 7.08, 7.08, 7.08, 8.06, 8.06, 8.06, 8.06, 10.01, 13.92],
|
|
|
|
/* Opus Voice */ [2.73, 3.22, 3.71, 4.20, 4.74, 5.22, 5.71, 6.20, 6.74, 7.23, 7.71],
|
|
/* Opus Music */ [3.08, 3.96, 4.83, 5.71, 6.59, 7.47, 8.35, 9.23, 10.11, 10.99, 11.87]
|
|
];
|
|
|
|
const CodecInfo = React.memo(() => {
|
|
const { propertyValue, propertyState } = useProperty("codec");
|
|
|
|
return (
|
|
<div className={cssStyle.codecInfo}>
|
|
<p>
|
|
Estimated needed bandwidth: <a>{propertyState !== "normal" ? tr("loading") : ((kBandwidthInfo[propertyValue.type] || [])[propertyValue.quality] || 0) + "KiB/s"}</a>
|
|
</p>
|
|
<p className={cssStyle.hint}>
|
|
For bad internet connection, lower settings are recommend to reduce bandwidth.
|
|
</p>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const SortOrder = React.memo(() => {
|
|
const mode = useContext(ModalTypeContext);
|
|
const { propertyValue, propertyState, setPropertyValue } = useProperty("sortingOrder");
|
|
const permissionGranted = usePropertyPermission("sortingOrder", false);
|
|
|
|
let value;
|
|
if(propertyState === "loading") {
|
|
value = "loading";
|
|
} else if(propertyValue.previousChannelId === 0) {
|
|
if(mode === "channel-create") {
|
|
/* We automatically use the last channel */
|
|
value = propertyValue.availableChannels?.last()?.channelId || "0";
|
|
} else {
|
|
value = "0";
|
|
}
|
|
} else {
|
|
value = propertyValue.availableChannels?.find(channel => channel.channelId === propertyValue.previousChannelId) ? propertyValue.previousChannelId.toString() : "unknown";
|
|
}
|
|
|
|
return (
|
|
<BlockProperty>
|
|
<Translatable>Sort this channel after</Translatable>
|
|
<Select
|
|
type={"boxed"}
|
|
value={value}
|
|
disabled={propertyState !== "normal" || !permissionGranted}
|
|
onChange={event => {
|
|
const channelId = parseInt(event.target.value);
|
|
if(isNaN(channelId)) { return; }
|
|
|
|
setPropertyValue({ availableChannels: propertyValue?.availableChannels, previousChannelId: channelId });
|
|
}}
|
|
>
|
|
<option style={{ display: "none" }} key={"0"} value={"0"} />
|
|
{
|
|
propertyValue?.availableChannels.map(channel => (
|
|
<option key={channel.channelId} value={channel.channelId}>{channel.channelName}</option>
|
|
))
|
|
}
|
|
<option style={{ display: "none" }} key={"loading"} value={"loading"}>{useTr("loading")}</option>
|
|
<option style={{ display: "none" }} key={"unknown"} value={"unknown"}>{useTr("Unknown channel id")}: {propertyValue?.previousChannelId}</option>
|
|
</Select>
|
|
</BlockProperty>
|
|
);
|
|
});
|
|
|
|
const TalkPower = React.memo(() => {
|
|
const { propertyValue, propertyState, setPropertyValue } = useProperty("talkPower");
|
|
const permissionGranted = usePropertyPermission("talkPower", false);
|
|
|
|
|
|
return (
|
|
<BlockProperty>
|
|
|
|
<BoxedInputField
|
|
value={propertyValue === undefined ? "" : propertyValue + ""}
|
|
disabled={!permissionGranted || propertyState !== "normal"}
|
|
prefix={useTr("Talk power") + ":"}
|
|
rightIcon={() => (
|
|
<IconTooltip className={cssStyle.tooltip}>
|
|
<Translatable>Required power to talk in this channel</Translatable>
|
|
</IconTooltip>
|
|
)}
|
|
onInput={value => value.match(/^[0-9]{0,9}$/) && setPropertyValue(value ? parseInt(value) : 0, true)}
|
|
onChange={value => value.match(/^[0-9]{0,9}$/) && setPropertyValue(value ? parseInt(value) : 0)}
|
|
/>
|
|
</BlockProperty>
|
|
);
|
|
});
|
|
|
|
const MaxClients = React.memo(() => {
|
|
const { propertyValue, setPropertyValue } = useProperty("maxUsers");
|
|
const permissionGranted = usePropertyPermission("maxUsers", false);
|
|
const channelType = useProperty("type");
|
|
|
|
let disabled = !permissionGranted || channelType.propertyValue?.type === "temporary" || channelType.propertyValue?.type === "default";
|
|
useEffect(() => {
|
|
if(channelType.propertyValue?.type === "temporary" || channelType.propertyValue?.type === "default") {
|
|
setPropertyValue("unlimited");
|
|
disabled = true;
|
|
}
|
|
});
|
|
|
|
return (
|
|
<div className={cssStyle.radioSelect}>
|
|
<RadioButton
|
|
name={"max-users"}
|
|
selected={propertyValue === "unlimited"}
|
|
onChange={() => setPropertyValue("unlimited")}
|
|
disabled={disabled}
|
|
>
|
|
<div>
|
|
<Translatable>Unlimited</Translatable>
|
|
</div>
|
|
</RadioButton>
|
|
<RadioButton
|
|
name={"max-users"}
|
|
selected={typeof propertyValue === "number"}
|
|
onChange={() => setPropertyValue(0)}
|
|
disabled={disabled}
|
|
>
|
|
<div>
|
|
<Translatable>Limited</Translatable>
|
|
</div>
|
|
<BoxedInputField
|
|
value={typeof propertyValue === "number" ? propertyValue + "" : "0"}
|
|
disabled={typeof propertyValue !== "number"}
|
|
className={cssStyle.limit}
|
|
rightIcon={() => (
|
|
<IconTooltip className={cssStyle.tooltip}>
|
|
<Translatable>Max users which could join the channel</Translatable>
|
|
</IconTooltip>
|
|
)}
|
|
size={"small"}
|
|
onInput={value => value.match(/^[0-9]{0,9}$/) && setPropertyValue(value ? parseInt(value) : 0, true)}
|
|
onChange={value => value.match(/^[0-9]{0,9}$/) && setPropertyValue(value ? parseInt(value) : 0)}
|
|
/>
|
|
</RadioButton>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const MaxFamilyClients = React.memo(() => {
|
|
const { propertyValue, setPropertyValue } = useProperty("maxFamilyUsers");
|
|
const permissionGranted = usePropertyPermission("maxFamilyUsers", false);
|
|
const channelType = useProperty("type");
|
|
|
|
let disabled = !permissionGranted || channelType.propertyValue?.type === "temporary" || channelType.propertyValue?.type === "default";
|
|
useEffect(() => {
|
|
if(channelType.propertyValue?.type === "temporary" || channelType.propertyValue?.type === "default") {
|
|
setPropertyValue("unlimited");
|
|
disabled = true;
|
|
}
|
|
});
|
|
|
|
return (
|
|
<div className={cssStyle.radioSelect}>
|
|
<RadioButton
|
|
name={"max-family-users"}
|
|
selected={propertyValue === "unlimited"}
|
|
onChange={() => setPropertyValue("unlimited")}
|
|
disabled={disabled}
|
|
>
|
|
<div>
|
|
<Translatable>Unlimited</Translatable>
|
|
</div>
|
|
</RadioButton>
|
|
<RadioButton
|
|
name={"max-family-users"}
|
|
selected={propertyValue === "inherited"}
|
|
onChange={() => setPropertyValue("inherited")}
|
|
disabled={disabled}
|
|
>
|
|
<div>
|
|
<Translatable>Inherited</Translatable>
|
|
</div>
|
|
</RadioButton>
|
|
<RadioButton
|
|
name={"max-family-users"}
|
|
selected={typeof propertyValue === "number"}
|
|
onChange={() => setPropertyValue(0)}
|
|
disabled={disabled}
|
|
>
|
|
<div>
|
|
<Translatable>Limited</Translatable>
|
|
</div>
|
|
<BoxedInputField
|
|
value={typeof propertyValue === "number" ? propertyValue + "" : "0"}
|
|
disabled={typeof propertyValue !== "number"}
|
|
className={cssStyle.limit}
|
|
rightIcon={() => (
|
|
<IconTooltip className={cssStyle.tooltip}>
|
|
<Translatable>Max users which could join the channel family</Translatable>
|
|
</IconTooltip>
|
|
)}
|
|
size={"small"}
|
|
onInput={value => value.match(/^[0-9]{0,9}$/) && setPropertyValue(value ? parseInt(value) : 0, true)}
|
|
onChange={value => value.match(/^[0-9]{0,9}$/) && setPropertyValue(value ? parseInt(value) : 0)}
|
|
/>
|
|
</RadioButton>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const DeleteDelay = React.memo(() => {
|
|
const { propertyValue, setPropertyValue, propertyState } = useProperty("deleteDelay");
|
|
const permission = usePropertyPermission("deleteDelay", { maxDelay: -1, editable: false });
|
|
|
|
return (
|
|
<BlockProperty>
|
|
<Translatable>Delete delay:</Translatable>
|
|
<div className={cssStyle.deleteDelay}>
|
|
<BoxedInputField
|
|
className={cssStyle.input}
|
|
size={"small"}
|
|
value={propertyValue + "" || "0"}
|
|
onInput={value => value.match(/^[0-9]{0,9}$/) && setPropertyValue(value ? parseInt(value) : 0, true)}
|
|
onChange={value => value.match(/^[0-9]{0,9}$/) && setPropertyValue(value ? parseInt(value) : 0)}
|
|
rightIcon={() => (
|
|
<IconTooltip className={cssStyle.tooltip}>
|
|
<Translatable>Time in seconds before the channel gets deleted when its empty.</Translatable>
|
|
</IconTooltip>
|
|
)}
|
|
disabled={propertyState !== "normal" || !permission.editable}
|
|
/>
|
|
<Button
|
|
color={"blue"}
|
|
type={"small"}
|
|
className={cssStyle.button + " " + (permission.maxDelay === -1 ? cssStyle.hidden : "")}
|
|
onClick={() => setPropertyValue(permission.maxDelay)}
|
|
>
|
|
<Translatable>Max</Translatable>
|
|
</Button>
|
|
</div>
|
|
</BlockProperty>
|
|
);
|
|
});
|
|
|
|
const EncryptVoiceData = React.memo(() => {
|
|
const { propertyValue, setPropertyValue, propertyState } = useProperty("encryptedVoiceData");
|
|
const permissionGranted = usePropertyPermission("encryptVoiceData", false);
|
|
|
|
let serverHint;
|
|
switch (propertyValue?.serverSetting) {
|
|
case "encrypted":
|
|
serverHint = (
|
|
<React.Fragment key={"encrypted"}>
|
|
(<Translatable>Overridden by the server with encrypted</Translatable>)
|
|
</React.Fragment>
|
|
);
|
|
break;
|
|
case "unencrypted":
|
|
serverHint = (
|
|
<React.Fragment key={"unencrypted"}>
|
|
(<Translatable>Overridden by the server with unencrypted</Translatable>)
|
|
</React.Fragment>
|
|
);
|
|
break;
|
|
}
|
|
|
|
return (
|
|
<BlockProperty>
|
|
<Translatable>Encrypt voice data:</Translatable>
|
|
<div className={cssStyle.encryptVoiceData}>
|
|
<Switch
|
|
className={cssStyle.switch}
|
|
disabled={propertyState !== "normal" || !permissionGranted}
|
|
value={!!propertyValue?.encrypted}
|
|
onChange={newValue => setPropertyValue({ encrypted: newValue, serverSetting: propertyValue.serverSetting })}
|
|
/>
|
|
{serverHint}
|
|
</div>
|
|
</BlockProperty>
|
|
);
|
|
});
|
|
|
|
const GeneralContainer = () => {
|
|
return (
|
|
<div className={cssStyle.containerGeneral}>
|
|
<div className={cssStyle.row}>
|
|
<ChannelName />
|
|
<ChannelIcon />
|
|
</div>
|
|
<div className={cssStyle.row}>
|
|
<ChannelPassword />
|
|
</div>
|
|
{
|
|
/*
|
|
<div className={cssStyle.row}>
|
|
<ChannelTopic />
|
|
</div>
|
|
*/
|
|
}
|
|
<div className={cssStyle.row}>
|
|
<ChannelDescription />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const TabAdvancedSettingsStandard = React.memo(() => {
|
|
return (
|
|
<div className={cssStyle.containerTab}>
|
|
<div className={cssStyle.top}>
|
|
<div className={cssStyle.left}>
|
|
<div className={cssStyle.header}><Translatable>Channel and Sidebar Type</Translatable></div>
|
|
<div className={cssStyle.content}>
|
|
<ChannelType />
|
|
<SidebarType />
|
|
</div>
|
|
</div>
|
|
<div className={cssStyle.right}>
|
|
<div className={cssStyle.header}><Translatable>Sorting and Talk power</Translatable></div>
|
|
<div className={cssStyle.content}>
|
|
<SortOrder />
|
|
<TalkPower />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className={cssStyle.bottom}>
|
|
<div className={cssStyle.left}>
|
|
<div className={cssStyle.header}><Translatable>Max users</Translatable></div>
|
|
<div className={cssStyle.content}>
|
|
<MaxClients />
|
|
</div>
|
|
</div>
|
|
<div className={cssStyle.right}>
|
|
<div className={cssStyle.header}><Translatable>Max family users</Translatable></div>
|
|
<div className={cssStyle.content}>
|
|
<MaxFamilyClients />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const TabAdvancedSettingsAudio = () => {
|
|
return (
|
|
<div className={cssStyle.containerTab}>
|
|
<div className={cssStyle.top}>
|
|
<div className={cssStyle.left}>
|
|
<div className={cssStyle.header}>
|
|
<Translatable>Presets</Translatable>
|
|
</div>
|
|
<div className={cssStyle.content}>
|
|
<AdvancedCodecPresets />
|
|
</div>
|
|
</div>
|
|
<div className={cssStyle.right}>
|
|
<div className={cssStyle.header}>
|
|
<Translatable>Custom Settings</Translatable>
|
|
</div>
|
|
<div className={cssStyle.content}>
|
|
<CustomCodec />
|
|
<CustomQuality />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className={cssStyle.bottom}>
|
|
<div className={cssStyle.codecInfoContainer}>
|
|
<div className={cssStyle.header}>
|
|
<Translatable>Information</Translatable>
|
|
</div>
|
|
<div className={cssStyle.content}>
|
|
<CodecInfo />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const PermissionName = (props: { permission: ChannelEditablePermissions }) => {
|
|
switch (props.permission) {
|
|
case "join": return <Translatable>Join</Translatable>;
|
|
case "view": return <Translatable>View</Translatable>;
|
|
case "view-description": return <Translatable>View description</Translatable>;
|
|
case "subscribe": return <Translatable>Subscribe</Translatable>;
|
|
case "modify": return <Translatable>Modify</Translatable>;
|
|
case "delete": return <Translatable>Delete</Translatable>;
|
|
|
|
case "browse": return <Translatable>Browse</Translatable>;
|
|
case "upload": return <Translatable>Upload</Translatable>;
|
|
case "download": return <Translatable>Download</Translatable>;
|
|
case "rename": return <Translatable>Rename</Translatable>;
|
|
case "directory-create": return <Translatable>Create directory</Translatable>;
|
|
case "file-delete": return <Translatable>Delete file</Translatable>;
|
|
|
|
default: return props.permission;
|
|
}
|
|
}
|
|
|
|
const PermissionTooltip = (props: { permission: ChannelEditablePermissions }) => {
|
|
switch (props.permission) {
|
|
case "join": return <Translatable>Required power to join this channel</Translatable>;
|
|
case "view": return <Translatable>Required power to see this channel</Translatable>;
|
|
case "view-description": return <Translatable>Required power to see the channel description</Translatable>;
|
|
case "subscribe": return <Translatable>Required power to subscribe to this channel</Translatable>;
|
|
case "modify": return <Translatable>Required power to modify this channel permissions</Translatable>;
|
|
case "delete": return <Translatable>Required power to delete this channel</Translatable>;
|
|
|
|
case "browse": return <Translatable>Required power to browse all files and directories</Translatable>;
|
|
case "upload": return <Translatable>Required power to upload files</Translatable>;
|
|
case "download": return <Translatable>Required power to download files</Translatable>;
|
|
case "rename": return <Translatable>Required power to rename files within this channel</Translatable>;
|
|
case "directory-create": return <Translatable>Required power to create a directory</Translatable>;
|
|
case "file-delete": return <Translatable>Required power to delete a directory or file</Translatable>;
|
|
|
|
default: return <VariadicTranslatable text={"Missing description for {}"}>{props.permission}</VariadicTranslatable>;
|
|
}
|
|
}
|
|
|
|
const PermissionRenderer = (props: { permission: ChannelEditablePermissions }) => {
|
|
const { permissionValue, setPermissionValue } = usePermission(props.permission);
|
|
|
|
const placeholderLoading = useTr("loading");
|
|
const placeholderNotSupported = useTr("Not supported");
|
|
|
|
return (
|
|
<BlockProperty>
|
|
<PermissionName permission={props.permission} />
|
|
<BoxedInputField
|
|
type={"number"}
|
|
className={cssStyle.input}
|
|
size={"small"}
|
|
value={permissionValue.state === "editable" || permissionValue.state === "readonly" || permissionValue.state === "applying" ? permissionValue.value.toString() : ""}
|
|
placeholder={permissionValue.state === "unsupported" ? placeholderNotSupported : permissionValue.state === "loading" ? placeholderLoading : ""}
|
|
disabled={permissionValue.state !== "editable"}
|
|
onInput={value => value.match(/^[0-9]{0,9}$/) && setPermissionValue(value ? parseInt(value) : 0, true)}
|
|
onChange={value => value.match(/^[0-9]{0,9}$/) && setPermissionValue(value ? parseInt(value) : 0)}
|
|
rightIcon={() => <IconTooltip className={cssStyle.tooltip}><PermissionTooltip permission={props.permission} /></IconTooltip>}
|
|
/>
|
|
</BlockProperty>
|
|
);
|
|
}
|
|
|
|
const PermissionTable = () => (
|
|
<React.Fragment>
|
|
<div className={cssStyle.left}>
|
|
<div className={cssStyle.header}>
|
|
<Translatable>Regular needed powers:</Translatable>
|
|
</div>
|
|
<div className={cssStyle.content}>
|
|
<PermissionRenderer permission={"join"} />
|
|
<PermissionRenderer permission={"view"} />
|
|
<PermissionRenderer permission={"view-description"} />
|
|
<PermissionRenderer permission={"subscribe"} />
|
|
<PermissionRenderer permission={"modify"} />
|
|
<PermissionRenderer permission={"delete"} />
|
|
</div>
|
|
</div>
|
|
<div className={cssStyle.right}>
|
|
<div className={cssStyle.header}>
|
|
<Translatable>File transfer needed powers:</Translatable>
|
|
</div>
|
|
<div className={cssStyle.content}>
|
|
<PermissionRenderer permission={"browse"} />
|
|
<PermissionRenderer permission={"upload"} />
|
|
<PermissionRenderer permission={"download"} />
|
|
<PermissionRenderer permission={"rename"} />
|
|
<PermissionRenderer permission={"directory-create"} />
|
|
<PermissionRenderer permission={"file-delete"} />
|
|
</div>
|
|
</div>
|
|
</React.Fragment>
|
|
);
|
|
|
|
const TabAdvancedSettingsPermissions = React.memo(() => {
|
|
const events = useContext(EventContext);
|
|
const [ state, setState ] = useState<ChannelEditPermissionsState>(() => {
|
|
events.fire("query_permissions");
|
|
return { state: "loading" };
|
|
});
|
|
|
|
events.reactUse("notify_permissions", event => setState(event.state));
|
|
|
|
let content;
|
|
switch (state.state) {
|
|
case "loading":
|
|
content = (
|
|
<div className={cssStyle.overlay} key={"loading"}>
|
|
<div className={cssStyle.text}><Translatable>loading</Translatable> <LoadingDots /></div>
|
|
</div>
|
|
);
|
|
break;
|
|
|
|
case "no-permissions":
|
|
content = (
|
|
<div className={cssStyle.overlay} key={"no-permissions"}>
|
|
<div className={cssStyle.text}>
|
|
<Translatable>You don't have permissions to see the channel permissions.</Translatable><br/>
|
|
<Translatable>Failed on permission</Translatable>: <code>{state.failedPermission}</code>
|
|
</div>
|
|
</div>
|
|
);
|
|
break;
|
|
|
|
case "error":
|
|
content = (
|
|
<div className={cssStyle.overlay} key={"error"}>
|
|
<div className={cssStyle.text + " " + cssStyle.error}>
|
|
<Translatable>Failed to fetch channel permissions:</Translatable><br />
|
|
{state.reason}
|
|
</div>
|
|
</div>
|
|
);
|
|
break;
|
|
|
|
case "editable":
|
|
content = <PermissionTable key={"table"} />;
|
|
break;
|
|
}
|
|
|
|
return (
|
|
<div className={cssStyle.containerTab + " " + cssStyle.containerPermissions}>
|
|
{content}
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const TabAdvancedSettingsMisc = () => {
|
|
|
|
return (
|
|
<div className={cssStyle.containerTab + " " + cssStyle.tabMisc}>
|
|
<div className={cssStyle.header}><Translatable>Other Settings</Translatable></div>
|
|
<DeleteDelay />
|
|
<EncryptVoiceData />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const ContainerAdvancedSettings = React.memo(() => {
|
|
return (
|
|
<Tab defaultTab={"standard"} permanentRender={true}>
|
|
<TabEntry id={"standard"}>
|
|
<Translatable>Standard</Translatable>
|
|
<TabAdvancedSettingsStandard />
|
|
</TabEntry>
|
|
<TabEntry id={"audio"}>
|
|
<Translatable>Audio</Translatable>
|
|
<TabAdvancedSettingsAudio />
|
|
</TabEntry>
|
|
<TabEntry id={"permissions"}>
|
|
<Translatable>Permissions</Translatable>
|
|
<TabAdvancedSettingsPermissions />
|
|
</TabEntry>
|
|
<TabEntry id={"misc"}>
|
|
<Translatable>Misc</Translatable>
|
|
<TabAdvancedSettingsMisc />
|
|
</TabEntry>
|
|
</Tab>
|
|
);
|
|
});
|
|
|
|
const ContainerSimpleSettings = React.memo(() => {
|
|
return (
|
|
<React.Fragment>
|
|
<div className={cssStyle.left}>
|
|
<div className={cssStyle.header}><Translatable>Channel Options</Translatable></div>
|
|
<div className={cssStyle.content}>
|
|
<ChannelType />
|
|
<SimpleCodecQuality />
|
|
</div>
|
|
</div>
|
|
<div className={cssStyle.right}>
|
|
<div className={cssStyle.header}><Translatable>Sorting and Talk power</Translatable></div>
|
|
<div className={cssStyle.content}>
|
|
<SortOrder />
|
|
<TalkPower />
|
|
</div>
|
|
</div>
|
|
</React.Fragment>
|
|
);
|
|
});
|
|
|
|
const SettingsContainer = React.memo(() => {
|
|
const [ advancedEnabled, setAdvancedEnabled ] = useState(settings.getValue(Settings.KEY_CHANNEL_EDIT_ADVANCED));
|
|
useEffect(() => settings.globalChangeListener(Settings.KEY_CHANNEL_EDIT_ADVANCED, newValue => setAdvancedEnabled(newValue)));
|
|
|
|
return (
|
|
<div className={cssStyle.containerSettings}>
|
|
<div className={cssStyle.settingsAdvanced + " " + (advancedEnabled ? "" : cssStyle.hidden)}>
|
|
<ContainerAdvancedSettings />
|
|
</div>
|
|
<div className={cssStyle.settingsSimple + " " + (!advancedEnabled ? "" : cssStyle.hidden)}>
|
|
<ContainerSimpleSettings />
|
|
</div>
|
|
</div>
|
|
)
|
|
});
|
|
|
|
const ButtonToggleAdvanced = React.memo(() => {
|
|
const [ advancedEnabled, setAdvancedEnabled ] = useState(settings.getValue(Settings.KEY_CHANNEL_EDIT_ADVANCED));
|
|
|
|
settings.globalChangeListener(Settings.KEY_CHANNEL_EDIT_ADVANCED, newValue => setAdvancedEnabled(newValue));
|
|
|
|
return (
|
|
<Switch
|
|
className={cssStyle.advancedSwitch}
|
|
value={advancedEnabled}
|
|
label={<Translatable>Advanced mode</Translatable>}
|
|
onChange={newState => settings.setValue(Settings.KEY_CHANNEL_EDIT_ADVANCED, newState)}
|
|
/>
|
|
)
|
|
});
|
|
|
|
const Buttons = React.memo(() => {
|
|
const type = useContext(ModalTypeContext);
|
|
const events = useContext(EventContext);
|
|
|
|
return (
|
|
<div className={cssStyle.buttons}>
|
|
<ButtonToggleAdvanced />
|
|
<div className={cssStyle.cancelCreate}>
|
|
<Button type={"small"} color={"red"} onClick={() => events.fire("action_cancel")}><Translatable>Cancel</Translatable></Button>
|
|
<Button type={"small"} color={"green"} onClick={() => events.fire("action_apply")}>
|
|
{type === "channel-create" ? <Translatable>Create</Translatable> : <Translatable>Apply</Translatable>}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
});
|
|
|
|
class ChannelEditModal extends AbstractModal {
|
|
private readonly events: Registry<ChannelEditEvents>;
|
|
private readonly isChannelCreate: boolean;
|
|
|
|
constructor(events: IpcRegistryDescription<ChannelEditEvents>, isChannelCreate: boolean) {
|
|
super();
|
|
this.events = Registry.fromIpcDescription(events);
|
|
this.isChannelCreate = isChannelCreate;
|
|
}
|
|
|
|
renderBody(): React.ReactElement {
|
|
return (
|
|
<EventContext.Provider value={this.events}>
|
|
<ModalTypeContext.Provider value={this.isChannelCreate ? "channel-create" : "channel-edit"}>
|
|
<div className={cssStyle.container}>
|
|
<GeneralContainer />
|
|
<SettingsContainer />
|
|
<Buttons />
|
|
</div>
|
|
</ModalTypeContext.Provider>
|
|
</EventContext.Provider>
|
|
);
|
|
}
|
|
|
|
renderTitle(): string | React.ReactElement {
|
|
if(this.isChannelCreate) {
|
|
return <Translatable key={"create"}>Create channel</Translatable>;
|
|
} else {
|
|
/* TODO: Channel name? */
|
|
return <Translatable key={"edit"}>Edit channel</Translatable>;
|
|
}
|
|
}
|
|
|
|
color(): "none" | "blue" {
|
|
return "blue";
|
|
}
|
|
}
|
|
|
|
export = ChannelEditModal; |