Files
LiveSplitOne/src/ui/HotkeyButton.tsx
T
Christopher Serr d503c39c32 Format everything with prettier (#1029)
We didn't have Prettier until now, so this adds a configuration and
reformats everything with it.
2025-03-25 19:10:25 +00:00

207 lines
6.6 KiB
TypeScript

import * as React from "react";
import { Option, map, expect } from "../util/OptionUtil";
import { hotkeySystem } from "./LiveSplit";
import { Circle, Trash } from "lucide-react";
import "../css/HotkeyButton.scss";
function resolveKey(keyCode: string): Promise<string> | string {
return expect(
hotkeySystem,
"The Hotkey System should always be initialized.",
).resolve(keyCode);
}
export interface Props {
value: Option<string>;
setValue: (value: Option<string>) => void;
}
export interface State {
listener: Option<EventListenerObject>;
intervalHandle: Option<number>;
resolvedKey: Option<string>;
}
export default class HotkeyButton extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
listener: null,
intervalHandle: null,
resolvedKey: null,
};
this.updateResolvedKey(props.value);
}
public componentDidUpdate(previousProps: Props) {
if (previousProps.value !== this.props.value) {
this.updateResolvedKey(this.props.value);
}
}
private async updateResolvedKey(value: Option<string>): Promise<void> {
let resolvedKey = "";
if (value != null) {
const matches = value.match(/(.+)\+\s*(.+)$/);
if (matches != null) {
resolvedKey = `${matches[1]}+ ${await resolveKey(matches[2])}`;
} else {
resolvedKey = await resolveKey(value);
}
}
this.setState({ resolvedKey });
}
public render() {
let buttonText: Option<string> | React.JSX.Element = null;
if (this.props.value != null) {
buttonText = this.state.resolvedKey;
} else if (this.state.listener != null) {
buttonText = (
<Circle strokeWidth={0} size={16} fill="currentColor" />
);
}
return (
<div className="hotkey-box">
<button
className={`hotkey-button tooltip ${
this.state.listener != null ? "focused" : ""
}`}
onClick={() => this.focusButton()}
>
{buttonText}
<span className="tooltip-text">
Click to record a hotkey. You may also use buttons on a
gamepad. Global hotkeys are currently not possible.
Gamepad buttons work globally.
</span>
</button>
{map(this.props.value, () => (
<Trash
className="trash"
strokeWidth={2.5}
size={20}
onClick={() => this.props.setValue(null)}
/>
))}
{this.state.listener != null && (
<div
style={{
bottom: "0px",
left: "0px",
position: "fixed",
right: "0px",
top: "0px",
zIndex: 5,
}}
onClick={() => this.blurButton()}
/>
)}
</div>
);
}
private focusButton() {
let listener = this.state.listener;
let intervalHandle = this.state.intervalHandle;
if (listener === null) {
listener = {
handleEvent: (ev: KeyboardEvent) => {
if (ev.repeat) {
return;
}
let text = "";
if (
ev.ctrlKey &&
ev.code !== "ControlLeft" &&
ev.code !== "ControlRight"
) {
text += "Ctrl + ";
}
if (
ev.altKey &&
ev.code !== "AltLeft" &&
ev.code !== "AltRight"
) {
text += "Alt + ";
}
if (
ev.metaKey &&
ev.code !== "MetaLeft" &&
ev.code !== "MetaRight"
) {
text += "Meta + ";
}
if (
ev.shiftKey &&
ev.code !== "ShiftLeft" &&
ev.code !== "ShiftRight"
) {
text += "Shift + ";
}
text += ev.code;
this.props.setValue(text);
ev.preventDefault();
},
};
window.addEventListener("keydown", listener);
}
if (intervalHandle === null) {
const oldButtonState: boolean[][] = [];
intervalHandle = window.setInterval(() => {
const gamepads = navigator.getGamepads();
let gamepadIdx = 0;
for (const gamepad of gamepads) {
if (gamepadIdx >= oldButtonState.length) {
oldButtonState[gamepadIdx] = [];
}
if (gamepad !== null) {
let buttonIdx = 0;
for (const button of gamepad.buttons) {
const oldState =
oldButtonState[gamepadIdx]?.[buttonIdx] ??
false;
if (button.pressed && !oldState) {
this.props.setValue(`Gamepad${buttonIdx}`);
}
oldButtonState[gamepadIdx][buttonIdx] =
button.pressed;
buttonIdx++;
}
}
gamepadIdx++;
}
}, 1000 / 60.0);
}
this.setState({
listener,
intervalHandle,
});
}
private blurButton() {
if (this.state.listener != null) {
window.removeEventListener("keydown", this.state.listener);
}
if (this.state.intervalHandle != null) {
window.clearTimeout(this.state.intervalHandle);
}
this.setState({
listener: null,
intervalHandle: null,
});
}
}