import { __version__, logger, EventType, Direction, LogLevel, } from './jsmind.common.js' import { merge_option } from './jsmind.option.js' import { Mind } from './jsmind.mind.js' import { Node } from './jsmind.node.js' import { DataProvider } from './jsmind.data_provider.js' import { LayoutProvider } from './jsmind.layout_provider.js' import { ViewProvider } from './jsmind.view_provider.js' import { ShortcutProvider } from './jsmind.shortcut_provider.js' import { Plugin, register as _register_plugin, apply as apply_plugins, } from './jsmind.plugin.js' import { format } from './jsmind.format.js' import { $ } from './jsmind.dom.js' import { util as _util } from './jsmind.util.js' export default class jsMind { static mind = Mind static node = Node static direction = Direction static event_type = EventType static $ = $ static plugin = Plugin static register_plugin = _register_plugin static util = _util constructor(options) { jsMind.current = this this.options = merge_option(options) logger.level(LogLevel[this.options.log_level]) this.version = __version__ this.initialized = false this.mind = null this.event_handles = [] this.init() } init() { if (!!this.initialized) { return } this.initialized = true var opts_layout = { mode: this.options.mode, hspace: this.options.layout.hspace, vspace: this.options.layout.vspace, pspace: this.options.layout.pspace, cousin_space: this.options.layout.cousin_space, } var opts_view = { container: this.options.container, support_html: this.options.support_html, engine: this.options.view.engine, enable_device_pixel_ratio: this.options.view.enable_device_pixel_ratio, hmargin: this.options.view.hmargin, vmargin: this.options.view.vmargin, line_width: this.options.view.line_width, line_color: this.options.view.line_color, line_style: this.options.view.line_style, custom_line_render: this.options.view.custom_line_render, draggable: this.options.view.draggable, hide_scrollbars_when_draggable: this.options.view.hide_scrollbars_when_draggable, node_overflow: this.options.view.node_overflow, zoom: this.options.view.zoom, custom_node_render: this.options.view.custom_node_render, expander_style: this.options.view.expander_style, } // create instance of function provider this.data = new DataProvider(this) this.layout = new LayoutProvider(this, opts_layout) this.view = new ViewProvider(this, opts_view) this.shortcut = new ShortcutProvider(this, this.options.shortcut) this.data.init() this.layout.init() this.view.init() this.shortcut.init() this._event_bind() apply_plugins(this, this.options.plugin) } get_editable() { return this.options.editable } enable_edit() { this.options.editable = true } disable_edit() { this.options.editable = false } get_view_draggable() { return this.options.view.draggable } enable_view_draggable() { this.options.view.draggable = true this.view.setup_canvas_draggable(true) } disable_view_draggable() { this.options.view.draggable = false this.view.setup_canvas_draggable(false) } // options are 'mousedown', 'click', 'dblclick', 'mousewheel' enable_event_handle(event_handle) { this.options.default_event_handle[ 'enable_' + event_handle + '_handle' ] = true } // options are 'mousedown', 'click', 'dblclick', 'mousewheel' disable_event_handle(event_handle) { this.options.default_event_handle[ 'enable_' + event_handle + '_handle' ] = false } set_theme(theme) { var theme_old = this.options.theme this.options.theme = !!theme ? theme : null if (theme_old != this.options.theme) { this.view.reset_theme() this.view.reset_custom_style() } } _event_bind() { this.view.add_event(this, 'mousedown', this.mousedown_handle) this.view.add_event(this, 'click', this.click_handle) this.view.add_event(this, 'dblclick', this.dblclick_handle) this.view.add_event(this, 'mousewheel', this.mousewheel_handle, true) } mousedown_handle(e) { if (!this.options.default_event_handle['enable_mousedown_handle']) { return } var element = e.target || event.srcElement var node_id = this.view.get_binded_nodeid(element) if (!!node_id) { if (this.view.is_node(element)) { this.select_node(node_id) } } else { this.select_clear() } } click_handle(e) { if (!this.options.default_event_handle['enable_click_handle']) { return } var element = e.target || event.srcElement var is_expander = this.view.is_expander(element) if (is_expander) { var node_id = this.view.get_binded_nodeid(element) if (!!node_id) { this.toggle_node(node_id) } } } dblclick_handle(e) { if (!this.options.default_event_handle['enable_dblclick_handle']) { return } if (this.get_editable()) { var element = e.target || event.srcElement var is_node = this.view.is_node(element) if (is_node) { var node_id = this.view.get_binded_nodeid(element) if (!!node_id) { this.begin_edit(node_id) } } } } // Use [Ctrl] + Mousewheel, to zoom in/out. mousewheel_handle(e) { // Test if mousewheel option is enabled and Ctrl key is pressed. if ( !this.options.default_event_handle['enable_mousewheel_handle'] || !e.ctrlKey ) { return } var evt = e || event // Avoid default page scrolling behavior. evt.preventDefault() if (evt.deltaY < 0) { this.view.zoom_in(evt) // wheel down } else { this.view.zoom_out(evt) } } begin_edit(node) { if (!Node.is_node(node)) { var the_node = this.get_node(node) if (!the_node) { logger.error('the node[id=' + node + '] can not be found.') return false } else { return this.begin_edit(the_node) } } if (this.get_editable()) { this.view.edit_node_begin(node) } else { logger.error('fail, this mind map is not editable.') return } } end_edit() { this.view.edit_node_end() } toggle_node(node) { if (!Node.is_node(node)) { var the_node = this.get_node(node) if (!the_node) { logger.error('the node[id=' + node + '] can not be found.') return } else { return this.toggle_node(the_node) } } if (node.isroot) { return } this.view.save_location(node) this.layout.toggle_node(node) this.view.relayout() this.view.restore_location(node) } expand_node(node) { if (!Node.is_node(node)) { var the_node = this.get_node(node) if (!the_node) { logger.error('the node[id=' + node + '] can not be found.') return } else { return this.expand_node(the_node) } } if (node.isroot) { return } this.view.save_location(node) this.layout.expand_node(node) this.view.relayout() this.view.restore_location(node) } collapse_node(node) { if (!Node.is_node(node)) { var the_node = this.get_node(node) if (!the_node) { logger.error('the node[id=' + node + '] can not be found.') return } else { return this.collapse_node(the_node) } } if (node.isroot) { return } this.view.save_location(node) this.layout.collapse_node(node) this.view.relayout() this.view.restore_location(node) } expand_all() { this.layout.expand_all() this.view.relayout() } collapse_all() { this.layout.collapse_all() this.view.relayout() } expand_to_depth(depth) { this.layout.expand_to_depth(depth) this.view.relayout() } _reset() { this.view.reset() this.layout.reset() this.data.reset() } _show(mind, skip_centering) { var m = mind || format.node_array.example this.mind = this.data.load(m) if (!this.mind) { logger.error('data.load error') return } else { logger.debug('data.load ok') } this.view.load() logger.debug('view.load ok') this.layout.layout() logger.debug('layout.layout ok') this.view.show(!skip_centering) logger.debug('view.show ok') this.invoke_event_handle(EventType.show, { data: [mind] }) } show(mind, skip_centering) { this._reset() this._show(mind, skip_centering) } get_meta() { return { name: this.mind.name, author: this.mind.author, version: this.mind.version, } } get_data(data_format) { var df = data_format || 'node_tree' return this.data.get_data(df) } get_root() { return this.mind.root } get_node(node) { if (Node.is_node(node)) { return node } return this.mind.get_node(node) } add_node(parent_node, node_id, topic, data, direction) { if (this.get_editable()) { var the_parent_node = this.get_node(parent_node) var dir = Direction.of(direction) if (dir === undefined) { dir = this.layout.calculate_next_child_direction(the_parent_node) } 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 node } else { logger.error('fail, this mind map is not editable') return null } } insert_node_before(node_before, node_id, topic, data, direction) { if (this.get_editable()) { var the_node_before = this.get_node(node_before) var dir = Direction.of(direction) if (dir === undefined) { dir = this.layout.calculate_next_child_direction( the_node_before.parent ) } 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, }) } return node } else { logger.error('fail, this mind map is not editable') return null } } insert_node_after(node_after, node_id, topic, data, direction) { if (this.get_editable()) { var the_node_after = this.get_node(node_after) var dir = Direction.of(direction) if (dir === undefined) { dir = this.layout.calculate_next_child_direction( the_node_after.parent ) } 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, }) } return node } else { logger.error('fail, this mind map is not editable') return null } } remove_node(node) { if (!Node.is_node(node)) { var the_node = this.get_node(node) if (!the_node) { logger.error('the node[id=' + node + '] can not be found.') return false } else { return this.remove_node(the_node) } } if (this.get_editable()) { if (node.isroot) { logger.error('fail, can not remove root node') return false } var node_id = node.id var parent_id = node.parent.id var parent_node = this.get_node(parent_id) this.view.save_location(parent_node) this.view.remove_node(node) this.mind.remove_node(node) this.layout.layout() this.view.show(false) this.view.restore_location(parent_node) this.invoke_event_handle(EventType.edit, { evt: 'remove_node', data: [node_id], node: parent_id, }) return true } else { logger.error('fail, this mind map is not editable') return false } } update_node(node_id, 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 } 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, }) } } else { logger.error('fail, this mind map is not editable') return } } move_node(node_id, before_id, parent_id, direction) { if (this.get_editable()) { var node = this.get_node(node_id) var updated_node = this.mind.move_node( node, before_id, parent_id, direction ) if (!!updated_node) { this.view.update_node(updated_node) this.layout.layout() this.view.show(false) this.invoke_event_handle(EventType.edit, { evt: 'move_node', data: [node_id, before_id, parent_id, direction], node: node_id, }) } } else { logger.error('fail, this mind map is not editable') return } } select_node(node) { if (!Node.is_node(node)) { var the_node = this.get_node(node) if (!the_node) { logger.error('the node[id=' + node + '] can not be found.') return } else { return this.select_node(the_node) } } if (!this.layout.is_visible(node)) { return } this.mind.selected = node this.view.select_node(node) this.invoke_event_handle(EventType.select, { evt: 'select_node', data: [], node: node.id, }) } get_selected_node() { if (!!this.mind) { return this.mind.selected } else { return null } } select_clear() { if (!!this.mind) { this.mind.selected = null this.view.select_clear() } } is_node_visible(node) { return this.layout.is_visible(node) } scroll_node_to_center(node) { if (!Node.is_node(node)) { var the_node = this.get_node(node) if (!the_node) { logger.error('the node[id=' + node + '] can not be found.') } else { this.scroll_node_to_center(the_node) } return } this.view.center_node(node) } find_node_before(node) { if (!Node.is_node(node)) { var the_node = this.get_node(node) if (!the_node) { logger.error('the node[id=' + node + '] can not be found.') return } else { return this.find_node_before(the_node) } } if (node.isroot) { return null } var n = null if (node.parent.isroot) { var c = node.parent.children var prev = null var ni = null for (var i = 0; i < c.length; i++) { ni = c[i] if (node.direction === ni.direction) { if (node.id === ni.id) { n = prev } prev = ni } } } else { n = this.mind.get_node_before(node) } return n } find_node_after(node) { if (!Node.is_node(node)) { var the_node = this.get_node(node) if (!the_node) { logger.error('the node[id=' + node + '] can not be found.') return } else { return this.find_node_after(the_node) } } if (node.isroot) { return null } var n = null if (node.parent.isroot) { var c = node.parent.children var found = false var ni = null for (var i = 0; i < c.length; i++) { ni = c[i] if (node.direction === ni.direction) { if (found) { n = ni break } if (node.id === ni.id) { found = true } } } } else { n = this.mind.get_node_after(node) } return n } set_node_color(node_id, bg_color, fg_color) { if (this.get_editable()) { var node = this.mind.get_node(node_id) if (!!node) { if (!!bg_color) { node.data['background-color'] = bg_color } if (!!fg_color) { node.data['foreground-color'] = fg_color } this.view.reset_node_custom_style(node) } } else { logger.error('fail, this mind map is not editable') return null } } set_node_font_style(node_id, size, weight, style) { if (this.get_editable()) { var node = this.mind.get_node(node_id) if (!!node) { if (!!size) { node.data['font-size'] = size } if (!!weight) { node.data['font-weight'] = weight } if (!!style) { node.data['font-style'] = style } this.view.reset_node_custom_style(node) this.view.update_node(node) this.layout.layout() this.view.show(false) } } else { logger.error('fail, this mind map is not editable') return null } } set_node_background_image(node_id, image, width, height, rotation) { if (this.get_editable()) { var node = this.mind.get_node(node_id) if (!!node) { if (!!image) { node.data['background-image'] = image } if (!!width) { node.data['width'] = width } if (!!height) { node.data['height'] = height } if (!!rotation) { node.data['background-rotation'] = rotation } this.view.reset_node_custom_style(node) this.view.update_node(node) this.layout.layout() this.view.show(false) } } else { logger.error('fail, this mind map is not editable') return null } } set_node_background_rotation(node_id, rotation) { if (this.get_editable()) { var node = this.mind.get_node(node_id) if (!!node) { if (!node.data['background-image']) { logger.error( 'fail, only can change rotation angle of node with background image' ) return null } node.data['background-rotation'] = rotation this.view.reset_node_custom_style(node) this.view.update_node(node) this.layout.layout() this.view.show(false) } } else { logger.error('fail, this mind map is not editable') return null } } resize() { this.view.resize() } // callback(type ,data) add_event_listener(callback) { if (typeof callback === 'function') { this.event_handles.push(callback) } } clear_event_listener() { this.event_handles = [] } invoke_event_handle(type, data) { var j = this $.w.setTimeout(function () { j._invoke_event_handle(type, data) }, 0) } _invoke_event_handle(type, data) { var l = this.event_handles.length for (var i = 0; i < l; i++) { this.event_handles[i](type, data) } } static show(options, mind) { logger.warn( '`jsMind.show(options, mind)` is deprecated, please use `jm = new jsMind(options); jm.show(mind);` instead' ) var _jm = new jsMind(options) _jm.show(mind) return _jm } }