mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Extract business logic from component while trying to solve re-rendering issue
Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
06379b089b
commit
4e2995f979
@ -20,9 +20,12 @@
|
||||
*/
|
||||
import { NamespaceSelectFilterModel } from "./namespace-select-filter-model";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import namespaceStoreInjectable from "../namespace-store/namespace-store.injectable";
|
||||
|
||||
const NamespaceSelectFilterModelInjectable = getInjectable({
|
||||
instantiate: () => new NamespaceSelectFilterModel(),
|
||||
instantiate: (di) => new NamespaceSelectFilterModel({
|
||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
@ -18,17 +18,22 @@
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { observable, makeObservable, action } from "mobx";
|
||||
import { observable, makeObservable, action, untracked } from "mobx";
|
||||
import type { NamespaceStore } from "../namespace-store/namespace.store";
|
||||
import type { SelectOption } from "../../select";
|
||||
import { isMac } from "../../../../common/vars";
|
||||
|
||||
interface Dependencies {
|
||||
namespaceStore: NamespaceStore;
|
||||
}
|
||||
|
||||
export class NamespaceSelectFilterModel {
|
||||
constructor() {
|
||||
constructor(private dependencies: Dependencies) {
|
||||
makeObservable(this, {
|
||||
menuIsOpen: observable,
|
||||
closeMenu: action,
|
||||
openMenu: action,
|
||||
toggleMenu: action,
|
||||
isMultiSelection: observable,
|
||||
setIsMultiSelection: action,
|
||||
reset: action,
|
||||
});
|
||||
}
|
||||
|
||||
@ -42,13 +47,65 @@ export class NamespaceSelectFilterModel {
|
||||
this.menuIsOpen = true;
|
||||
};
|
||||
|
||||
toggleMenu = () => {
|
||||
this.menuIsOpen = !this.menuIsOpen;
|
||||
get selectedNames() {
|
||||
return untracked(() => this.dependencies.namespaceStore.selectedNames);
|
||||
}
|
||||
|
||||
isSelected = (namespace: string | string[]) =>
|
||||
this.dependencies.namespaceStore.hasContext(namespace);
|
||||
|
||||
selectSingle = (namespace: string) => {
|
||||
this.dependencies.namespaceStore.selectSingle(namespace);
|
||||
};
|
||||
|
||||
isMultiSelection = false;
|
||||
selectAll = () => {
|
||||
this.dependencies.namespaceStore.selectAll();
|
||||
};
|
||||
|
||||
setIsMultiSelection = (isMultiSelection: boolean) => {
|
||||
this.isMultiSelection = isMultiSelection;
|
||||
onChange = ([{ value: namespace }]: SelectOption[]) => {
|
||||
if (namespace) {
|
||||
if (this.isMultiSelection) {
|
||||
this.dependencies.namespaceStore.toggleSingle(namespace);
|
||||
} else {
|
||||
this.dependencies.namespaceStore.selectSingle(namespace);
|
||||
}
|
||||
} else {
|
||||
this.dependencies.namespaceStore.selectAll();
|
||||
}
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
if (!this.menuIsOpen) {
|
||||
this.openMenu();
|
||||
} else if (!this.isMultiSelection) {
|
||||
this.closeMenu();
|
||||
}
|
||||
};
|
||||
|
||||
private isMultiSelection = false;
|
||||
|
||||
onKeyDown = (event: React.KeyboardEvent) => {
|
||||
if (isSelectionKey(event)) {
|
||||
this.isMultiSelection = true;
|
||||
}
|
||||
};
|
||||
|
||||
onKeyUp = (event: React.KeyboardEvent) => {
|
||||
if (isSelectionKey(event)) {
|
||||
this.isMultiSelection = false;
|
||||
}
|
||||
};
|
||||
|
||||
reset = () => {
|
||||
this.isMultiSelection = false;
|
||||
this.closeMenu();
|
||||
};
|
||||
}
|
||||
|
||||
const isSelectionKey = (event: React.KeyboardEvent): boolean => {
|
||||
if (isMac) {
|
||||
return event.key === "Meta";
|
||||
}
|
||||
|
||||
return event.key === "Control"; // windows or linux
|
||||
};
|
||||
|
||||
@ -22,58 +22,61 @@
|
||||
import "./namespace-select-filter.scss";
|
||||
|
||||
import React from "react";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { observer } from "mobx-react";
|
||||
import { components, PlaceholderProps } from "react-select";
|
||||
import { action, makeObservable, observable, reaction } from "mobx";
|
||||
|
||||
import { Icon } from "../icon";
|
||||
import { NamespaceSelect } from "./namespace-select";
|
||||
import type { NamespaceStore } from "./namespace-store/namespace.store";
|
||||
|
||||
import type { SelectOption, SelectProps } from "../select";
|
||||
import { isMac } from "../../../common/vars";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
|
||||
import type { NamespaceSelectFilterModel } from "./namespace-select-filter-model/namespace-select-filter-model";
|
||||
import namespaceSelectFilterModelInjectable from "./namespace-select-filter-model/namespace-select-filter-model.injectable";
|
||||
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
model: NamespaceSelectFilterModel,
|
||||
namespaceStore: NamespaceStore
|
||||
model: NamespaceSelectFilterModel;
|
||||
}
|
||||
|
||||
@observer
|
||||
class NonInjectedNamespaceSelectFilter extends React.Component<SelectProps & Dependencies> {
|
||||
|
||||
/**
|
||||
* Only updated on every open
|
||||
*/
|
||||
private selected = observable.set<string>();
|
||||
private didToggle = false;
|
||||
|
||||
constructor(props: SelectProps & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
class NonInjectedNamespaceSelectFilter extends React.Component<
|
||||
SelectProps & Dependencies
|
||||
> {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
onKeyUp={this.props.model.onKeyUp}
|
||||
onKeyDown={this.props.model.onKeyDown}
|
||||
onClick={this.props.model.onClick}
|
||||
>
|
||||
<NamespaceSelect
|
||||
isMulti={true}
|
||||
menuIsOpen={this.props.model.menuIsOpen}
|
||||
components={{ Placeholder }}
|
||||
showAllNamespacesOption={true}
|
||||
closeMenuOnSelect={false}
|
||||
controlShouldRenderValue={false}
|
||||
placeholder={""}
|
||||
onChange={this.props.model.onChange}
|
||||
onBlur={this.props.model.reset}
|
||||
formatOptionLabel={formatOptionLabelFor(this.props.model)}
|
||||
className="NamespaceSelectFilter"
|
||||
menuClass="NamespaceSelectFilterMenu"
|
||||
sort={(left, right) =>
|
||||
+this.props.model.selectedNames.has(right.value) -
|
||||
+this.props.model.selectedNames.has(left.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
get model() {
|
||||
return this.props.model;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.model.menuIsOpen, newVal => {
|
||||
if (newVal) { // rising edge of selection
|
||||
this.selected.replace(this.props.namespaceStore.selectedNames);
|
||||
this.didToggle = false;
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
formatOptionLabel = ({ value: namespace, label }: SelectOption) => {
|
||||
const formatOptionLabelFor =
|
||||
(model: NamespaceSelectFilterModel) =>
|
||||
({ value: namespace, label }: SelectOption) => {
|
||||
if (namespace) {
|
||||
const isSelected = this.props.namespaceStore.hasContext(namespace);
|
||||
const isSelected = model.isSelected(namespace);
|
||||
|
||||
return (
|
||||
<div className="flex gaps align-center">
|
||||
@ -87,90 +90,12 @@ class NonInjectedNamespaceSelectFilter extends React.Component<SelectProps & Dep
|
||||
return label;
|
||||
};
|
||||
|
||||
@action
|
||||
onChange = ([{ value: namespace }]: SelectOption[]) => {
|
||||
if (namespace) {
|
||||
if (this.model.isMultiSelection) {
|
||||
this.didToggle = true;
|
||||
this.props.namespaceStore.toggleSingle(namespace);
|
||||
} else {
|
||||
this.props.namespaceStore.selectSingle(namespace);
|
||||
}
|
||||
} else {
|
||||
this.props.namespaceStore.selectAll();
|
||||
}
|
||||
};
|
||||
|
||||
private isSelectionKey(e: React.KeyboardEvent): boolean {
|
||||
if (isMac) {
|
||||
return e.key === "Meta";
|
||||
}
|
||||
|
||||
return e.key === "Control"; // windows or linux
|
||||
}
|
||||
|
||||
@action
|
||||
onKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (this.isSelectionKey(e)) {
|
||||
this.model.setIsMultiSelection(true);
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
onKeyUp = (e: React.KeyboardEvent) => {
|
||||
if (this.isSelectionKey(e)) {
|
||||
this.model.setIsMultiSelection(false);
|
||||
}
|
||||
|
||||
if (!this.model.isMultiSelection && this.didToggle) {
|
||||
this.model.closeMenu();
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
onClick = () => {
|
||||
if (!this.model.menuIsOpen) {
|
||||
this.model.openMenu();
|
||||
} else if (!this.model.isMultiSelection) {
|
||||
this.model.toggleMenu();
|
||||
}
|
||||
};
|
||||
|
||||
reset = () => {
|
||||
this.model.setIsMultiSelection(true);
|
||||
this.model.closeMenu();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div onKeyUp={this.onKeyUp} onKeyDown={this.onKeyDown} onClick={this.onClick}>
|
||||
<NamespaceSelect
|
||||
isMulti={true}
|
||||
menuIsOpen={this.model.menuIsOpen}
|
||||
components={{ Placeholder }}
|
||||
showAllNamespacesOption={true}
|
||||
closeMenuOnSelect={false}
|
||||
controlShouldRenderValue={false}
|
||||
placeholder={""}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.reset}
|
||||
formatOptionLabel={this.formatOptionLabel}
|
||||
className="NamespaceSelectFilter"
|
||||
menuClass="NamespaceSelectFilterMenu"
|
||||
sort={(left, right) => +this.selected.has(right.value) - +this.selected.has(left.value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const NamespaceSelectFilter = withInjectables<Dependencies, SelectProps>(
|
||||
NonInjectedNamespaceSelectFilter,
|
||||
observer(NonInjectedNamespaceSelectFilter),
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
model: di.inject(namespaceSelectFilterModelInjectable),
|
||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
@ -179,7 +104,7 @@ export const NamespaceSelectFilter = withInjectables<Dependencies, SelectProps>(
|
||||
type CustomPlaceholderProps = PlaceholderProps<any, boolean>;
|
||||
|
||||
interface PlaceholderDependencies {
|
||||
namespaceStore: NamespaceStore
|
||||
namespaceStore: NamespaceStore;
|
||||
}
|
||||
|
||||
const NonInjectedPlaceholder = observer(
|
||||
@ -206,7 +131,6 @@ const NonInjectedPlaceholder = observer(
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
const Placeholder = withInjectables<PlaceholderDependencies, CustomPlaceholderProps>(
|
||||
NonInjectedPlaceholder,
|
||||
|
||||
@ -217,4 +141,3 @@ const Placeholder = withInjectables<PlaceholderDependencies, CustomPlaceholderPr
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -89,7 +89,7 @@ class NonInjectedNamespaceSelect extends React.Component<Props & Dependencies> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className, showIcons, customizeOptions, components = {}, ...selectProps } = this.props;
|
||||
const { className, showIcons, customizeOptions, components = {}, namespaceStore, ...selectProps } = this.props;
|
||||
|
||||
return (
|
||||
<Select
|
||||
|
||||
Loading…
Reference in New Issue
Block a user