Adding a setting editor
This commit is contained in:
parent
2a8027ec23
commit
3771f40625
13 changed files with 568 additions and 24 deletions
|
@ -4,7 +4,6 @@ import {Sound} from "../sound/Sounds";
|
||||||
import {ConnectionHandler} from "../ConnectionHandler";
|
import {ConnectionHandler} from "../ConnectionHandler";
|
||||||
import {server_connections} from "../ui/frames/connection_handlers";
|
import {server_connections} from "../ui/frames/connection_handlers";
|
||||||
import {createErrorModal, createInfoModal, createInputModal} from "../ui/elements/Modal";
|
import {createErrorModal, createInfoModal, createInputModal} from "../ui/elements/Modal";
|
||||||
import {settings} from "../settings";
|
|
||||||
import {spawnConnectModal} from "../ui/modal/ModalConnect";
|
import {spawnConnectModal} from "../ui/modal/ModalConnect";
|
||||||
import PermissionType from "../permission/PermissionType";
|
import PermissionType from "../permission/PermissionType";
|
||||||
import {spawnQueryCreate} from "../ui/modal/ModalQuery";
|
import {spawnQueryCreate} from "../ui/modal/ModalQuery";
|
||||||
|
@ -14,6 +13,8 @@ import {CommandResult} from "../connection/ServerConnectionDeclaration";
|
||||||
import {spawnSettingsModal} from "../ui/modal/ModalSettings";
|
import {spawnSettingsModal} from "../ui/modal/ModalSettings";
|
||||||
import {spawnPermissionEditorModal} from "../ui/modal/permission/ModalPermissionEditor";
|
import {spawnPermissionEditorModal} from "../ui/modal/permission/ModalPermissionEditor";
|
||||||
import {tr} from "../i18n/localize";
|
import {tr} from "../i18n/localize";
|
||||||
|
import {spawnGlobalSettingsEditor} from "tc-shared/ui/modal/global-settings-editor/Controller";
|
||||||
|
import {spawnModalCssVariableEditor} from "tc-shared/ui/modal/css-editor/Controller";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
function initialize_sounds(event_registry: Registry<ClientGlobalControlEvents>) {
|
function initialize_sounds(event_registry: Registry<ClientGlobalControlEvents>) {
|
||||||
|
@ -137,10 +138,18 @@ export function initialize(event_registry: Registry<ClientGlobalControlEvents>)
|
||||||
}).open();
|
}).open();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "css-variable-editor":
|
||||||
|
spawnModalCssVariableEditor();
|
||||||
|
break;
|
||||||
|
|
||||||
case "settings":
|
case "settings":
|
||||||
spawnSettingsModal();
|
spawnSettingsModal();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "settings-registry":
|
||||||
|
spawnGlobalSettingsEditor();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn(tr("Received open window event for an unknown window: %s"), event.window);
|
console.warn(tr("Received open window event for an unknown window: %s"), event.window);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ export interface ClientGlobalControlEvents {
|
||||||
action_open_window: {
|
action_open_window: {
|
||||||
window:
|
window:
|
||||||
"settings" | /* use action_open_window_settings! */
|
"settings" | /* use action_open_window_settings! */
|
||||||
|
"settings-registry" |
|
||||||
|
"css-variable-editor" |
|
||||||
"bookmark-manage" |
|
"bookmark-manage" |
|
||||||
"query-manage" |
|
"query-manage" |
|
||||||
"query-create" |
|
"query-create" |
|
||||||
|
|
|
@ -46,6 +46,7 @@ import "./profiles/ConnectionProfile";
|
||||||
import "./update/UpdaterWeb";
|
import "./update/UpdaterWeb";
|
||||||
import ContextMenuEvent = JQuery.ContextMenuEvent;
|
import ContextMenuEvent = JQuery.ContextMenuEvent;
|
||||||
import {defaultConnectProfile, findConnectProfile} from "tc-shared/profiles/ConnectionProfile";
|
import {defaultConnectProfile, findConnectProfile} from "tc-shared/profiles/ConnectionProfile";
|
||||||
|
import {spawnGlobalSettingsEditor} from "tc-shared/ui/modal/global-settings-editor/Controller";
|
||||||
|
|
||||||
let preventWelcomeUI = false;
|
let preventWelcomeUI = false;
|
||||||
async function initialize() {
|
async function initialize() {
|
||||||
|
@ -151,8 +152,11 @@ export function handle_connect_request(properties: ConnectRequestData, connectio
|
||||||
function main() {
|
function main() {
|
||||||
/* initialize font */
|
/* initialize font */
|
||||||
{
|
{
|
||||||
const font = settings.static_global(Settings.KEY_FONT_SIZE, 14); //parseInt(getComputedStyle(document.body).fontSize)
|
const font = settings.static_global(Settings.KEY_FONT_SIZE);
|
||||||
$(document.body).css("font-size", font + "px");
|
$(document.body).css("font-size", font + "px");
|
||||||
|
settings.globalChangeListener(Settings.KEY_FONT_SIZE, value => {
|
||||||
|
$(document.body).css("font-size", value + "px");
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/* context menu prevent */
|
/* context menu prevent */
|
||||||
|
@ -308,6 +312,7 @@ function main() {
|
||||||
modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false));
|
modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spawnGlobalSettingsEditor();
|
||||||
//spawnVideoPopout(server_connections.active_connection(), "https://www.youtube.com/watch?v=9683D18fyvs");
|
//spawnVideoPopout(server_connections.active_connection(), "https://www.youtube.com/watch?v=9683D18fyvs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -439,7 +439,7 @@ export class Settings extends StaticSettings {
|
||||||
static readonly KEY_FONT_SIZE: ValuedSettingsKey<number> = {
|
static readonly KEY_FONT_SIZE: ValuedSettingsKey<number> = {
|
||||||
key: "font_size",
|
key: "font_size",
|
||||||
valueType: "number",
|
valueType: "number",
|
||||||
defaultValue: 14
|
defaultValue: 14 //parseInt(getComputedStyle(document.body).fontSize)
|
||||||
};
|
};
|
||||||
|
|
||||||
static readonly KEY_ICON_SIZE: ValuedSettingsKey<number> = {
|
static readonly KEY_ICON_SIZE: ValuedSettingsKey<number> = {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {control_bar_instance} from "../../ui/frames/control-bar";
|
||||||
import {icon_cache_loader, IconManager, LocalIcon} from "../../file/Icons";
|
import {icon_cache_loader, IconManager, LocalIcon} from "../../file/Icons";
|
||||||
import {spawnPermissionEditorModal} from "../../ui/modal/permission/ModalPermissionEditor";
|
import {spawnPermissionEditorModal} from "../../ui/modal/permission/ModalPermissionEditor";
|
||||||
import {spawnModalCssVariableEditor} from "../../ui/modal/css-editor/Controller";
|
import {spawnModalCssVariableEditor} from "../../ui/modal/css-editor/Controller";
|
||||||
|
import {global_client_actions} from "tc-shared/events/GlobalEvents";
|
||||||
|
|
||||||
export interface HRItem { }
|
export interface HRItem { }
|
||||||
|
|
||||||
|
@ -523,11 +524,16 @@ export function initialize() {
|
||||||
menu.append_hr();
|
menu.append_hr();
|
||||||
|
|
||||||
item = menu.append_item(tr("Modify CSS variables"));
|
item = menu.append_item(tr("Modify CSS variables"));
|
||||||
item.click(() => spawnModalCssVariableEditor());
|
item.click(() => global_client_actions.fire("action_open_window", { window: "css-variable-editor" }));
|
||||||
|
|
||||||
|
item = menu.append_item(tr("Open Registry"));
|
||||||
|
item.click(() => global_client_actions.fire("action_open_window", { window: "settings-registry" }));
|
||||||
|
|
||||||
|
menu.append_hr();
|
||||||
|
|
||||||
item = menu.append_item(tr("Settings"));
|
item = menu.append_item(tr("Settings"));
|
||||||
item.icon("client-settings");
|
item.icon("client-settings");
|
||||||
item.click(() => spawnSettingsModal());
|
item.click(() => global_client_actions.fire("action_open_window_settings"));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,8 +54,16 @@ export const ServerLogRenderer = (props: { events: Registry<ServerLogUIEvents>,
|
||||||
});
|
});
|
||||||
|
|
||||||
props.events.reactUse("notify_log_add", event => {
|
props.events.reactUse("notify_log_add", event => {
|
||||||
if(logs === "loading")
|
if(logs === "loading") {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(__build.mode === "debug") {
|
||||||
|
const index = logs.findIndex(e => e.uniqueId === event.event.uniqueId);
|
||||||
|
if(index !== -1) {
|
||||||
|
debugger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logs.push(event.event);
|
logs.push(event.event);
|
||||||
logs.splice(0, Math.max(0, logs.length - 100));
|
logs.splice(0, Math.max(0, logs.length - 100));
|
||||||
|
|
|
@ -103,17 +103,16 @@ function settings_general_application(container: JQuery, modal: Modal) {
|
||||||
const current_size = parseInt(getComputedStyle(document.body).fontSize); //settings.static_global(Settings.KEY_FONT_SIZE, 12);
|
const current_size = parseInt(getComputedStyle(document.body).fontSize); //settings.static_global(Settings.KEY_FONT_SIZE, 12);
|
||||||
const select = container.find(".option-font-size");
|
const select = container.find(".option-font-size");
|
||||||
|
|
||||||
if (select.find("option[value='" + current_size + "']").length)
|
if (select.find("option[value='" + current_size + "']").length) {
|
||||||
select.find("option[value='" + current_size + "']").prop("selected", true);
|
select.find("option[value='" + current_size + "']").prop("selected", true);
|
||||||
else
|
} else {
|
||||||
select.find("option[value='-1']").prop("selected", true);
|
select.find("option[value='-1']").prop("selected", true);
|
||||||
|
}
|
||||||
|
|
||||||
select.on('change', event => {
|
select.on('change', event => {
|
||||||
const value = parseInt(select.val() as string);
|
const value = parseInt(select.val() as string);
|
||||||
settings.changeGlobal(Settings.KEY_FONT_SIZE, value);
|
settings.changeGlobal(Settings.KEY_FONT_SIZE, value);
|
||||||
console.log("Changed font size to %dpx", value);
|
console.log("Changed font size to %dpx", value);
|
||||||
|
|
||||||
$(document.body).css("font-size", value + "px");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
80
shared/js/ui/modal/global-settings-editor/Controller.tsx
Normal file
80
shared/js/ui/modal/global-settings-editor/Controller.tsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import {spawnReactModal} from "tc-shared/ui/react-elements/Modal";
|
||||||
|
import {ModalGlobalSettingsEditor} from "tc-shared/ui/modal/global-settings-editor/Renderer";
|
||||||
|
import {Registry} from "tc-shared/events";
|
||||||
|
import {ModalGlobalSettingsEditorEvents, Setting} from "tc-shared/ui/modal/global-settings-editor/Definitions";
|
||||||
|
import {settings, Settings, SettingsKey} from "tc-shared/settings";
|
||||||
|
|
||||||
|
export function spawnGlobalSettingsEditor() {
|
||||||
|
const events = new Registry<ModalGlobalSettingsEditorEvents>();
|
||||||
|
initializeController(events);
|
||||||
|
|
||||||
|
const modal = spawnReactModal(ModalGlobalSettingsEditor, events);
|
||||||
|
modal.show();
|
||||||
|
modal.events.on("destroy", () => {
|
||||||
|
events.fire("notify_destroy");
|
||||||
|
events.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeController(events: Registry<ModalGlobalSettingsEditorEvents>) {
|
||||||
|
events.on("query_settings", () => {
|
||||||
|
const settingsList: Setting[] = [];
|
||||||
|
|
||||||
|
for(const key of Settings.KEYS) {
|
||||||
|
const setting = Settings[key] as SettingsKey<ConfigValueTypes>;
|
||||||
|
settingsList.push({
|
||||||
|
key: setting.key,
|
||||||
|
description: setting.description,
|
||||||
|
type: setting.valueType,
|
||||||
|
defaultValue: setting.defaultValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
events.fire_async("notify_settings", { settings: settingsList });
|
||||||
|
});
|
||||||
|
|
||||||
|
events.on("action_select_setting", event => {
|
||||||
|
events.fire("notify_selected_setting", { setting: event.setting });
|
||||||
|
});
|
||||||
|
|
||||||
|
events.on("query_setting", event => {
|
||||||
|
const setting = Settings.KEYS.map(setting => Settings[setting] as SettingsKey<ConfigValueTypes>).find(e => e.key === event.setting);
|
||||||
|
if(typeof setting === "undefined") {
|
||||||
|
events.fire("notify_setting", {
|
||||||
|
key: event.setting,
|
||||||
|
status: "not-found"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
events.fire("notify_setting", {
|
||||||
|
setting: event.setting,
|
||||||
|
status: "success",
|
||||||
|
info: {
|
||||||
|
key: setting.key,
|
||||||
|
description: setting.description,
|
||||||
|
type: setting.valueType,
|
||||||
|
defaultValue: setting.defaultValue
|
||||||
|
},
|
||||||
|
value: settings.global(setting)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
events.on("action_set_value", event => {
|
||||||
|
const setting = Settings.KEYS.map(setting => Settings[setting] as SettingsKey<ConfigValueTypes>).find(e => e.key === event.setting);
|
||||||
|
if(typeof setting === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the change will may already trigger a notify_setting_value, but just to ensure we're fiering it later as well */
|
||||||
|
settings.changeGlobal(setting, event.value);
|
||||||
|
|
||||||
|
events.fire_async("notify_setting_value", { setting: event.setting, value: event.value });
|
||||||
|
});
|
||||||
|
|
||||||
|
events.on("notify_destroy", settings.events.on("notify_setting_changed", event => {
|
||||||
|
if(event.mode === "global") {
|
||||||
|
events.fire_async("notify_setting_value", { setting: event.setting, value: event.newValue });
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
36
shared/js/ui/modal/global-settings-editor/Definitions.ts
Normal file
36
shared/js/ui/modal/global-settings-editor/Definitions.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
export interface Setting {
|
||||||
|
key: string;
|
||||||
|
|
||||||
|
type: ConfigValueTypeNames;
|
||||||
|
|
||||||
|
description: string | undefined;
|
||||||
|
defaultValue: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModalGlobalSettingsEditorEvents {
|
||||||
|
action_select_setting: { setting: string }
|
||||||
|
action_set_filter: { filter: string },
|
||||||
|
action_set_value: { setting: string, value: string }
|
||||||
|
|
||||||
|
query_settings: {},
|
||||||
|
query_setting: { setting: string }
|
||||||
|
|
||||||
|
notify_settings: {
|
||||||
|
settings: Setting[]
|
||||||
|
},
|
||||||
|
notify_setting: {
|
||||||
|
setting: string,
|
||||||
|
status: "success" | "not-found",
|
||||||
|
|
||||||
|
info?: Setting,
|
||||||
|
value?: string
|
||||||
|
},
|
||||||
|
notify_selected_setting: {
|
||||||
|
setting: string
|
||||||
|
},
|
||||||
|
notify_setting_value: {
|
||||||
|
setting: string,
|
||||||
|
value: string
|
||||||
|
},
|
||||||
|
notify_destroy: {}
|
||||||
|
}
|
198
shared/js/ui/modal/global-settings-editor/Renderer.scss
Normal file
198
shared/js/ui/modal/global-settings-editor/Renderer.scss
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
@import "../../../../css/static/mixin";
|
||||||
|
@import "../../../../css/static/properties";
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
padding: .5em;
|
||||||
|
|
||||||
|
width: 50em;
|
||||||
|
max-width: 50em;
|
||||||
|
min-width: 20em;
|
||||||
|
|
||||||
|
height: 30em;
|
||||||
|
|
||||||
|
@include user-select(none);
|
||||||
|
|
||||||
|
.subContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
/* allocate as much space as we can get */
|
||||||
|
width: 100vw;
|
||||||
|
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
color: #e0e0e0;
|
||||||
|
|
||||||
|
@include text-dotdotdot();
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
min-height: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.containerList {
|
||||||
|
max-width: 20em;
|
||||||
|
min-width: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.containerEdit {
|
||||||
|
min-width: 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
margin-right: 1em;
|
||||||
|
min-height: 6.5em;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
border: 1px #161616 solid;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
background-color: #28292b;
|
||||||
|
|
||||||
|
.entries {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
min-height: 3em;
|
||||||
|
|
||||||
|
@include chat-scrollbar-vertical();
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
padding-left: .5em;
|
||||||
|
padding-right: .5em;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
height: 1.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #2c2d2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: #1a1a1b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter {
|
||||||
|
border-top: 1px #161616 solid;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
.input {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: .5em 1em;
|
||||||
|
|
||||||
|
min-width: 3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
z-index: 1;
|
||||||
|
background-color: #28292b;
|
||||||
|
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&.shown {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
.info {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--modal-query-key);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
user-select: text;
|
||||||
|
@include text-dotdotdot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoDescription {
|
||||||
|
.value {
|
||||||
|
white-space: pre-wrap!important;
|
||||||
|
height: 3.2em!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoValue {
|
||||||
|
.input {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
196
shared/js/ui/modal/global-settings-editor/Renderer.tsx
Normal file
196
shared/js/ui/modal/global-settings-editor/Renderer.tsx
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
||||||
|
import * as React from "react";
|
||||||
|
import {InternalModal} from "tc-shared/ui/react-elements/internal-modal/Controller";
|
||||||
|
import {createContext, useContext, useRef, useState} from "react";
|
||||||
|
import {Registry} from "tc-shared/events";
|
||||||
|
import {ModalGlobalSettingsEditorEvents, Setting} from "tc-shared/ui/modal/global-settings-editor/Definitions";
|
||||||
|
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
|
||||||
|
import {FlatInputField} from "tc-shared/ui/react-elements/InputField";
|
||||||
|
|
||||||
|
const ModalEvents = createContext<Registry<ModalGlobalSettingsEditorEvents>>(undefined);
|
||||||
|
const cssStyle = require("./Renderer.scss");
|
||||||
|
|
||||||
|
const SettingInfoRenderer = (props: { children: [React.ReactNode, React.ReactNode ], className?: string }) => (
|
||||||
|
<div className={cssStyle.info + " " + (props.className || "")}>
|
||||||
|
<div className={cssStyle.title}>{props.children[0]}</div>
|
||||||
|
<div className={cssStyle.value}>{props.children[1]}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const SettingEditor = () => {
|
||||||
|
const events = useContext(ModalEvents);
|
||||||
|
|
||||||
|
const [ isApplying, setApplying ] = useState(false);
|
||||||
|
const [ currentValue, setCurrentValue ] = useState<string>();
|
||||||
|
const [ currentSetting, setCurrentSetting ] = useState<Setting | "not-found">(false);
|
||||||
|
const currentSettingKey = useRef();
|
||||||
|
|
||||||
|
events.reactUse("notify_selected_setting", event => {
|
||||||
|
if(event.setting === currentSettingKey.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSettingKey.current = event.setting;
|
||||||
|
events.fire("query_setting", { setting: event.setting });
|
||||||
|
});
|
||||||
|
|
||||||
|
events.reactUse("notify_setting", event => {
|
||||||
|
if(event.setting !== currentSettingKey.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setApplying(false);
|
||||||
|
if(event.status === "not-found") {
|
||||||
|
setCurrentSetting("not-found");
|
||||||
|
} else {
|
||||||
|
setCurrentValue(event.value);
|
||||||
|
setCurrentSetting(event.info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
events.reactUse("action_set_value", event => {
|
||||||
|
if(event.setting !== currentSettingKey.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setApplying(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
events.reactUse("notify_setting_value", event => {
|
||||||
|
if(event.setting !== currentSettingKey.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setApplying(false);
|
||||||
|
setCurrentValue(event.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(currentSetting === "not-found") {
|
||||||
|
return null;
|
||||||
|
} else if(!currentSetting) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cssStyle.body + " " + cssStyle.editor}>
|
||||||
|
<SettingInfoRenderer>
|
||||||
|
<Translatable>Setting key</Translatable>
|
||||||
|
{currentSetting.key}
|
||||||
|
</SettingInfoRenderer>
|
||||||
|
<SettingInfoRenderer className={cssStyle.infoDescription}>
|
||||||
|
<Translatable>Description</Translatable>
|
||||||
|
{currentSetting.description}
|
||||||
|
</SettingInfoRenderer>
|
||||||
|
<SettingInfoRenderer className={cssStyle}>
|
||||||
|
<Translatable>Default Value</Translatable>
|
||||||
|
{typeof currentSetting.defaultValue !== "undefined" ? (currentSetting.defaultValue + "") : tr("unset")}
|
||||||
|
</SettingInfoRenderer>
|
||||||
|
<SettingInfoRenderer className={cssStyle.infoValue}>
|
||||||
|
<Translatable>Value</Translatable>
|
||||||
|
<FlatInputField
|
||||||
|
className={cssStyle.input}
|
||||||
|
value={isApplying ? "" : currentValue ? currentValue : " "}
|
||||||
|
editable={!isApplying}
|
||||||
|
placeholder={isApplying ? tr("applying...") : tr("setting unset")}
|
||||||
|
onChange={text => {
|
||||||
|
setCurrentValue(text);
|
||||||
|
}}
|
||||||
|
finishOnEnter={true}
|
||||||
|
onBlur={() => {
|
||||||
|
events.fire("action_set_value", { setting: currentSettingKey.current, value: currentValue });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingInfoRenderer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingEntryRenderer = (props: { setting: Setting, selected: boolean }) => {
|
||||||
|
const events = useContext(ModalEvents);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cssStyle.entry + " " + (props.selected ? cssStyle.selected : "")} onClick={() => events.fire("action_select_setting", { setting: props.setting.key })}>
|
||||||
|
{props.setting.key}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingList = () => {
|
||||||
|
const events = useContext(ModalEvents);
|
||||||
|
const [ settings, setSettings ] = useState<"loading" | Setting[]>(() => {
|
||||||
|
events.fire("query_settings");
|
||||||
|
return "loading";
|
||||||
|
});
|
||||||
|
const [ selectedSetting, setSelectedSetting ] = useState<string>(undefined);
|
||||||
|
const [ filter, setFilter ] = useState<string>(undefined);
|
||||||
|
|
||||||
|
events.reactUse("notify_settings", event => setSettings(event.settings));
|
||||||
|
events.reactUse("notify_selected_setting", event => setSelectedSetting(event.setting));
|
||||||
|
events.reactUse("action_set_filter", event => setFilter((event.filter || "").toLowerCase()));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cssStyle.body + " " + cssStyle.list}>
|
||||||
|
<div className={cssStyle.entries}>
|
||||||
|
{settings === "loading" ? undefined :
|
||||||
|
settings.map(setting => {
|
||||||
|
filterBlock:
|
||||||
|
if(filter) {
|
||||||
|
if(setting.key.toLowerCase().indexOf(filter) !== -1) {
|
||||||
|
break filterBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(setting.description && setting.description.toLowerCase().indexOf(filter) !== -1) {
|
||||||
|
break filterBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return <SettingEntryRenderer setting={setting} selected={setting.key === selectedSetting} key={setting.key} />;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className={cssStyle.filter}>
|
||||||
|
<FlatInputField className={cssStyle.input} onInput={text => events.fire("action_set_filter", { filter: text })} placeholder={tr("Filter settings")} />
|
||||||
|
</div>
|
||||||
|
<div className={cssStyle.overlay + " " + (settings === "loading" ? cssStyle.shown : "")}>
|
||||||
|
<a><Translatable>loading</Translatable> <LoadingDots /></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ModalGlobalSettingsEditor extends InternalModal {
|
||||||
|
protected readonly events: Registry<ModalGlobalSettingsEditorEvents>;
|
||||||
|
|
||||||
|
constructor(events: Registry<ModalGlobalSettingsEditorEvents>) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.events = events;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBody(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<ModalEvents.Provider value={this.events}>
|
||||||
|
<div className={cssStyle.container}>
|
||||||
|
<div className={cssStyle.subContainer + " " + cssStyle.containerList}>
|
||||||
|
<div className={cssStyle.header}>
|
||||||
|
<a><Translatable>Setting list</Translatable></a>
|
||||||
|
</div>
|
||||||
|
<SettingList />
|
||||||
|
</div>
|
||||||
|
<div className={cssStyle.subContainer + " " + cssStyle.containerEdit}>
|
||||||
|
<div className={cssStyle.header}>
|
||||||
|
<a><Translatable>Setting editor</Translatable></a>
|
||||||
|
</div>
|
||||||
|
<SettingEditor />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalEvents.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
title(): string | React.ReactElement<Translatable> {
|
||||||
|
return <Translatable>Global settings registry</Translatable>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -157,15 +157,15 @@ html:root {
|
||||||
.containerFlat {
|
.containerFlat {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
padding-top: 1.75rem; /* the label above (might be floating) */
|
padding-top: 1.75em; /* the label above (might be floating) */
|
||||||
margin-bottom: 1rem; /* for invalid label/help label */
|
margin-bottom: 1em; /* for invalid label/help label */
|
||||||
|
|
||||||
label {
|
label {
|
||||||
color: #999999;
|
color: #999999;
|
||||||
|
|
||||||
top: 1rem;
|
top: 1em;
|
||||||
left: 0;
|
left: 0;
|
||||||
font-size: .75rem;
|
font-size: .75em;
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -182,13 +182,13 @@ html:root {
|
||||||
&.type-floating {
|
&.type-floating {
|
||||||
will-change: left, top, contents;
|
will-change: left, top, contents;
|
||||||
color: #999999;
|
color: #999999;
|
||||||
top: 2.42rem;
|
top: 2.42em;
|
||||||
font-size: 1rem;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.type-static {
|
&.type-static {
|
||||||
top: 1rem;
|
top: 1em;
|
||||||
font-size: .75rem;
|
font-size: .75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include transition(color $button_hover_animation_time ease-in-out, top $button_hover_animation_time ease-in-out, font-size $button_hover_animation_time ease-in-out);
|
@include transition(color $button_hover_animation_time ease-in-out, top $button_hover_animation_time ease-in-out, font-size $button_hover_animation_time ease-in-out);
|
||||||
|
@ -199,8 +199,8 @@ html:root {
|
||||||
color: #3c74a2;
|
color: #3c74a2;
|
||||||
|
|
||||||
&.type-floating {
|
&.type-floating {
|
||||||
font-size: .75rem;
|
font-size: .75em;
|
||||||
top: 1rem;
|
top: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,7 @@ html:root {
|
||||||
height: 2.25em;
|
height: 2.25em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
font-size: 1rem;
|
font-size: 1em;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
|
||||||
color: #cdd1d0;
|
color: #cdd1d0;
|
||||||
|
@ -226,7 +226,7 @@ html:root {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
||||||
transition: background 0s ease-out;
|
transition: background 0s ease-out;
|
||||||
padding: .4375rem 0;
|
padding: .4375em 0;
|
||||||
|
|
||||||
@include transition(all .15s ease-in-out);
|
@include transition(all .15s ease-in-out);
|
||||||
|
|
||||||
|
@ -248,7 +248,7 @@ html:root {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: .25rem;
|
margin-top: .25em;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
color: #f44336;
|
color: #f44336;
|
||||||
|
|
||||||
|
@ -274,7 +274,7 @@ html:root {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: .25rem;
|
margin-top: .25em;
|
||||||
|
|
||||||
font-size: .75em;
|
font-size: .75em;
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,8 @@ export class BoxedInputField extends React.Component<BoxedInputFieldProperties,
|
||||||
|
|
||||||
export interface FlatInputFieldProperties {
|
export interface FlatInputFieldProperties {
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
|
value?: string;
|
||||||
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -165,6 +167,7 @@ export class FlatInputField extends React.Component<FlatInputFieldProperties, Fl
|
||||||
const disabled = typeof this.state.disabled === "boolean" ? this.state.disabled : typeof this.props.disabled === "boolean" ? this.props.disabled : false;
|
const disabled = typeof this.state.disabled === "boolean" ? this.state.disabled : typeof this.props.disabled === "boolean" ? this.props.disabled : false;
|
||||||
const readOnly = typeof this.state.editable === "boolean" ? !this.state.editable : typeof this.props.editable === "boolean" ? !this.props.editable : false;
|
const readOnly = typeof this.state.editable === "boolean" ? !this.state.editable : typeof this.props.editable === "boolean" ? !this.props.editable : false;
|
||||||
const placeholder = typeof this.state.placeholder === "string" ? this.state.placeholder : typeof this.props.placeholder === "string" ? this.props.placeholder : undefined;
|
const placeholder = typeof this.state.placeholder === "string" ? this.state.placeholder : typeof this.props.placeholder === "string" ? this.props.placeholder : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssStyle.containerFlat + " " + (this.state.isInvalid ? cssStyle.isInvalid : "") + " " + (this.state.filled ? cssStyle.isFilled : "") + " " + (this.props.className || "")}>
|
<div className={cssStyle.containerFlat + " " + (this.state.isInvalid ? cssStyle.isInvalid : "") + " " + (this.state.filled ? cssStyle.isFilled : "") + " " + (this.props.className || "")}>
|
||||||
{this.props.label ?
|
{this.props.label ?
|
||||||
|
@ -174,6 +177,8 @@ export class FlatInputField extends React.Component<FlatInputFieldProperties, Fl
|
||||||
(this.props.labelFloatingClassName && this.state.filled ? this.props.labelFloatingClassName : "")}>{this.props.label}</label> : undefined}
|
(this.props.labelFloatingClassName && this.state.filled ? this.props.labelFloatingClassName : "")}>{this.props.label}</label> : undefined}
|
||||||
<input
|
<input
|
||||||
defaultValue={this.props.defaultValue}
|
defaultValue={this.props.defaultValue}
|
||||||
|
value={this.props.value}
|
||||||
|
|
||||||
type={"text"}
|
type={"text"}
|
||||||
ref={this.refInput}
|
ref={this.refInput}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
|
|
Loading…
Add table
Reference in a new issue