Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature support promised custom node render #594

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/jsmind.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,22 @@ function setup_logger_level(log_level) {
logger.error = console.error;
}
}

// 如果 promise 不为空,则认为是 promise,执行 promise.then(follow_logic) 并返回新的 promise
export function then_if_promise(promise, follow_logic) {
if (!!promise) {
return promise.then(() => {
follow_logic();
});
} else {
follow_logic();
}
}

export function is_promise(obj) {
return (
!!obj &&
(typeof obj === 'object' || typeof obj === 'function') &&
typeof obj.then === 'function'
);
}
104 changes: 60 additions & 44 deletions src/jsmind.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { __version__, logger, EventType, Direction, LogLevel } from './jsmind.common.js';
import { then_if_promise } from './jsmind.common.js';
import { merge_option } from './jsmind.option.js';
import { Mind } from './jsmind.mind.js';
import { Node } from './jsmind.node.js';
Expand Down Expand Up @@ -285,16 +286,21 @@ export default class jsMind {
logger.debug('data.load ok');
}

this.view.load();
const view_load_promise = this.view.load();
logger.debug('view.load ok');

this.layout.layout();
logger.debug('layout.layout ok');
const tail_process = () => {
this.layout.layout();
logger.debug('layout.layout ok');

this.view.show(true);
logger.debug('view.show ok');

this.view.show(true);
logger.debug('view.show ok');
this.invoke_event_handle(EventType.show, { data: [mind] });
//logger.info('[jsmind._show] tail_process done.');
};

