1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/renderer/item.store.ts
Roman 2c3b510997
Mobx-6 migration (#2718)
* mobx-6 migration -- part 1

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6 migration -- part 2 (npx mobx-undecorate --keepDecorators)

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6 migration -- part 3 (more fixes)

Signed-off-by: Roman <ixrock@gmail.com>

* unwrap possible observables from IPC-messaging

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6 migration -- remove @autobind as class-decorator

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6: replacing @autobind() as method-decorator to @boundMethod

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6: use toJS()-wrapper since monkey-patching require(mobx).toJS doesn't work

Signed-off-by: Roman <ixrock@gmail.com>

* removed `@observable static`

Signed-off-by: Roman <ixrock@gmail.com>

* use {useDefineForClassFields: true} in tsconfig.json

Signed-off-by: Roman <ixrock@gmail.com>

* remove ExtendedObservableMap

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix: removed makeObservable(this) from "terminal-tab.tsx"

Signed-off-by: Roman <ixrock@gmail.com>

* storage-helper refactoring

Signed-off-by: Roman <ixrock@gmail.com>

* normalize usages of #observable-value.toJSON() / attempt to catch the wind

Signed-off-by: Roman <ixrock@gmail.com>

* refactoring, more possible branch fixes + lint

Signed-off-by: Roman <ixrock@gmail.com>

* debugging cluster-view error -- part 1

Signed-off-by: Roman <ixrock@gmail.com>

* fix: refreshing cluster-view on ready

Signed-off-by: Roman <ixrock@gmail.com>

* fix: various app-crashes related to KubeObject.spec.* access from "undefined"
fix: config-map-details crash

Signed-off-by: Roman <ixrock@gmail.com>

* fix: namespace-store refactoring / saving selected-namespaces to external json-file

Signed-off-by: Roman <ixrock@gmail.com>

* fix: don't cache mobx.when(() => this.someObservable) cause might not work as expected due later call of makeObservable(this) in constructor

Signed-off-by: Roman <ixrock@gmail.com>

* fix: app-crash on editing k8s resource

Signed-off-by: Roman <ixrock@gmail.com>

* fix: restore "all namespaces" on page reload

Signed-off-by: Roman <ixrock@gmail.com>

* - fix: persist table-sort params and cluster-view's sidebar state to lens-local-storage
- new-feature: auto-open main-window's devtools in development-mode (yes/no/ugly?)

Signed-off-by: Roman <ixrock@gmail.com>

* fix: crd definition details -> crashing with <AceEditor mode="json"> (added missing mode-file in ace-editor.tsx)

Signed-off-by: Roman <ixrock@gmail.com>

* fix: crd definitions -> groups selector couldn't deselect last selected option

Signed-off-by: Roman <ixrock@gmail.com>

* refactoring: extensions-api exports clarification for "@k8slens/extensions"

Signed-off-by: Roman <ixrock@gmail.com>

* fix: various app-crashes related to kube-events (events page, some details page, overview, etc.)

Signed-off-by: Roman <ixrock@gmail.com>

* Reverted "use {useDefineForClassFields: true} in tsconfig.json" (various app-crash fixes)
This flag seems to be not possible to use with class-inheritance in some cases.

Example / demo:
`KubeObject` class has initial type definitions for the fields like: "metadata", "kind", etc.
and constructor() has Object.assign(this, data);
Meanwhile child class, e.g. KubeEvent inherited from KubeObject and has it's own extra type definitions for underlying resource, e.g. "involvedObject", "source", etc.

So calling super(data) doesn't work as expected for child class as it's own type definitions overwrites data from parent's constructor with `undefined` at later point.

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge lint-fixes

Signed-off-by: Roman <ixrock@gmail.com>

* catalog.tsx / catalog-entities.store.ts refactoring & fixes

Signed-off-by: Roman <ixrock@gmail.com>

* fix: Catalog -> Browse all tab

Signed-off-by: Roman <ixrock@gmail.com>

* fix: CommandPalette doesn't appear from global menu by click/hotkey

Signed-off-by: Roman <ixrock@gmail.com>

* - Merging interfaces & classses to avoid overwriting fields from parent's super(data)-call with Object.assign(this, data). Otherwise use "declare" keyword at class field definition.

- Revamping {useDefineForClassFields: true} to avoid issues with non-observable class fields in some cases (from previous commit):

```
@observer
export class CommandContainer extends React.Component<CommandContainerProps> {
  // without some defined initial value "commandComponent" is non-observable for some reasons
  // when tsconfig.ts has {useDefineForClassFields:false}
  @observable.ref commandComponent: React.ReactNode = null;

  constructor(props: CommandContainerProps) {
    super(props);
    makeObservable(this);
  }
```

Signed-off-by: Roman <ixrock@gmail.com>

* update KubeObject class type definition

Signed-off-by: Roman <ixrock@gmail.com>

* clean up / responding to comments

Signed-off-by: Roman <ixrock@gmail.com>

* fix: app-crash when navigating to catalog from active cluster-view, refactoring `catalog-entity-store`

Signed-off-by: Roman <ixrock@gmail.com>

* catalog-pusher clean up, replaced .observe_() to external observe() helper from "mobx"

Signed-off-by: Roman <ixrock@gmail.com>

* fix: catalog's items stale/non-observable (after connection to the cluster status still "disconnected"), lint-fixes

Signed-off-by: Roman <ixrock@gmail.com>

* fix: Catalog is empty after closing main-window and re-opening app from Tray

Signed-off-by: Roman <ixrock@gmail.com>

* fix: HotBar's icon context menu items non-observable (no "disconnect cluster", etc.)

Signed-off-by: Roman <ixrock@gmail.com>

* lint-fix/license check

Signed-off-by: Roman <ixrock@gmail.com>

* fix: redirect to catalog when disconnecting active cluster

Signed-off-by: Roman <ixrock@gmail.com>

* fix: refresh visibility of active cluster-view on switching from hotbar/catalog

Signed-off-by: Roman <ixrock@gmail.com>

* updated package.json for built-in extensions to use "*" version for packages served from main app

Signed-off-by: Roman <ixrock@gmail.com>

* - added missing makeObservable(this) to metrics-settings.tsx
- updated package-lock.json for built-in extensions
- lint fixes

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge clean up fix, updated package-lock.json for built-in extensions after `make clean-extensions && make build-extensions`

Signed-off-by: Roman <ixrock@gmail.com>

* fix unit-tests

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge fixes

Signed-off-by: Roman <ixrock@gmail.com>

* make lint happy

Signed-off-by: Roman <ixrock@gmail.com>

* reverted some changes, removed auto-opening devtools in dev-mode

Signed-off-by: Roman <ixrock@gmail.com>

* merge fixes

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge conflict fixes:
- proper handling and navigating into catalog's active category via URL-builder

Signed-off-by: Roman <ixrock@gmail.com>

* reverting splitted params for catalog's page route to "/catalog/:group?/:kind?"

Signed-off-by: Roman <ixrock@gmail.com>

* clean-up: remove app's injecting dependencies from `extensions/kube-object-event-status/package.json`

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge fix: added missing makeObservable(this) for extensions.tsx

Signed-off-by: Roman <ixrock@gmail.com>

* fix: catalog entity context menu stale/unobservable

Signed-off-by: Roman <ixrock@gmail.com>

Co-authored-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
2021-05-25 10:24:31 +03:00

225 lines
6.3 KiB
TypeScript

/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* 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 orderBy from "lodash/orderBy";
import { autoBind, noop } from "./utils";
import { action, computed, observable, when, makeObservable } from "mobx";
export interface ItemObject {
getId(): string;
getName(): string;
}
export abstract class ItemStore<T extends ItemObject = ItemObject> {
abstract loadAll(...args: any[]): Promise<void | T[]>;
protected defaultSorting = (item: T) => item.getName();
@observable failedLoading = false;
@observable isLoading = false;
@observable isLoaded = false;
@observable items = observable.array<T>([], { deep: false });
@observable selectedItemsIds = observable.map<string, boolean>();
constructor() {
makeObservable(this);
autoBind(this);
}
@computed get selectedItems(): T[] {
return this.items.filter(item => this.selectedItemsIds.get(item.getId()));
}
public getItems(): T[] {
return Array.from(this.items);
}
public getTotalCount(): number {
return this.items.length;
}
getByName(name: string, ...args: any[]): T;
getByName(name: string): T {
return this.items.find(item => item.getName() === name);
}
getIndexById(id: string): number {
return this.items.findIndex(item => item.getId() === id);
}
/**
* Return `items` sorted by the given ordering functions. If two elements of
* `items` are sorted to the same "index" then the next sorting function is used
* to determine where to place them relative to each other. Once the `sorting`
* functions have been all exhausted then the order is unchanged (ie a stable sort).
* @param items the items to be sorted (default: the current items in this store)
* @param sorting list of functions to determine sort order (default: sorting by name)
* @param order whether to sort from least to greatest (`"asc"` (default)) or vice-versa (`"desc"`)
*/
@action
protected sortItems(items: T[] = this.items, sorting: ((item: T) => any)[] = [this.defaultSorting], order?: "asc" | "desc"): T[] {
return orderBy(items, sorting, order);
}
protected async createItem(...args: any[]): Promise<any>;
@action
protected async createItem(request: () => Promise<T>) {
const newItem = await request();
const item = this.items.find(item => item.getId() === newItem.getId());
if (item) {
return item;
} else {
const items = this.sortItems([...this.items, newItem]);
this.items.replace(items);
return newItem;
}
}
protected async loadItems(...args: any[]): Promise<any>;
@action
protected async loadItems(request: () => Promise<T[] | any>, sortItems = true) {
if (this.isLoading) {
await when(() => !this.isLoading);
return;
}
this.isLoading = true;
try {
let items = await request();
if (sortItems) items = this.sortItems(items);
this.items.replace(items);
this.isLoaded = true;
} finally {
this.isLoading = false;
}
}
protected async loadItem(...args: any[]): Promise<T>
@action
protected async loadItem(request: () => Promise<T>, sortItems = true) {
const item = await Promise.resolve(request()).catch(() => null);
if (item) {
const existingItem = this.items.find(el => el.getId() === item.getId());
if (existingItem) {
const index = this.items.findIndex(item => item === existingItem);
this.items.splice(index, 1, item);
} else {
let items = [...this.items, item];
if (sortItems) items = this.sortItems(items);
this.items.replace(items);
}
return item;
}
}
@action
protected async updateItem(item: T, request: () => Promise<T>) {
const updatedItem = await request();
const index = this.items.findIndex(i => i.getId() === item.getId());
this.items.splice(index, 1, updatedItem);
return updatedItem;
}
@action
protected async removeItem(item: T, request: () => Promise<any>) {
await request();
this.items.remove(item);
this.selectedItemsIds.delete(item.getId());
}
isSelected(item: T) {
return !!this.selectedItemsIds.get(item.getId());
}
@action
select(item: T) {
this.selectedItemsIds.set(item.getId(), true);
}
@action
unselect(item: T) {
this.selectedItemsIds.delete(item.getId());
}
@action
toggleSelection(item: T) {
if (this.isSelected(item)) {
this.unselect(item);
} else {
this.select(item);
}
}
@action
toggleSelectionAll(visibleItems: T[] = this.items) {
const allSelected = visibleItems.every(this.isSelected);
if (allSelected) {
visibleItems.forEach(this.unselect);
} else {
visibleItems.forEach(this.select);
}
}
isSelectedAll(visibleItems: T[] = this.items) {
if (!visibleItems.length) return false;
return visibleItems.every(this.isSelected);
}
@action
resetSelection() {
this.selectedItemsIds.clear();
}
@action
reset() {
this.resetSelection();
this.items.clear();
this.selectedItemsIds.clear();
this.isLoaded = false;
this.isLoading = false;
}
async removeSelectedItems?(): Promise<any>;
// eslint-disable-next-line unused-imports/no-unused-vars-ts
subscribe(...args: any[]) {
return noop;
}
* [Symbol.iterator]() {
yield* this.items;
}
}