2020-07-20 17:08:13 +00:00
|
|
|
import * as React from "react";
|
2020-09-12 13:49:20 +00:00
|
|
|
import {useState} from "react";
|
2021-01-22 15:50:55 +00:00
|
|
|
import {CssEditorEvents, CssVariable} from "tc-shared/ui/modal/css-editor/Definitions";
|
|
|
|
import {IpcRegistryDescription, Registry} from "tc-shared/events";
|
2020-07-20 17:08:13 +00:00
|
|
|
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
|
|
|
import {BoxedInputField, FlatInputField} from "tc-shared/ui/react-elements/InputField";
|
|
|
|
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
|
|
|
|
import {Checkbox} from "tc-shared/ui/react-elements/Checkbox";
|
|
|
|
import {Button} from "tc-shared/ui/react-elements/Button";
|
|
|
|
import {createErrorModal, createInfoModal} from "tc-shared/ui/elements/Modal";
|
2020-08-09 16:58:19 +00:00
|
|
|
import {AbstractModal} from "tc-shared/ui/react-elements/ModalDefinitions";
|
2021-03-14 18:39:08 +00:00
|
|
|
import {downloadTextAsFile, requestFileAsText} from "tc-shared/file/Utils";
|
2020-07-20 17:08:13 +00:00
|
|
|
|
|
|
|
const cssStyle = require("./Renderer.scss");
|
|
|
|
|
|
|
|
const CssVariableRenderer = React.memo((props: { events: Registry<CssEditorEvents>, variable: CssVariable, selected: boolean }) => {
|
2020-09-12 13:49:20 +00:00
|
|
|
const [selected, setSelected] = useState(props.selected);
|
|
|
|
const [override, setOverride] = useState(props.variable.overwriteValue);
|
|
|
|
const [overrideColor, setOverrideColor] = useState(props.variable.customValue);
|
2020-07-20 17:08:13 +00:00
|
|
|
|
|
|
|
props.events.reactUse("action_select_entry", event => setSelected(event.variable === props.variable));
|
|
|
|
props.events.reactUse("action_override_toggle", event => {
|
2020-09-12 13:49:20 +00:00
|
|
|
if (event.variableName !== props.variable.name)
|
2020-07-20 17:08:13 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
setOverride(event.enabled);
|
2020-09-12 13:49:20 +00:00
|
|
|
if (event.enabled)
|
2020-07-20 17:08:13 +00:00
|
|
|
setOverrideColor(event.value);
|
|
|
|
});
|
|
|
|
|
|
|
|
props.events.reactUse("action_change_override_value", event => {
|
2020-09-12 13:49:20 +00:00
|
|
|
if (event.variableName !== props.variable.name)
|
2020-07-20 17:08:13 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
setOverrideColor(event.value);
|
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
className={cssStyle.variable + " " + (selected ? cssStyle.selected : "")}
|
|
|
|
onClick={() => {
|
2020-09-12 13:49:20 +00:00
|
|
|
if (selected)
|
2020-07-20 17:08:13 +00:00
|
|
|
return;
|
|
|
|
|
2020-09-12 13:49:20 +00:00
|
|
|
props.events.fire("action_select_entry", {variable: props.variable})
|
2020-07-20 17:08:13 +00:00
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div className={cssStyle.preview}>
|
|
|
|
<div
|
|
|
|
className={cssStyle.color}
|
2020-09-12 13:49:20 +00:00
|
|
|
style={{backgroundColor: props.variable.defaultValue}}
|
2020-07-20 17:08:13 +00:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.preview}>
|
|
|
|
<div
|
|
|
|
className={cssStyle.color}
|
2020-09-12 13:49:20 +00:00
|
|
|
style={{backgroundColor: override ? overrideColor : undefined}}
|
2020-07-20 17:08:13 +00:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<a>{props.variable.name}</a>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
const CssVariableListBodyRenderer = (props: { events: Registry<CssEditorEvents> }) => {
|
2020-09-12 13:49:20 +00:00
|
|
|
const [variables, setVariables] = useState<"loading" | CssVariable[]>(() => {
|
2020-11-07 12:16:07 +00:00
|
|
|
props.events.fire_react("query_css_variables");
|
2020-07-20 17:08:13 +00:00
|
|
|
return "loading";
|
|
|
|
});
|
|
|
|
|
2020-09-12 13:49:20 +00:00
|
|
|
const [filter, setFilter] = useState(undefined);
|
|
|
|
const [selectedVariable, setSelectedVariable] = useState(undefined);
|
2020-07-20 17:08:13 +00:00
|
|
|
|
|
|
|
props.events.reactUse("action_select_entry", event => setSelectedVariable(event.variable));
|
|
|
|
props.events.reactUse("query_css_variables", () => setVariables("loading"));
|
|
|
|
|
|
|
|
let content;
|
2020-09-12 13:49:20 +00:00
|
|
|
if (variables === "loading") {
|
2020-07-20 17:08:13 +00:00
|
|
|
content = (
|
|
|
|
<div className={cssStyle.overlay} key={"loading"}>
|
|
|
|
<a>
|
|
|
|
<Translatable>Loading</Translatable>
|
2020-09-12 13:49:20 +00:00
|
|
|
<LoadingDots/>
|
2020-07-20 17:08:13 +00:00
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
content = [];
|
2020-09-12 13:49:20 +00:00
|
|
|
for (const variable of variables) {
|
|
|
|
if (filter && variable.name.toLowerCase().indexOf(filter) === -1)
|
2020-07-20 17:08:13 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
content.push(<CssVariableRenderer
|
|
|
|
key={"variable-" + variable.name}
|
|
|
|
events={props.events}
|
|
|
|
variable={variable}
|
|
|
|
selected={selectedVariable === variable.name}
|
|
|
|
/>);
|
|
|
|
}
|
|
|
|
|
2020-09-12 13:49:20 +00:00
|
|
|
if (content.length === 0) {
|
2020-07-20 17:08:13 +00:00
|
|
|
content.push(
|
|
|
|
<div className={cssStyle.overlay} key={"no-match"}>
|
|
|
|
<a><Translatable>No variable matched your filter</Translatable></a>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
props.events.reactUse("action_set_filter", event => setFilter(event.filter?.toLowerCase()));
|
|
|
|
props.events.reactUse("notify_css_variables", event => setVariables(event.variables));
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={cssStyle.body} onKeyPress={event => {
|
2020-09-12 13:49:20 +00:00
|
|
|
if (variables === "loading")
|
2020-07-20 17:08:13 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
/* TODO: This isn't working since the div isn't focused properly yet */
|
|
|
|
let offset = 0;
|
2020-09-12 13:49:20 +00:00
|
|
|
if (event.key === "ArrowDown") {
|
2020-07-20 17:08:13 +00:00
|
|
|
offset = 1;
|
2020-09-12 13:49:20 +00:00
|
|
|
} else if (event.key === "ArrowUp") {
|
2020-07-20 17:08:13 +00:00
|
|
|
offset = -1;
|
|
|
|
}
|
|
|
|
|
2020-09-12 13:49:20 +00:00
|
|
|
if (offset !== 0) {
|
2020-07-20 17:08:13 +00:00
|
|
|
const selectIndex = variables.findIndex(e => e === selectedVariable);
|
2020-09-12 13:49:20 +00:00
|
|
|
if (selectIndex === -1)
|
2020-07-20 17:08:13 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
const variable = variables[selectIndex + offset];
|
2020-09-12 13:49:20 +00:00
|
|
|
if (!variable)
|
2020-07-20 17:08:13 +00:00
|
|
|
return;
|
|
|
|
|
2020-09-12 13:49:20 +00:00
|
|
|
props.events.fire("action_select_entry", {variable: variable});
|
2020-07-20 17:08:13 +00:00
|
|
|
}
|
|
|
|
}} tabIndex={0}>
|
|
|
|
{content}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const CssVariableListSearchRenderer = (props: { events: Registry<CssEditorEvents> }) => {
|
2020-09-12 13:49:20 +00:00
|
|
|
const [isLoading, setLoading] = useState(true);
|
2020-07-20 17:08:13 +00:00
|
|
|
|
|
|
|
props.events.reactUse("notify_css_variables", () => setLoading(false));
|
|
|
|
props.events.reactUse("query_css_variables", () => setLoading(true));
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={cssStyle.search}>
|
|
|
|
<FlatInputField
|
|
|
|
label={<Translatable>Filter variables</Translatable>}
|
|
|
|
labelType={"floating"}
|
|
|
|
className={cssStyle.input}
|
2020-09-12 13:49:20 +00:00
|
|
|
onInput={text => props.events.fire("action_set_filter", {filter: text})}
|
2020-07-20 17:08:13 +00:00
|
|
|
disabled={isLoading}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const CssVariableListRenderer = (props: { events: Registry<CssEditorEvents> }) => (
|
|
|
|
<div className={cssStyle.containerList}>
|
|
|
|
<div className={cssStyle.header}>
|
|
|
|
<a><Translatable>CSS Variable list</Translatable></a>
|
|
|
|
</div>
|
2021-01-10 16:36:57 +00:00
|
|
|
<div className={cssStyle.list}>
|
2020-09-12 13:49:20 +00:00
|
|
|
<CssVariableListBodyRenderer events={props.events}/>
|
|
|
|
<CssVariableListSearchRenderer events={props.events}/>
|
2020-07-20 17:08:13 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
|
|
|
|
const SelectedVariableInfo = (props: { events: Registry<CssEditorEvents> }) => {
|
2020-09-12 13:49:20 +00:00
|
|
|
const [selectedVariable, setSelectedVariable] = useState<CssVariable>(undefined);
|
2020-07-20 17:08:13 +00:00
|
|
|
props.events.reactUse("action_select_entry", event => setSelectedVariable(event.variable));
|
|
|
|
|
|
|
|
return (<>
|
|
|
|
<div className={cssStyle.detail}>
|
|
|
|
<div className={cssStyle.title}>
|
|
|
|
<Translatable>Name</Translatable>
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.value}>
|
|
|
|
<BoxedInputField
|
|
|
|
editable={false}
|
|
|
|
value={selectedVariable ? selectedVariable.name : "-"}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.detail}>
|
|
|
|
<div className={cssStyle.title}>
|
|
|
|
<Translatable>Default Value</Translatable>
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.value}>
|
|
|
|
<BoxedInputField
|
|
|
|
editable={false}
|
|
|
|
value={selectedVariable ? selectedVariable.defaultValue : "-"}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</>);
|
|
|
|
};
|
|
|
|
|
|
|
|
const OverrideVariableInfo = (props: { events: Registry<CssEditorEvents> }) => {
|
2020-09-12 13:49:20 +00:00
|
|
|
const [selectedVariable, setSelectedVariable] = useState<CssVariable>(undefined);
|
|
|
|
const [overwriteValue, setOverwriteValue] = useState<string>(undefined);
|
|
|
|
const [overwriteEnabled, setOverwriteEnabled] = useState(false);
|
2020-07-20 17:08:13 +00:00
|
|
|
|
|
|
|
props.events.reactUse("action_select_entry", event => {
|
|
|
|
setSelectedVariable(event.variable);
|
|
|
|
setOverwriteEnabled(event.variable?.overwriteValue);
|
|
|
|
setOverwriteValue(event.variable?.customValue);
|
|
|
|
});
|
|
|
|
|
|
|
|
props.events.reactUse("action_override_toggle", event => {
|
2020-09-12 13:49:20 +00:00
|
|
|
if (event.variableName !== selectedVariable?.name)
|
2020-07-20 17:08:13 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
selectedVariable.overwriteValue = event.enabled;
|
|
|
|
setOverwriteEnabled(event.enabled);
|
2020-09-24 13:51:22 +00:00
|
|
|
if (event.enabled) {
|
2020-07-20 17:08:13 +00:00
|
|
|
setOverwriteValue(event.value);
|
2020-09-24 13:51:22 +00:00
|
|
|
}
|
|
|
|
}, true, [selectedVariable]);
|
2020-07-20 17:08:13 +00:00
|
|
|
|
|
|
|
props.events.reactUse("action_change_override_value", event => {
|
2020-09-12 13:49:20 +00:00
|
|
|
if (event.variableName !== selectedVariable?.name)
|
2020-07-20 17:08:13 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
setOverwriteValue(event.value);
|
2020-09-24 13:51:22 +00:00
|
|
|
}, true, [selectedVariable]);
|
2020-07-20 17:08:13 +00:00
|
|
|
|
|
|
|
return (<>
|
|
|
|
<div className={cssStyle.detail}>
|
|
|
|
<div className={cssStyle.title}>
|
|
|
|
<Translatable>Override Value</Translatable>
|
|
|
|
<Checkbox
|
|
|
|
value={overwriteEnabled}
|
|
|
|
disabled={!selectedVariable}
|
|
|
|
onChange={value => {
|
|
|
|
props.events.fire("action_override_toggle", {
|
|
|
|
variableName: selectedVariable.name,
|
|
|
|
value: typeof selectedVariable.customValue === "string" ? selectedVariable.customValue : selectedVariable.defaultValue,
|
|
|
|
enabled: value
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className={cssStyle.value + " " + cssStyle.color}>
|
|
|
|
<BoxedInputField
|
|
|
|
className={cssStyle.input}
|
|
|
|
disabled={!overwriteEnabled}
|
|
|
|
value={overwriteValue || " "}
|
|
|
|
onInput={text => {
|
|
|
|
selectedVariable.customValue = text;
|
2020-09-12 13:49:20 +00:00
|
|
|
props.events.fire("action_change_override_value", {
|
|
|
|
value: text,
|
|
|
|
variableName: selectedVariable.name
|
|
|
|
});
|
2020-07-20 17:08:13 +00:00
|
|
|
}}
|
|
|
|
/>
|
2020-09-12 13:49:20 +00:00
|
|
|
<CssVariableColorPicker events={props.events} selectedVariable={selectedVariable}/>
|
2020-07-20 17:08:13 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</>);
|
|
|
|
};
|
|
|
|
|
|
|
|
const CssVariableColorPicker = (props: { events: Registry<CssEditorEvents>, selectedVariable: CssVariable }) => {
|
2020-09-12 13:49:20 +00:00
|
|
|
const [overwriteValue, setOverwriteValue] = useState<string>(undefined);
|
|
|
|
const [overwriteEnabled, setOverwriteEnabled] = useState(false);
|
2020-07-20 17:08:13 +00:00
|
|
|
|
|
|
|
props.events.reactUse("action_override_toggle", event => {
|
2020-09-12 13:49:20 +00:00
|
|
|
if (event.variableName !== props.selectedVariable?.name)
|
2020-07-20 17:08:13 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
props.selectedVariable.overwriteValue = event.enabled;
|
|
|
|
setOverwriteEnabled(event.enabled);
|
2020-09-12 13:49:20 +00:00
|
|
|
if (event.enabled)
|
2020-07-20 17:08:13 +00:00
|
|
|
setOverwriteValue(event.value);
|
|
|
|
});
|
|
|
|
|
|
|
|
props.events.reactUse("action_change_override_value", event => {
|
2020-09-12 13:49:20 +00:00
|
|
|
if (event.variableName !== props.selectedVariable?.name || 'cpInvoker' in event)
|
2020-07-20 17:08:13 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
setOverwriteValue(event.value);
|
|
|
|
});
|
|
|
|
|
|
|
|
let currentInput: string;
|
|
|
|
let inputTimeout: number;
|
|
|
|
return (
|
2020-09-12 13:49:20 +00:00
|
|
|
<label className={cssStyle.colorButton}>
|
2020-07-20 17:08:13 +00:00
|
|
|
<input
|
|
|
|
disabled={!overwriteEnabled}
|
|
|
|
type={"color"}
|
|
|
|
value={overwriteValue}
|
|
|
|
onChange={event => {
|
|
|
|
currentInput = event.target.value;
|
2020-09-12 13:49:20 +00:00
|
|
|
if (inputTimeout)
|
2020-07-20 17:08:13 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
inputTimeout = setTimeout(() => {
|
|
|
|
inputTimeout = undefined;
|
2020-09-12 13:49:20 +00:00
|
|
|
props.events.fire("action_change_override_value", {
|
|
|
|
value: currentInput,
|
|
|
|
variableName: props.selectedVariable.name
|
|
|
|
});
|
2020-07-20 17:08:13 +00:00
|
|
|
}, 150);
|
|
|
|
}}
|
|
|
|
/>
|
2020-09-12 13:49:20 +00:00
|
|
|
<a className="rainbow-letter" style={{borderBottomColor: overwriteValue}}>C</a>
|
2020-07-20 17:08:13 +00:00
|
|
|
</label>
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
const ControlButtons = (props: { events: Registry<CssEditorEvents> }) => {
|
|
|
|
return (
|
|
|
|
<div className={cssStyle.buttons}>
|
|
|
|
<Button
|
|
|
|
color={"blue"}
|
|
|
|
type={"normal"}
|
|
|
|
className={cssStyle.button}
|
|
|
|
onClick={() => props.events.fire("action_randomize")}
|
|
|
|
><Translatable>Randomize</Translatable></Button>
|
|
|
|
<Button
|
|
|
|
color={"red"}
|
|
|
|
type={"normal"}
|
|
|
|
className={cssStyle.button + " " + cssStyle.buttonReset}
|
|
|
|
onClick={() => props.events.fire("action_reset")}
|
|
|
|
><Translatable>Reset</Translatable></Button>
|
|
|
|
<Button
|
|
|
|
color={"blue"}
|
|
|
|
type={"normal"}
|
|
|
|
className={cssStyle.button}
|
2020-09-12 13:49:20 +00:00
|
|
|
onClick={event => props.events.fire("action_export", {allValues: event.shiftKey})}
|
2020-08-23 09:05:55 +00:00
|
|
|
title={tr("Click to export the changed values, Shift click to export all values")}
|
2020-07-20 17:08:13 +00:00
|
|
|
><Translatable>Export</Translatable></Button>
|
|
|
|
<Button
|
|
|
|
color={"green"}
|
|
|
|
type={"normal"}
|
|
|
|
className={cssStyle.button}
|
|
|
|
onClick={() => requestFileAsText().then(content => {
|
2020-09-12 13:49:20 +00:00
|
|
|
props.events.fire("action_import", {config: content})
|
2020-07-20 17:08:13 +00:00
|
|
|
})}
|
|
|
|
><Translatable>Import</Translatable></Button>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
const CssVariableEditor = (props: { events: Registry<CssEditorEvents> }) => {
|
|
|
|
return (
|
|
|
|
<div className={cssStyle.containerEdit}>
|
|
|
|
<div className={cssStyle.header}>
|
|
|
|
<a><Translatable>Variable details</Translatable></a>
|
|
|
|
</div>
|
2020-09-12 13:49:20 +00:00
|
|
|
<SelectedVariableInfo events={props.events}/>
|
|
|
|
<OverrideVariableInfo events={props.events}/>
|
|
|
|
<ControlButtons events={props.events}/>
|
2020-07-20 17:08:13 +00:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
class PopoutConversationUI extends AbstractModal {
|
|
|
|
private readonly events: Registry<CssEditorEvents>;
|
|
|
|
|
2021-01-22 15:50:55 +00:00
|
|
|
constructor(events: IpcRegistryDescription<CssEditorEvents>) {
|
2020-07-20 17:08:13 +00:00
|
|
|
super();
|
|
|
|
|
2021-01-22 15:50:55 +00:00
|
|
|
this.events = Registry.fromIpcDescription(events);
|
2020-07-20 17:08:13 +00:00
|
|
|
this.events.on("notify_export_result", event => {
|
|
|
|
createInfoModal(tr("Config exported successfully"), tr("The config has been exported successfully.")).open();
|
|
|
|
downloadTextAsFile(event.config, "teaweb-style.json");
|
|
|
|
});
|
|
|
|
this.events.on("notify_import_result", event => {
|
2020-09-12 13:49:20 +00:00
|
|
|
if (event.success)
|
2020-07-20 17:08:13 +00:00
|
|
|
createInfoModal(tr("Config imported successfully"), tr("The config has been imported successfully.")).open();
|
|
|
|
else
|
|
|
|
createErrorModal(tr("Config imported failed"), tr("The config import has been failed.")).open();
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
renderBody() {
|
|
|
|
return (
|
|
|
|
<div className={cssStyle.container}>
|
2020-09-12 13:49:20 +00:00
|
|
|
<CssVariableListRenderer events={this.events}/>
|
|
|
|
<CssVariableEditor events={this.events}/>
|
2020-07-20 17:08:13 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-01-17 22:11:21 +00:00
|
|
|
renderTitle() {
|
2021-01-22 15:50:55 +00:00
|
|
|
return <Translatable>"CSS Variable editor"</Translatable>;
|
2020-07-20 17:08:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export = PopoutConversationUI;
|