mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-23 12:31:07 +01:00
vmui: fix input cursor position reset (#6530)
### Describe Your Changes This PR addresses the issue where the cursor jumps to the end of the input fields in the modal settings window after each keystroke. ### Before fix: ![ezgif-7-4c69805cea](https://github.com/VictoriaMetrics/VictoriaMetrics/assets/29711459/2e99e833-09e3-4b44-89aa-fc1bd3c4346d) ### Checklist The following checks are **mandatory**: - [x] My change adheres [VictoriaMetrics contributing guidelines](https://docs.victoriametrics.com/contributing/).
This commit is contained in:
parent
6cab811134
commit
e9b71a2883
@ -1,22 +1,17 @@
|
||||
import React, { FC, useEffect, useState } from "preact/compat";
|
||||
import React, { FC, useRef } from "preact/compat";
|
||||
import ServerConfigurator from "./ServerConfigurator/ServerConfigurator";
|
||||
import { useAppDispatch, useAppState } from "../../../state/common/StateContext";
|
||||
import { ArrowDownIcon, SettingsIcon } from "../../Main/Icons";
|
||||
import Button from "../../Main/Button/Button";
|
||||
import Modal from "../../Main/Modal/Modal";
|
||||
import "./style.scss";
|
||||
import Tooltip from "../../Main/Tooltip/Tooltip";
|
||||
import LimitsConfigurator from "./LimitsConfigurator/LimitsConfigurator";
|
||||
import { Theme } from "../../../types";
|
||||
import { useCustomPanelDispatch, useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext";
|
||||
import { getAppModeEnable } from "../../../utils/app-mode";
|
||||
import classNames from "classnames";
|
||||
import Timezones from "./Timezones/Timezones";
|
||||
import { useTimeDispatch, useTimeState } from "../../../state/time/TimeStateContext";
|
||||
import ThemeControl from "../ThemeControl/ThemeControl";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import useBoolean from "../../../hooks/useBoolean";
|
||||
import { getTenantIdFromUrl } from "../../../utils/tenants";
|
||||
import { AppType } from "../../../types/appType";
|
||||
|
||||
const title = "Settings";
|
||||
@ -24,27 +19,18 @@ const title = "Settings";
|
||||
const { REACT_APP_TYPE } = process.env;
|
||||
const isLogsApp = REACT_APP_TYPE === AppType.logs;
|
||||
|
||||
export interface ChildComponentHandle {
|
||||
handleApply: () => void;
|
||||
}
|
||||
|
||||
const GlobalSettings: FC = () => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
|
||||
const appModeEnable = getAppModeEnable();
|
||||
const { serverUrl: stateServerUrl, theme } = useAppState();
|
||||
const { timezone: stateTimezone, defaultTimezone } = useTimeState();
|
||||
const { seriesLimits } = useCustomPanelState();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const timeDispatch = useTimeDispatch();
|
||||
const customPanelDispatch = useCustomPanelDispatch();
|
||||
|
||||
const [serverUrl, setServerUrl] = useState(stateServerUrl);
|
||||
const [limits, setLimits] = useState(seriesLimits);
|
||||
const [timezone, setTimezone] = useState(stateTimezone);
|
||||
|
||||
const setDefaultsValues = () => {
|
||||
setServerUrl(stateServerUrl);
|
||||
setLimits(seriesLimits);
|
||||
setTimezone(stateTimezone);
|
||||
};
|
||||
const serverSettingRef = useRef<ChildComponentHandle>(null);
|
||||
const limitsSettingRef = useRef<ChildComponentHandle>(null);
|
||||
const timezoneSettingRef = useRef<ChildComponentHandle>(null);
|
||||
|
||||
const {
|
||||
value: open,
|
||||
@ -52,68 +38,35 @@ const GlobalSettings: FC = () => {
|
||||
setFalse: handleClose,
|
||||
} = useBoolean(false);
|
||||
|
||||
const handleCloseAndReset = () => {
|
||||
handleClose();
|
||||
setDefaultsValues();
|
||||
};
|
||||
|
||||
const handleChangeTheme = (value: Theme) => {
|
||||
dispatch({ type: "SET_THEME", payload: value });
|
||||
};
|
||||
|
||||
const handlerApply = () => {
|
||||
const tenantIdFromUrl = getTenantIdFromUrl(serverUrl);
|
||||
if (tenantIdFromUrl !== "") {
|
||||
dispatch({ type: "SET_TENANT_ID", payload: tenantIdFromUrl });
|
||||
}
|
||||
dispatch({ type: "SET_SERVER", payload: serverUrl });
|
||||
timeDispatch({ type: "SET_TIMEZONE", payload: timezone });
|
||||
customPanelDispatch({ type: "SET_SERIES_LIMITS", payload: limits });
|
||||
const handleApply = () => {
|
||||
serverSettingRef.current && serverSettingRef.current.handleApply();
|
||||
limitsSettingRef.current && limitsSettingRef.current.handleApply();
|
||||
timezoneSettingRef.current && timezoneSettingRef.current.handleApply();
|
||||
handleClose();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// the tenant selector can change the serverUrl
|
||||
if (stateServerUrl === serverUrl) return;
|
||||
setServerUrl(stateServerUrl);
|
||||
}, [stateServerUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimezone(stateTimezone);
|
||||
}, [stateTimezone]);
|
||||
|
||||
const controls = [
|
||||
{
|
||||
show: !appModeEnable && !isLogsApp,
|
||||
component: <ServerConfigurator
|
||||
stateServerUrl={stateServerUrl}
|
||||
serverUrl={serverUrl}
|
||||
onChange={setServerUrl}
|
||||
onEnter={handlerApply}
|
||||
ref={serverSettingRef}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
},
|
||||
{
|
||||
show: !isLogsApp,
|
||||
component: <LimitsConfigurator
|
||||
limits={limits}
|
||||
onChange={setLimits}
|
||||
onEnter={handlerApply}
|
||||
ref={limitsSettingRef}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
},
|
||||
{
|
||||
show: true,
|
||||
component: <Timezones
|
||||
timezoneState={timezone}
|
||||
defaultTimezone={defaultTimezone}
|
||||
onChange={setTimezone}
|
||||
/>
|
||||
component: <Timezones ref={timezoneSettingRef}/>
|
||||
},
|
||||
{
|
||||
show: !appModeEnable,
|
||||
component: <ThemeControl
|
||||
theme={theme}
|
||||
onChange={handleChangeTheme}
|
||||
/>
|
||||
component: <ThemeControl/>
|
||||
}
|
||||
].filter(control => control.show);
|
||||
|
||||
@ -146,7 +99,7 @@ const GlobalSettings: FC = () => {
|
||||
{open && (
|
||||
<Modal
|
||||
title={title}
|
||||
onClose={handleCloseAndReset}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
@ -166,14 +119,14 @@ const GlobalSettings: FC = () => {
|
||||
<Button
|
||||
color="error"
|
||||
variant="outlined"
|
||||
onClick={handleCloseAndReset}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
onClick={handlerApply}
|
||||
onClick={handleApply}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { FC, useState } from "preact/compat";
|
||||
import { DisplayType, ErrorTypes, SeriesLimits } from "../../../../types";
|
||||
import React, { forwardRef, useCallback, useImperativeHandle, useState } from "preact/compat";
|
||||
import { DisplayType, ErrorTypes } from "../../../../types";
|
||||
import TextField from "../../../Main/TextField/TextField";
|
||||
import Tooltip from "../../../Main/Tooltip/Tooltip";
|
||||
import { InfoIcon, RestartIcon } from "../../../Main/Icons";
|
||||
@ -8,11 +8,11 @@ import { DEFAULT_MAX_SERIES } from "../../../../constants/graph";
|
||||
import "./style.scss";
|
||||
import classNames from "classnames";
|
||||
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
||||
import { ChildComponentHandle } from "../GlobalSettings";
|
||||
import { useCustomPanelDispatch, useCustomPanelState } from "../../../../state/customPanel/CustomPanelStateContext";
|
||||
|
||||
export interface ServerConfiguratorProps {
|
||||
limits: SeriesLimits
|
||||
onChange: (limits: SeriesLimits) => void
|
||||
onEnter: () => void
|
||||
interface ServerConfiguratorProps {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const fields: {label: string, type: DisplayType}[] = [
|
||||
@ -21,31 +21,38 @@ const fields: {label: string, type: DisplayType}[] = [
|
||||
{ label: "Table", type: DisplayType.table }
|
||||
];
|
||||
|
||||
const LimitsConfigurator: FC<ServerConfiguratorProps> = ({ limits, onChange , onEnter }) => {
|
||||
const LimitsConfigurator = forwardRef<ChildComponentHandle, ServerConfiguratorProps>(({ onClose }, ref) => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
|
||||
const { seriesLimits } = useCustomPanelState();
|
||||
const customPanelDispatch = useCustomPanelDispatch();
|
||||
|
||||
const [limits, setLimits] = useState(seriesLimits);
|
||||
const [error, setError] = useState({
|
||||
table: "",
|
||||
chart: "",
|
||||
code: ""
|
||||
});
|
||||
|
||||
const handleChange = (val: string, type: DisplayType) => {
|
||||
const handleReset = () => {
|
||||
setLimits(DEFAULT_MAX_SERIES);
|
||||
};
|
||||
|
||||
const createChangeHandler = (type: DisplayType) => (val: string) => {
|
||||
const value = val || "";
|
||||
setError(prev => ({ ...prev, [type]: +value < 0 ? ErrorTypes.positiveNumber : "" }));
|
||||
onChange({
|
||||
setLimits({
|
||||
...limits,
|
||||
[type]: !value ? Infinity : value
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
onChange(DEFAULT_MAX_SERIES);
|
||||
};
|
||||
const handleApply = useCallback(() => {
|
||||
customPanelDispatch({ type: "SET_SERIES_LIMITS", payload: limits });
|
||||
onClose();
|
||||
}, [limits]);
|
||||
|
||||
const createChangeHandler = (type: DisplayType) => (val: string) => {
|
||||
handleChange(val, type);
|
||||
};
|
||||
useImperativeHandle(ref, () => ({ handleApply }), [handleApply]);
|
||||
|
||||
return (
|
||||
<div className="vm-limits-configurator">
|
||||
@ -84,7 +91,7 @@ const LimitsConfigurator: FC<ServerConfiguratorProps> = ({ limits, onChange , on
|
||||
value={limits[f.type]}
|
||||
error={error[f.type]}
|
||||
onChange={createChangeHandler(f.type)}
|
||||
onEnter={onEnter}
|
||||
onEnter={handleApply}
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
@ -92,6 +99,6 @@ const LimitsConfigurator: FC<ServerConfiguratorProps> = ({ limits, onChange , on
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default LimitsConfigurator;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { FC, useEffect, useState } from "preact/compat";
|
||||
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "preact/compat";
|
||||
import { ErrorTypes } from "../../../../types";
|
||||
import TextField from "../../../Main/TextField/TextField";
|
||||
import { isValidHttpUrl } from "../../../../utils/url";
|
||||
@ -7,12 +7,12 @@ import { StorageIcon } from "../../../Main/Icons";
|
||||
import Tooltip from "../../../Main/Tooltip/Tooltip";
|
||||
import { getFromStorage, removeFromStorage, saveToStorage } from "../../../../utils/storage";
|
||||
import useBoolean from "../../../../hooks/useBoolean";
|
||||
import { ChildComponentHandle } from "../GlobalSettings";
|
||||
import { useAppDispatch, useAppState } from "../../../../state/common/StateContext";
|
||||
import { getTenantIdFromUrl } from "../../../../utils/tenants";
|
||||
|
||||
export interface ServerConfiguratorProps {
|
||||
serverUrl: string
|
||||
stateServerUrl: string
|
||||
onChange: (url: string) => void
|
||||
onEnter: () => void
|
||||
interface ServerConfiguratorProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const tooltipSave = {
|
||||
@ -20,24 +20,33 @@ const tooltipSave = {
|
||||
disable: "Disable to stop saving the server URL to local storage, reverting to the default URL on page refresh."
|
||||
};
|
||||
|
||||
const ServerConfigurator: FC<ServerConfiguratorProps> = ({
|
||||
serverUrl,
|
||||
stateServerUrl,
|
||||
onChange ,
|
||||
onEnter
|
||||
}) => {
|
||||
const ServerConfigurator = forwardRef<ChildComponentHandle, ServerConfiguratorProps>(({ onClose }, ref) => {
|
||||
const { serverUrl: stateServerUrl } = useAppState();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const {
|
||||
value: enabledStorage,
|
||||
toggle: handleToggleStorage,
|
||||
} = useBoolean(!!getFromStorage("SERVER_URL"));
|
||||
|
||||
const [serverUrl, setServerUrl] = useState(stateServerUrl);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const onChangeServer = (val: string) => {
|
||||
const handleChange = (val: string) => {
|
||||
const value = val || "";
|
||||
onChange(value);
|
||||
setServerUrl(value);
|
||||
setError("");
|
||||
};
|
||||
|
||||
const handleApply = useCallback(() => {
|
||||
const tenantIdFromUrl = getTenantIdFromUrl(serverUrl);
|
||||
if (tenantIdFromUrl !== "") {
|
||||
dispatch({ type: "SET_TENANT_ID", payload: tenantIdFromUrl });
|
||||
}
|
||||
dispatch({ type: "SET_SERVER", payload: serverUrl });
|
||||
onClose();
|
||||
}, [serverUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!stateServerUrl) setError(ErrorTypes.emptyServer);
|
||||
if (!isValidHttpUrl(stateServerUrl)) setError(ErrorTypes.validServer);
|
||||
@ -57,6 +66,14 @@ const ServerConfigurator: FC<ServerConfiguratorProps> = ({
|
||||
}
|
||||
}, [serverUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
// the tenant selector can change the serverUrl
|
||||
if (stateServerUrl === serverUrl) return;
|
||||
setServerUrl(stateServerUrl);
|
||||
}, [stateServerUrl]);
|
||||
|
||||
useImperativeHandle(ref, () => ({ handleApply }), [handleApply]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="vm-server-configurator__title">
|
||||
@ -67,8 +84,8 @@ const ServerConfigurator: FC<ServerConfiguratorProps> = ({
|
||||
autofocus
|
||||
value={serverUrl}
|
||||
error={error}
|
||||
onChange={onChangeServer}
|
||||
onEnter={onEnter}
|
||||
onChange={handleChange}
|
||||
onEnter={handleApply}
|
||||
inputmode="url"
|
||||
/>
|
||||
<Tooltip title={enabledStorage ? tooltipSave.disable : tooltipSave.enable}>
|
||||
@ -83,6 +100,6 @@ const ServerConfigurator: FC<ServerConfiguratorProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default ServerConfigurator;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { FC, useMemo, useRef, useState } from "preact/compat";
|
||||
import React, { FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "preact/compat";
|
||||
import { getBrowserTimezone, getTimezoneList, getUTCByTimezone } from "../../../../utils/time";
|
||||
import { ArrowDropDownIcon } from "../../../Main/Icons";
|
||||
import classNames from "classnames";
|
||||
@ -10,12 +10,7 @@ import "./style.scss";
|
||||
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
||||
import useBoolean from "../../../../hooks/useBoolean";
|
||||
import WarningTimezone from "./WarningTimezone";
|
||||
|
||||
interface TimezonesProps {
|
||||
timezoneState: string;
|
||||
defaultTimezone?: string;
|
||||
onChange: (val: string) => void;
|
||||
}
|
||||
import { useTimeDispatch, useTimeState } from "../../../../state/time/TimeStateContext";
|
||||
|
||||
interface PinnedTimezone extends Timezone {
|
||||
title: string;
|
||||
@ -24,10 +19,14 @@ interface PinnedTimezone extends Timezone {
|
||||
|
||||
const browserTimezone = getBrowserTimezone();
|
||||
|
||||
const Timezones: FC<TimezonesProps> = ({ timezoneState, defaultTimezone, onChange }) => {
|
||||
const Timezones: FC = forwardRef((props, ref) => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
const timezones = getTimezoneList();
|
||||
|
||||
const { timezone: stateTimezone, defaultTimezone } = useTimeState();
|
||||
const timeDispatch = useTimeDispatch();
|
||||
|
||||
const [timezone, setTimezone] = useState(stateTimezone);
|
||||
const [search, setSearch] = useState("");
|
||||
const targetRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -68,16 +67,16 @@ const Timezones: FC<TimezonesProps> = ({ timezoneState, defaultTimezone, onChang
|
||||
const timezonesGroups = useMemo(() => Object.keys(searchTimezones), [searchTimezones]);
|
||||
|
||||
const activeTimezone = useMemo(() => ({
|
||||
region: timezoneState,
|
||||
utc: getUTCByTimezone(timezoneState)
|
||||
}), [timezoneState]);
|
||||
region: timezone,
|
||||
utc: getUTCByTimezone(timezone)
|
||||
}), [timezone]);
|
||||
|
||||
const handleChangeSearch = (val: string) => {
|
||||
setSearch(val);
|
||||
};
|
||||
|
||||
const handleSetTimezone = (val: Timezone) => {
|
||||
onChange(val.region);
|
||||
setTimezone(val.region);
|
||||
setSearch("");
|
||||
handleCloseList();
|
||||
};
|
||||
@ -86,6 +85,16 @@ const Timezones: FC<TimezonesProps> = ({ timezoneState, defaultTimezone, onChang
|
||||
handleSetTimezone(val);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setTimezone(stateTimezone);
|
||||
}, [stateTimezone]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleApply: () => {
|
||||
timeDispatch({ type: "SET_TIMEZONE", payload: timezone });
|
||||
}
|
||||
}), [timezone]);
|
||||
|
||||
return (
|
||||
<div className="vm-timezones">
|
||||
<div className="vm-server-configurator__title">
|
||||
@ -169,6 +178,6 @@ const Timezones: FC<TimezonesProps> = ({ timezoneState, defaultTimezone, onChang
|
||||
</Popper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default Timezones;
|
||||
|
@ -5,18 +5,17 @@ import Toggle from "../../Main/Toggle/Toggle";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import classNames from "classnames";
|
||||
import { FC } from "preact/compat";
|
||||
|
||||
interface ThemeControlProps {
|
||||
theme: Theme;
|
||||
onChange: (val: Theme) => void
|
||||
}
|
||||
import { useAppDispatch, useAppState } from "../../../state/common/StateContext";
|
||||
|
||||
const options = Object.values(Theme).map(value => ({ title: value, value }));
|
||||
const ThemeControl: FC<ThemeControlProps> = ({ theme, onChange }) => {
|
||||
const ThemeControl: FC = () => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { theme } = useAppState();
|
||||
|
||||
const handleClickItem = (value: string) => {
|
||||
onChange(value as Theme);
|
||||
dispatch({ type: "SET_THEME", payload: value as Theme });
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -32,7 +32,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
|
||||
* FEATURE: [dashboards](https://grafana.com/orgs/victoriametrics): add [Grafana dashboard](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/dashboards/vmauth.json) and [alerting rules](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts-vmauth.yml) for [vmauth](https://docs.victoriametrics.com/vmauth/) dashboard. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4313) for details.
|
||||
|
||||
* BUGFIX: [docker-compose](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker#docker-compose-environment-for-victoriametrics): fix incorrect link to vmui from [VictoriaMetrics plugin in Grafana](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker#grafana).
|
||||
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix input cursor position reset in modal settings. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6530).
|
||||
|
||||
## [v1.102.0-rc2](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.0-rc2)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user