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 { NamespaceSelectFilterModel } from "./namespace-select-filter-model";
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import namespaceStoreInjectable from "../namespace-store/namespace-store.injectable";
|
||||||
|
|
||||||
const NamespaceSelectFilterModelInjectable = getInjectable({
|
const NamespaceSelectFilterModelInjectable = getInjectable({
|
||||||
instantiate: () => new NamespaceSelectFilterModel(),
|
instantiate: (di) => new NamespaceSelectFilterModel({
|
||||||
|
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||||
|
}),
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -18,17 +18,22 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* 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.
|
* 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 {
|
export class NamespaceSelectFilterModel {
|
||||||
constructor() {
|
constructor(private dependencies: Dependencies) {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
menuIsOpen: observable,
|
menuIsOpen: observable,
|
||||||
closeMenu: action,
|
closeMenu: action,
|
||||||
openMenu: action,
|
openMenu: action,
|
||||||
toggleMenu: action,
|
reset: action,
|
||||||
isMultiSelection: observable,
|
|
||||||
setIsMultiSelection: action,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,13 +47,65 @@ export class NamespaceSelectFilterModel {
|
|||||||
this.menuIsOpen = true;
|
this.menuIsOpen = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleMenu = () => {
|
get selectedNames() {
|
||||||
this.menuIsOpen = !this.menuIsOpen;
|
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) => {
|
onChange = ([{ value: namespace }]: SelectOption[]) => {
|
||||||
this.isMultiSelection = isMultiSelection;
|
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 "./namespace-select-filter.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { components, PlaceholderProps } from "react-select";
|
import { components, PlaceholderProps } from "react-select";
|
||||||
import { action, makeObservable, observable, reaction } from "mobx";
|
|
||||||
|
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { NamespaceSelect } from "./namespace-select";
|
import { NamespaceSelect } from "./namespace-select";
|
||||||
import type { NamespaceStore } from "./namespace-store/namespace.store";
|
import type { NamespaceStore } from "./namespace-store/namespace.store";
|
||||||
|
|
||||||
import type { SelectOption, SelectProps } from "../select";
|
import type { SelectOption, SelectProps } from "../select";
|
||||||
import { isMac } from "../../../common/vars";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
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 type { NamespaceSelectFilterModel } from "./namespace-select-filter-model/namespace-select-filter-model";
|
||||||
import namespaceSelectFilterModelInjectable from "./namespace-select-filter-model/namespace-select-filter-model.injectable";
|
import namespaceSelectFilterModelInjectable from "./namespace-select-filter-model/namespace-select-filter-model.injectable";
|
||||||
|
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
model: NamespaceSelectFilterModel,
|
model: NamespaceSelectFilterModel;
|
||||||
namespaceStore: NamespaceStore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
class NonInjectedNamespaceSelectFilter extends React.Component<
|
||||||
class NonInjectedNamespaceSelectFilter extends React.Component<SelectProps & Dependencies> {
|
SelectProps & Dependencies
|
||||||
|
> {
|
||||||
/**
|
render() {
|
||||||
* Only updated on every open
|
return (
|
||||||
*/
|
<div
|
||||||
private selected = observable.set<string>();
|
onKeyUp={this.props.model.onKeyUp}
|
||||||
private didToggle = false;
|
onKeyDown={this.props.model.onKeyDown}
|
||||||
|
onClick={this.props.model.onClick}
|
||||||
constructor(props: SelectProps & Dependencies) {
|
>
|
||||||
super(props);
|
<NamespaceSelect
|
||||||
makeObservable(this);
|
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)
|
||||||
}
|
}
|
||||||
|
/>
|
||||||
get model() {
|
</div>
|
||||||
return this.props.model;
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
const formatOptionLabelFor =
|
||||||
disposeOnUnmount(this, [
|
(model: NamespaceSelectFilterModel) =>
|
||||||
reaction(() => this.model.menuIsOpen, newVal => {
|
({ value: namespace, label }: SelectOption) => {
|
||||||
if (newVal) { // rising edge of selection
|
|
||||||
this.selected.replace(this.props.namespaceStore.selectedNames);
|
|
||||||
this.didToggle = false;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
formatOptionLabel = ({ value: namespace, label }: SelectOption) => {
|
|
||||||
if (namespace) {
|
if (namespace) {
|
||||||
const isSelected = this.props.namespaceStore.hasContext(namespace);
|
const isSelected = model.isSelected(namespace);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gaps align-center">
|
<div className="flex gaps align-center">
|
||||||
@ -87,90 +90,12 @@ class NonInjectedNamespaceSelectFilter extends React.Component<SelectProps & Dep
|
|||||||
return label;
|
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>(
|
export const NamespaceSelectFilter = withInjectables<Dependencies, SelectProps>(
|
||||||
NonInjectedNamespaceSelectFilter,
|
observer(NonInjectedNamespaceSelectFilter),
|
||||||
|
|
||||||
{
|
{
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
model: di.inject(namespaceSelectFilterModelInjectable),
|
model: di.inject(namespaceSelectFilterModelInjectable),
|
||||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -179,7 +104,7 @@ export const NamespaceSelectFilter = withInjectables<Dependencies, SelectProps>(
|
|||||||
type CustomPlaceholderProps = PlaceholderProps<any, boolean>;
|
type CustomPlaceholderProps = PlaceholderProps<any, boolean>;
|
||||||
|
|
||||||
interface PlaceholderDependencies {
|
interface PlaceholderDependencies {
|
||||||
namespaceStore: NamespaceStore
|
namespaceStore: NamespaceStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedPlaceholder = observer(
|
const NonInjectedPlaceholder = observer(
|
||||||
@ -206,7 +131,6 @@ const NonInjectedPlaceholder = observer(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const Placeholder = withInjectables<PlaceholderDependencies, CustomPlaceholderProps>(
|
const Placeholder = withInjectables<PlaceholderDependencies, CustomPlaceholderProps>(
|
||||||
NonInjectedPlaceholder,
|
NonInjectedPlaceholder,
|
||||||
|
|
||||||
@ -217,4 +141,3 @@ const Placeholder = withInjectables<PlaceholderDependencies, CustomPlaceholderPr
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -89,7 +89,7 @@ class NonInjectedNamespaceSelect extends React.Component<Props & Dependencies> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, showIcons, customizeOptions, components = {}, ...selectProps } = this.props;
|
const { className, showIcons, customizeOptions, components = {}, namespaceStore, ...selectProps } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user