grafana/public/app/features/scenes/core/SceneObjectBase.tsx
Torkel Ödegaard 849134b5dd
Scene: Small refactorings and name changes (#51866)
* Rename onMount and onUnmount and some other small refactorings

* More refactorings fixing typescript issues
2022-07-07 16:49:05 +02:00

201 lines
4.8 KiB
TypeScript

import { useObservable } from 'react-use';
import { Observer, Subject, Subscription } from 'rxjs';
import { v4 as uuidv4 } from 'uuid';
import { EventBusSrv } from '@grafana/data';
import { SceneComponentWrapper } from './SceneComponentWrapper';
import { SceneObjectStateChangedEvent } from './events';
import {
SceneDataState,
SceneObject,
SceneLayoutState,
SceneObjectState,
SceneComponent,
SceneEditor,
SceneObjectList,
SceneTimeRange,
} from './types';
export abstract class SceneObjectBase<TState extends SceneObjectState = {}> implements SceneObject<TState> {
subject = new Subject<TState>();
state: TState;
parent?: SceneObjectBase<any>;
subs = new Subscription();
isActive?: boolean;
events = new EventBusSrv();
constructor(state: TState) {
if (!state.key) {
state.key = uuidv4();
}
this.state = state;
this.subject.next(state);
this.setParent();
}
/**
* Used in render functions when rendering a SceneObject.
* Wraps the component in an EditWrapper that handles edit mode
*/
get Component(): SceneComponent<this> {
return SceneComponentWrapper;
}
/**
* Temporary solution, should be replaced by declarative options
*/
get Editor(): SceneComponent<this> {
return ((this as any).constructor['Editor'] ?? (() => null)) as SceneComponent<this>;
}
private setParent() {
for (const propValue of Object.values(this.state)) {
if (propValue instanceof SceneObjectBase) {
propValue.parent = this;
}
if (Array.isArray(propValue)) {
for (const child of propValue) {
if (child instanceof SceneObjectBase) {
child.parent = this;
}
}
}
}
}
/** This function implements the Subscribable<TState> interface */
subscribe(observer: Partial<Observer<TState>>) {
return this.subject.subscribe(observer);
}
setState(update: Partial<TState>) {
const prevState = this.state;
this.state = {
...this.state,
...update,
};
this.setParent();
this.subject.next(this.state);
// broadcast state change. This is event is subscribed to by UrlSyncManager and UndoManager
this.getRoot().events.publish(
new SceneObjectStateChangedEvent({
prevState,
newState: this.state,
partialUpdate: update,
changedObject: this,
})
);
}
private getRoot(): SceneObject {
return !this.parent ? this : this.parent.getRoot();
}
activate() {
this.isActive = true;
const { $data } = this.state;
if ($data && !$data.isActive) {
$data.activate();
}
}
deactivate(): void {
this.isActive = false;
const { $data } = this.state;
if ($data && $data.isActive) {
$data.deactivate();
}
this.subs.unsubscribe();
this.subs = new Subscription();
}
useState() {
// eslint-disable-next-line react-hooks/rules-of-hooks
return useObservable(this.subject, this.state);
}
/**
* Will walk up the scene object graph to the closest $timeRange scene object
*/
getTimeRange(): SceneTimeRange {
const { $timeRange } = this.state;
if ($timeRange) {
return $timeRange;
}
if (this.parent) {
return this.parent.getTimeRange();
}
throw new Error('No time range found in scene tree');
}
/**
* Will walk up the scene object graph to the closest $data scene object
*/
getData(): SceneObject<SceneDataState> {
const { $data } = this.state;
if ($data) {
return $data;
}
if (this.parent) {
return this.parent.getData();
}
throw new Error('No data found in scene tree');
}
/**
* Will walk up the scene object graph to the closest $editor scene object
*/
getSceneEditor(): SceneEditor {
const { $editor } = this.state;
if ($editor) {
return $editor;
}
if (this.parent) {
return this.parent.getSceneEditor();
}
throw new Error('No editor found in scene tree');
}
/**
* Will create new SceneItem with shalled cloned state, but all states items of type SceneItem are deep cloned
*/
clone(withState?: Partial<TState>): this {
const clonedState = { ...this.state };
// Clone any SceneItems in state
for (const key in clonedState) {
const propValue = clonedState[key];
if (propValue instanceof SceneObjectBase) {
clonedState[key] = propValue.clone();
}
}
// Clone layout children
const layout = this.state as any as SceneLayoutState;
if (layout.children) {
const newChildren: SceneObjectList = [];
for (const child of layout.children) {
newChildren.push(child.clone());
}
(clonedState as any).children = newChildren;
}
Object.assign(clonedState, withState);
return new (this.constructor as any)(clonedState);
}
}