this.invoke_event_handle(EventType.show, { data: [mind] });
then_if_promise(view_load_promise, tail_process);
}
show(mind) {
this._reset();
Expand Down Expand Up @@ -329,22 +335,23 @@ export default class jsMind {
}
var node = this.mind.add_node(the_parent_node, node_id, topic, data, dir);
if (!!node) {
this.view.add_node(node);
this.layout.layout();
this.view.show(false);
this.view.reset_node_custom_style(node);
this.expand_node(the_parent_node);
this.invoke_event_handle(EventType.edit, {
evt: 'add_node',
data: [the_parent_node.id, node_id, topic, data, dir],
node: node_id,
return this.view.add_node(node).then(() => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里按说就会返回一个 Promise 对象了吧。那么 API 里的 add_node 这个方法的返回值也就是一个 Promise 了,我再考虑考虑,看这样搞是不是动静有点儿大,相当于不能向后兼容了。

this.layout.layout();
this.view.show(false);
this.view.reset_node_custom_style(node);
this.expand_node(the_parent_node);
this.invoke_event_handle(EventType.edit, {
evt: 'add_node',
data: [the_parent_node.id, node_id, topic, data, dir],
node: node_id,
});
return node;
});
}
return node;
} else {
logger.error('fail, this mind map is not editable');
return null;
}
return Promise.resolve(null);
}
insert_node_before(node_before, node_id, topic, data, direction) {
if (this.get_editable()) {
Expand All @@ -355,13 +362,14 @@ export default class jsMind {
}
var node = this.mind.insert_node_before(the_node_before, node_id, topic, data, dir);
if (!!node) {
this.view.add_node(node);
this.layout.layout();
this.view.show(false);
this.invoke_event_handle(EventType.edit, {
evt: 'insert_node_before',
data: [the_node_before.id, node_id, topic, data, dir],
node: node_id,
this.view.add_node(node).then(() => {
this.layout.layout();
this.view.show(false);
this.invoke_event_handle(EventType.edit, {
evt: 'insert_node_before',
data: [the_node_before.id, node_id, topic, data, dir],
node: node_id,
});
});
}
return node;
Expand All @@ -379,13 +387,14 @@ export default class jsMind {
}
var node = this.mind.insert_node_after(the_node_after, node_id, topic, data, dir);
if (!!node) {
this.view.add_node(node);
this.layout.layout();
this.view.show(false);
this.invoke_event_handle(EventType.edit, {
evt: 'insert_node_after',
data: [the_node_after.id, node_id, topic, data, dir],
node: node_id,
this.view.add_node(node).then(() => {
this.layout.layout();
this.view.show(false);
this.invoke_event_handle(EventType.edit, {
evt: 'insert_node_after',
data: [the_node_after.id, node_id, topic, data, dir],
node: node_id,
});
});
}
return node;
Expand Down Expand Up @@ -430,27 +439,34 @@ export default class jsMind {
}
}
update_node(node_id, topic) {
//logger.info('[jsmind.update_node] node_id:' + node_id + ', topic:', topic);
if (this.get_editable()) {
if (_util.text.is_empty(topic)) {
logger.warn('fail, topic can not be empty');
return;
}
var node = this.get_node(node_id);
if (!!node) {
if (node.topic === topic) {
logger.info('nothing changed');
this.view.update_node(node);
return;
}
// 为了在 react 场景下,能通过编辑不修改退出这个场景,暂时注释掉下面代码
// if (node.topic === topic) {
// logger.info('nothing changed');
// this.view.update_node(node);
// return;
// }
node.topic = topic;
this.view.update_node(node);
this.layout.layout();
this.view.show(false);
this.invoke_event_handle(EventType.edit, {
evt: 'update_node',
data: [node_id, topic],
node: node_id,
});
const promise = this.view.update_node(node);
const follow_logic = () => {
this.layout.layout();
this.view.show(false);
this.invoke_event_handle(EventType.edit, {
evt: 'update_node',
data: [node_id, topic],
node: node_id,
});
//logger.info('[jsmind.update_node] follow_logic node_id:' + node_id);
};
//logger.info('[jsmind.update_node] view.update_node return promise?:', !!promise);
return then_if_promise(promise, follow_logic);
}
} else {
logger.error('fail, this mind map is not editable');
Expand Down
11 changes: 6 additions & 5 deletions src/jsmind.shortcut_provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,12 @@ export class ShortcutProvider {
var selected_node = _jm.get_selected_node();
if (!!selected_node) {
var node_id = this._newid();
var node = _jm.add_node(selected_node, node_id, 'New Node');
if (!!node) {
_jm.select_node(node_id);
_jm.begin_edit(node_id);
}
_jm.add_node(selected_node, node_id, 'New Node').then(node => {
if (!!node) {
_jm.select_node(node_id);
_jm.begin_edit(node_id);
}
});
}
}
handle_addbrother(_jm, e) {
Expand Down
111 changes: 76 additions & 35 deletions src/jsmind.view_provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Project Home:
* https://github.com/hizzgdev/jsmind/
*/
import { logger, EventType } from './jsmind.common.js';
import { logger, EventType, then_if_promise, is_promise } from './jsmind.common.js';
import { $ } from './jsmind.dom.js';
import { init_graph } from './jsmind.graph.js';
import { util } from './jsmind.util.js';
Expand Down Expand Up @@ -137,10 +137,10 @@ export class ViewProvider {
}
}
load() {
logger.debug('view.load');
this.setup_canvas_draggable(this.opts.draggable);
this.init_nodes();
const init_res = this.init_nodes();
this._initialized = true;
return init_res;
}
expand_size() {
var min_size = this.layout.get_min_size();
Expand All @@ -165,21 +165,26 @@ export class ViewProvider {
init_nodes() {
var nodes = this.jm.mind.nodes;
var doc_frag = $.d.createDocumentFragment();
for (var nodeid in nodes) {
this.create_node_element(nodes[nodeid], doc_frag);
const promises = [];

for (let nodeid in nodes) {
promises.push(
this.create_node_element(nodes[nodeid], doc_frag).then(() => {
this.run_in_c11y_mode_if_needed(() => {
this.init_nodes_size(nodes[nodeid]);
});
})
);
}
this.e_nodes.appendChild(doc_frag);

this.run_in_c11y_mode_if_needed(() => {
for (var nodeid in nodes) {
this.init_nodes_size(nodes[nodeid]);
}
});
return Promise.all(promises); //该 Promise 在所有子 Promise 完成后解决
}
add_node(node) {
this.create_node_element(node, this.e_nodes);
this.run_in_c11y_mode_if_needed(() => {
this.init_nodes_size(node);
//logger.info('[view.add_node] node:', node);
return this.create_node_element(node, this.e_nodes).then(() => {
this.run_in_c11y_mode_if_needed(() => {
this.init_nodes_size(node);
});
});
}
run_in_c11y_mode_if_needed(func) {
Expand Down Expand Up @@ -218,15 +223,17 @@ export class ViewProvider {
parent_node.appendChild(d_e);
view_data.expander = d_e;
}
let render_promise = Promise.resolve();
if (!!node.topic) {
this.render_node(d, node);
render_promise = this.render_node(d, node);
}
d.setAttribute('nodeid', node.id);
d.style.visibility = 'hidden';
this._reset_node_custom_style(d, node.data);

parent_node.appendChild(d);
view_data.element = d;
return render_promise;
}
remove_node(node) {
if (this.selected_node != null && this.selected_node.id == node.id) {
Expand All @@ -252,20 +259,43 @@ export class ViewProvider {
}
update_node(node) {
var view_data = node._data.view;
var element = view_data.element;
let element = view_data.element;
let render_promise = Promise.resolve();
if (!!node.topic) {
this.render_node(element, node);
}
if (this.layout.is_visible(node)) {
view_data.width = element.clientWidth;
view_data.height = element.clientHeight;
} else {
let origin_style = element.getAttribute('style');
element.style = 'visibility: visible; left:0; top:0;';
view_data.width = element.clientWidth;
view_data.height = element.clientHeight;
element.style = origin_style;
//const obj = this.render_node(element, node);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可能需要再测试一下拖动节点到其它 parent 的情况

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

哦,拖动我没用到,还没测过。回头补上

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我在 https://github.com/hqm19/jsmind-react 中,额外引入 jsmind.draggable-node.js 后:

import jsMind from "jsmind"
import "jsmind/style/jsmind.css"
import "jsmind/es6/jsmind.draggable-node.js"

原来可以work的功能就报错了:

Compiled with problems:×
ERROR in ./src/jsmind-test.jsx 9:0-45
Module not found: Error: Package path ./es6/jsmind.draggable-node.js is not exported from package /Users/xlxk/12_code/jzbj/jsmind-react/node_modules/jsmind (see exports field in /Users/xlxk/12_code/jzbj/jsmind-react/node_modules/jsmind/package.json)
``

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/hizzgdev/jsmind/blob/master/docs/zh/plugin-screenshot.md

试试这样

<script>
    import domtoimage from 'dom-to-image';
    import jsMind from 'jsmind'
    import 'jsmind/screenshot'
    import 'jsmind/style/jsmind.css'

    // ...

    var jm = new jsMind(options);
    jm.show(mind_data);
    // export current mindmap to an image
    jm.shoot()
</script>

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

拖动节点会有bug, set_node_font_style 也会有bug。

// 这里如果直接复用 element 来渲染, react 会报错: Warning: render(...): It looks like the React-rendered
// content of this container was removed without using React. This is not supported and will cause errors.
// Instead, call ReactDOM.unmountComponentAtNode to empty a container.
// const obj = this.render_node(element, node);
// 所以新建一个节点来渲染,并替换掉原来的节点
let d = element;
if (view_data.promise_rendered) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为什么只在 update_node 时才需要做这个判断? add 的时候需要吗?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

第一次渲染的时候不会有问题。同一个 容器 第二次渲染的时候才会报错。与这个问题很像: facebook/react#17547 可能与 DocumentFragment 的使用有关。所以只需在 update 时规避这个问题即可

d = $.c('jmnode');
}

render_promise = this.render_node(d, node).then(() => {
if (!!element.parentNode) {
element.parentNode.replaceChild(d, element);
}
view_data.element = d;
element = d;
d.setAttribute('nodeid', node.id);
d.style.visibility = 'hidden';
this._reset_node_custom_style(d, node.data);
});
}
return render_promise.then(() => {
if (this.layout.is_visible(node)) {
view_data.width = element.clientWidth;
view_data.height = element.clientHeight;
} else {
let origin_style = element.getAttribute('style');
element.style = 'visibility: visible; left:0; top:0;';
view_data.width = element.clientWidth;
view_data.height = element.clientHeight;
element.style = origin_style;
}
});
}
select_node(node) {
if (!!this.selected_node) {
Expand Down Expand Up @@ -314,6 +344,7 @@ export class ViewProvider {
this.e_editor.select();
}
edit_node_end() {
let render_promise;
if (this.editing_node != null) {
var node = this.editing_node;
this.editing_node = null;
Expand All @@ -322,13 +353,17 @@ export class ViewProvider {
var topic = this.e_editor.value;
element.style.zIndex = 'auto';
element.removeChild(this.e_editor);
if (util.text.is_empty(topic) || node.topic === topic) {
this.render_node(element, node);
} else {
this.jm.update_node(node.id, topic);

//logger.info('[view.edit_node_end] node:', node);
let obj = this.jm.update_node(node.id, topic);
if (is_promise(obj)) {
render_promise = obj;
}
}
this.e_panel.focus();
const _this = this;
then_if_promise(render_promise, () => {
_this.e_panel.focus();
});
}
get_view_offset() {
var bounds = this.layout.bounds;
Expand Down Expand Up @@ -492,12 +527,18 @@ export class ViewProvider {
} else {
$.t(ele, node.topic);
}
return Promise.resolve();
}
_custom_node_render(ele, node) {
let rendered = this.opts.custom_node_render(this.jm, ele, node);
if (!rendered) {
this._default_node_render(ele, node);
let obj = this.opts.custom_node_render(this.jm, ele, node);
if (is_promise(obj)) {
node._data.view.promise_rendered = true;
return obj;
}
if (!obj) {
return this._default_node_render(ele, node);
}
return Promise.resolve();
}
reset_node_custom_style(node) {
this._reset_node_custom_style(node._data.view.element, node.data);
Expand Down
Loading