WatchRendition.ts
97 lines
| 2.7 KiB
| video/mp2t
|
TypeScriptLexer
cin
|
r94 | import { id as mid } from "module"; | ||
import { TraceSource } from "@implab/core-amd/log/TraceSource"; | ||||
import { argumentNotNull } from "@implab/core-amd/safe"; | ||||
cin
|
r101 | import { getScope, render } from "./render"; | ||
cin
|
r94 | import { RenditionBase } from "./RenditionBase"; | ||
cin
|
r101 | import { Scope } from "./Scope"; | ||
cin
|
r102 | import { Subscribable } from "../observable"; | ||
import { Cancellation } from "@implab/core-amd/Cancellation"; | ||||
import { collectNodes, destroy, isDocumentFragmentNode, isMounted, placeAt, startupWidgets } from "./traits"; | ||||
cin
|
r94 | |||
const trace = TraceSource.get(mid); | ||||
export class WatchRendition<T> extends RenditionBase<Node> { | ||||
cin
|
r98 | private readonly _component: (arg: T) => unknown; | ||
cin
|
r94 | |||
cin
|
r102 | private readonly _node: Node; | ||
cin
|
r94 | |||
private readonly _scope = new Scope(); | ||||
cin
|
r102 | private readonly _subject: Subscribable<T>; | ||
private _renderJob?: { value: T }; | ||||
cin
|
r96 | |||
cin
|
r102 | private _ct = Cancellation.none; | ||
constructor(component: (arg: T) => unknown, subject: Subscribable<T>) { | ||||
cin
|
r94 | super(); | ||
argumentNotNull(component, "component"); | ||||
cin
|
r98 | this._component = component; | ||
cin
|
r94 | |||
cin
|
r96 | this._subject = subject; | ||
cin
|
r102 | this._node = document.createComment("[Watch]"); | ||
cin
|
r94 | } | ||
cin
|
r102 | protected _create() { | ||
cin
|
r101 | const scope = getScope(); | ||
cin
|
r102 | scope.own(() => { | ||
this._scope.destroy(); | ||||
destroy(this._node); | ||||
}); | ||||
scope.own(this._subject.subscribe({ next: this._onValue })); | ||||
this._ct = new Cancellation(cancel => scope.own(cancel)); | ||||
} | ||||
private _onValue = (value: T) => { | ||||
if (!this._renderJob) { | ||||
// schedule a new job | ||||
this._renderJob = { value }; | ||||
this._render().catch(e => trace.error(e)); | ||||
} else { | ||||
// update existing job | ||||
this._renderJob = { value }; | ||||
} | ||||
cin
|
r94 | } | ||
cin
|
r102 | private async _render() { | ||
// fork | ||||
await Promise.resolve(); | ||||
// don't render destroyed rendition | ||||
if (this._ct.isRequested()) | ||||
return; | ||||
// remove all previous content | ||||
this._scope.clean(); | ||||
cin
|
r96 | |||
cin
|
r102 | // render the new node | ||
const node = render( | ||||
this._renderJob ? this._component(this._renderJob.value) : undefined, | ||||
this._scope | ||||
); | ||||
// get actual content | ||||
const pending = isDocumentFragmentNode(node) ? | ||||
collectNodes(node.childNodes) : | ||||
[node]; | ||||
placeAt(node, this._node, "after"); | ||||
if (isMounted(this._node)) | ||||
pending.forEach(n => startupWidgets(n)); | ||||
if (pending.length) | ||||
this._scope.own(() => pending.forEach(destroy)); | ||||
this._renderJob = undefined; | ||||
cin
|
r94 | } | ||
protected _getDomNode() { | ||||
if (!this._node) | ||||
throw new Error("The instance of the widget isn't created"); | ||||
return this._node; | ||||
} | ||||
} | ||||