2020-06-09 05:33:21 +03:00
|
|
|
/*
|
|
|
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import * as React from "react";
|
|
|
|
import { createRef } from "react";
|
|
|
|
import classNames from "classnames";
|
|
|
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
|
|
|
import { _t } from "../../languageHandler";
|
|
|
|
import { ActionPayload } from "../../dispatcher/payloads";
|
|
|
|
import { throttle } from 'lodash';
|
|
|
|
import { Key } from "../../Keyboard";
|
|
|
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
|
|
|
import { Action } from "../../dispatcher/actions";
|
|
|
|
|
|
|
|
interface IProps {
|
|
|
|
onQueryUpdate: (newQuery: string) => void;
|
2020-06-11 23:39:28 +03:00
|
|
|
isMinimized: boolean;
|
2020-07-03 00:21:10 +03:00
|
|
|
onVerticalArrow(ev: React.KeyboardEvent);
|
2020-07-12 21:06:47 +03:00
|
|
|
onEnter(ev: React.KeyboardEvent);
|
2020-06-09 05:33:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
interface IState {
|
|
|
|
query: string;
|
|
|
|
focused: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|
|
|
private dispatcherRef: string;
|
|
|
|
private inputRef: React.RefObject<HTMLInputElement> = createRef();
|
|
|
|
|
|
|
|
constructor(props: IProps) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
query: "",
|
|
|
|
focused: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
|
|
|
}
|
|
|
|
|
|
|
|
public componentWillUnmount() {
|
|
|
|
defaultDispatcher.unregister(this.dispatcherRef);
|
|
|
|
}
|
|
|
|
|
|
|
|
private onAction = (payload: ActionPayload) => {
|
|
|
|
if (payload.action === 'view_room' && payload.clear_search) {
|
|
|
|
this.clearInput();
|
|
|
|
} else if (payload.action === 'focus_room_filter' && this.inputRef.current) {
|
|
|
|
this.inputRef.current.focus();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private clearInput = () => {
|
|
|
|
if (!this.inputRef.current) return;
|
|
|
|
this.inputRef.current.value = "";
|
|
|
|
this.onChange();
|
|
|
|
};
|
|
|
|
|
2020-06-11 23:39:28 +03:00
|
|
|
private openSearch = () => {
|
|
|
|
defaultDispatcher.dispatch({action: "show_left_panel"});
|
2020-07-08 02:27:29 +03:00
|
|
|
defaultDispatcher.dispatch({action: "focus_room_filter"});
|
2020-06-11 23:39:28 +03:00
|
|
|
};
|
|
|
|
|
2020-06-09 05:33:21 +03:00
|
|
|
private onChange = () => {
|
|
|
|
if (!this.inputRef.current) return;
|
|
|
|
this.setState({query: this.inputRef.current.value});
|
|
|
|
this.onSearchUpdated();
|
|
|
|
};
|
|
|
|
|
|
|
|
// it wants this at the top of the file, but we know better
|
|
|
|
// tslint:disable-next-line
|
2020-06-09 16:58:39 +03:00
|
|
|
private onSearchUpdated = throttle(
|
|
|
|
() => {
|
2020-06-09 05:33:21 +03:00
|
|
|
// We can't use the state variable because it can lag behind the input.
|
|
|
|
// The lag is most obvious when deleting/clearing text with the keyboard.
|
|
|
|
this.props.onQueryUpdate(this.inputRef.current.value);
|
|
|
|
}, 200, {trailing: true, leading: true},
|
|
|
|
);
|
|
|
|
|
|
|
|
private onFocus = (ev: React.FocusEvent<HTMLInputElement>) => {
|
|
|
|
this.setState({focused: true});
|
|
|
|
ev.target.select();
|
|
|
|
};
|
|
|
|
|
2020-07-08 19:28:15 +03:00
|
|
|
private onBlur = (ev: React.FocusEvent<HTMLInputElement>) => {
|
2020-06-09 05:33:21 +03:00
|
|
|
this.setState({focused: false});
|
|
|
|
};
|
|
|
|
|
|
|
|
private onKeyDown = (ev: React.KeyboardEvent) => {
|
|
|
|
if (ev.key === Key.ESCAPE) {
|
|
|
|
this.clearInput();
|
|
|
|
defaultDispatcher.fire(Action.FocusComposer);
|
2020-07-03 00:21:10 +03:00
|
|
|
} else if (ev.key === Key.ARROW_UP || ev.key === Key.ARROW_DOWN) {
|
|
|
|
this.props.onVerticalArrow(ev);
|
2020-07-12 21:06:47 +03:00
|
|
|
} else if (ev.key === Key.ENTER) {
|
2020-07-16 08:31:06 +03:00
|
|
|
const shouldClear = this.props.onEnter(ev);
|
|
|
|
if (shouldClear) {
|
|
|
|
// wrap in set immediate to delay it so that we don't clear the filter & then change room
|
|
|
|
setImmediate(() => {
|
|
|
|
this.clearInput();
|
|
|
|
});
|
|
|
|
}
|
2020-06-09 05:33:21 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
public render(): React.ReactNode {
|
|
|
|
const classes = classNames({
|
|
|
|
'mx_RoomSearch': true,
|
|
|
|
'mx_RoomSearch_expanded': this.state.query || this.state.focused,
|
2020-06-11 23:39:28 +03:00
|
|
|
'mx_RoomSearch_minimized': this.props.isMinimized,
|
2020-06-09 05:33:21 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
const inputClasses = classNames({
|
|
|
|
'mx_RoomSearch_input': true,
|
|
|
|
'mx_RoomSearch_inputExpanded': this.state.query || this.state.focused,
|
|
|
|
});
|
|
|
|
|
2020-06-11 23:39:28 +03:00
|
|
|
let icon = (
|
|
|
|
<div className='mx_RoomSearch_icon'/>
|
|
|
|
);
|
|
|
|
let input = (
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
ref={this.inputRef}
|
|
|
|
className={inputClasses}
|
|
|
|
value={this.state.query}
|
|
|
|
onFocus={this.onFocus}
|
|
|
|
onBlur={this.onBlur}
|
|
|
|
onChange={this.onChange}
|
|
|
|
onKeyDown={this.onKeyDown}
|
|
|
|
placeholder={_t("Search")}
|
|
|
|
autoComplete="off"
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
let clearButton = (
|
|
|
|
<AccessibleButton
|
|
|
|
tabIndex={-1}
|
2020-07-05 03:07:46 +03:00
|
|
|
title={_t("Clear filter")}
|
|
|
|
className="mx_RoomSearch_clearButton"
|
2020-06-11 23:39:28 +03:00
|
|
|
onClick={this.clearInput}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
|
|
|
|
if (this.props.isMinimized) {
|
|
|
|
icon = (
|
2020-06-09 05:33:21 +03:00
|
|
|
<AccessibleButton
|
2020-07-05 03:07:46 +03:00
|
|
|
title={_t("Search rooms")}
|
|
|
|
className="mx_RoomSearch_icon"
|
2020-06-11 23:39:28 +03:00
|
|
|
onClick={this.openSearch}
|
2020-06-09 05:33:21 +03:00
|
|
|
/>
|
2020-06-11 23:39:28 +03:00
|
|
|
);
|
|
|
|
input = null;
|
|
|
|
clearButton = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={classes}>
|
|
|
|
{icon}
|
|
|
|
{input}
|
|
|
|
{clearButton}
|
2020-06-09 05:33:21 +03:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|