This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/app/assets/javascripts/discourse/app/components/mount-widget.js
Robin Ward 7f769e9e76 FIX: Optimistically fix topic timeline state issues
This is my second try at this. The first b246a63a59 raised an issue
with the event delegation not working because the topic id changed.

This adds support for delegating events to dynamic keys by passing a
function where a static key would normally be needed. This means that
each timeline will have its own unique state key and events will only
delegate to the proper topic.
2021-09-13 16:29:39 -04:00

155 lines
4.0 KiB
JavaScript

import { cancel, scheduleOnce } from "@ember/runloop";
import { diff, patch } from "virtual-dom";
import { queryRegistry, traverseCustomWidgets } from "discourse/widgets/widget";
import Component from "@ember/component";
import DirtyKeys from "discourse/lib/dirty-keys";
import { WidgetClickHook } from "discourse/widgets/hooks";
import { camelize } from "@ember/string";
import { getRegister } from "discourse-common/lib/get-owner";
let _cleanCallbacks = {};
export function addWidgetCleanCallback(widgetName, fn) {
_cleanCallbacks[widgetName] = _cleanCallbacks[widgetName] || [];
_cleanCallbacks[widgetName].push(fn);
}
export function resetWidgetCleanCallbacks() {
_cleanCallbacks = {};
}
export default Component.extend({
_tree: null,
_rootNode: null,
_timeout: null,
_widgetClass: null,
_renderCallback: null,
_childEvents: null,
_dispatched: null,
dirtyKeys: null,
init() {
this._super(...arguments);
const name = this.widget;
this.register = getRegister(this);
this._widgetClass =
queryRegistry(name) || this.register.lookupFactory(`widget:${name}`);
if (!this._widgetClass) {
// eslint-disable-next-line no-console
console.error(`Error: Could not find widget: ${name}`);
}
this._childEvents = [];
this._connected = [];
this._dispatched = [];
this.dirtyKeys = new DirtyKeys(name);
},
didInsertElement() {
WidgetClickHook.setupDocumentCallback();
this._rootNode = document.createElement("div");
this.element.appendChild(this._rootNode);
this._timeout = scheduleOnce("render", this, this.rerenderWidget);
},
willClearRender() {
const callbacks = _cleanCallbacks[this.widget];
if (callbacks) {
callbacks.forEach((cb) => cb(this._tree));
}
this._connected.forEach((v) => v.destroy());
this._connected.length = 0;
this._rootNode = patch(this._rootNode, diff(this._tree, null));
this._tree = null;
},
willDestroyElement() {
this._dispatched.forEach((evt) => {
const [eventName, caller] = evt;
this.appEvents.off(eventName, this, caller);
});
cancel(this._timeout);
},
afterRender() {},
beforePatch() {},
afterPatch() {},
eventDispatched(eventName, key, refreshArg) {
key = typeof key === "function" ? key(refreshArg) : key;
const onRefresh = camelize(eventName.replace(/:/, "-"));
this.dirtyKeys.keyDirty(key, { onRefresh, refreshArg });
this.queueRerender();
},
dispatch(eventName, key) {
this._childEvents.push(eventName);
const caller = (refreshArg) =>
this.eventDispatched(eventName, key, refreshArg);
this._dispatched.push([eventName, caller]);
this.appEvents.on(eventName, this, caller);
},
queueRerender(callback) {
if (callback && !this._renderCallback) {
this._renderCallback = callback;
}
scheduleOnce("render", this, this.rerenderWidget);
},
buildArgs() {},
rerenderWidget() {
cancel(this._timeout);
if (this._rootNode) {
if (!this._widgetClass) {
return;
}
const t0 = Date.now();
const args = this.args || this.buildArgs();
const opts = {
model: this.model,
dirtyKeys: this.dirtyKeys,
};
const newTree = new this._widgetClass(args, this.register, opts);
newTree._rerenderable = this;
newTree._emberView = this;
const patches = diff(this._tree || this._rootNode, newTree);
traverseCustomWidgets(this._tree, (w) => w.willRerenderWidget());
this.beforePatch();
this._rootNode = patch(this._rootNode, patches);
this.afterPatch();
this._tree = newTree;
traverseCustomWidgets(newTree, (w) => w.didRenderWidget());
if (this._renderCallback) {
this._renderCallback();
this._renderCallback = null;
}
this.afterRender();
this.dirtyKeys.renderedKey("*");
if (this.profileWidget) {
// eslint-disable-next-line no-console
console.log(Date.now() - t0);
}
}
},
});