Skip to content

Commit

Permalink
[Features] options for plugins (#472)
Browse files Browse the repository at this point in the history
  • Loading branch information
hizzgdev committed May 29, 2023
1 parent 2d0d1ab commit 78b1d7a
Show file tree
Hide file tree
Showing 10 changed files with 600 additions and 991 deletions.
470 changes: 4 additions & 466 deletions example/2_features.html

Large diffs are not rendered by default.

455 changes: 455 additions & 0 deletions example/2_features.js

Large diffs are not rendered by default.

458 changes: 1 addition & 457 deletions example/2_features_cn.html

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions src/jsmind.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export default class jsMind {
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,
};
// create instance of function provider
this.data = new DataProvider(this);
Expand All @@ -77,7 +78,7 @@ export default class jsMind {

this._event_bind();

apply_plugins(this);
apply_plugins(this, this.options);
}
get_editable() {
return this.options.editable;
Expand Down Expand Up @@ -174,9 +175,9 @@ export default class jsMind {
evt.preventDefault();

if (evt.deltaY < 0) {
this.view.zoomIn(evt); // wheel down
this.view.zoom_in(evt); // wheel down
} else {
this.view.zoomOut(evt);
this.view.zoom_out(evt);
}
}
begin_edit(node) {
Expand Down
5 changes: 5 additions & 0 deletions src/jsmind.option.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ const default_options = {
draggable: false, // drag the mind map with your mouse, when it's larger that the container
hide_scrollbars_when_draggable: false, // hide container scrollbars, when mind map is larger than container and draggable option is true.
node_overflow: 'hidden', // hidden or wrap
zoom: {
min: 0.5,
max: 2.1,
step: 0.1,
},
},
layout: {
hspace: 30,
Expand Down
33 changes: 18 additions & 15 deletions src/jsmind.plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,35 @@ const plugin_data = {
};

export function register(plugin) {
if (!plugin instanceof Plugin) {
throw new Error('plugin ' + plugin + ' is not a supported plugin');
if (!(plugin instanceof Plugin)) {
throw new Error('can not register plugin, it is not an instance of Plugin');
}
if (plugin_data.plugins.map(p => p.name).includes(plugin.name)) {
throw new Error('can not register plugin ' + plugin.name + ': plugin name already exist');
}
plugin_data.plugins.push(plugin);
}

export function apply(jm) {
export function apply(jm, options) {
$.w.setTimeout(function () {
_apply(jm);
_apply(jm, options);
}, 0);
}

function _apply(jm) {
var l = plugin_data.plugins.length;
var fn_init = null;
for (var i = 0; i < l; i++) {
fn_init = plugin_data.plugins[i].init;
if (typeof fn_init === 'function') {
fn_init(jm);
}
}
function _apply(jm, options) {
plugin_data.plugins.forEach(p => p.fn_init(jm, options[p.name]));
}

export class Plugin {
constructor(name, init) {
// fn_init(jm, options)
constructor(name, fn_init) {
if (!name) {
throw new Error('plugin must has a name');
}
if (!fn_init || typeof fn_init !== 'function') {
throw new Error('plugin must has an init function');
}
this.name = name;
this.init = init;
this.fn_init = fn_init;
}
}
10 changes: 4 additions & 6 deletions src/jsmind.view_provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class ViewProvider {
this._initialized = false;
}
init() {
logger.debug(this.opts);
logger.debug('view.init');

this.container = $.i(this.opts.container) ? this.opts.container : $.g(this.opts.container);
Expand Down Expand Up @@ -56,9 +57,6 @@ export class ViewProvider {
this.e_editor.type = 'text';

this.zoom_current = 1;
this.zoom_step = 0.1;
this.zoom_min = 0.5;
this.zoom_max = 2.1;

var v = this;
$.on(this.e_editor, 'keydown', function (e) {
Expand Down Expand Up @@ -340,13 +338,13 @@ export class ViewProvider {
this.jm.invoke_event_handle(EventType.resize, { data: [] });
}
zoom_in(e) {
return this.set_zoom(this.zoom_current + this.zoom_step, e);
return this.set_zoom(this.zoom_current + this.opts.zoom.step, e);
}
zoom_out(e) {
return this.set_zoom(this.zoom_current - this.zoom_step, e);
return this.set_zoom(this.zoom_current - this.opts.zoom.step, e);
}
set_zoom(zoom, e) {
if (zoom < this.zoom_min || zoom > this.zoom_max) {
if (zoom < this.opts.zoom.min || zoom > this.opts.zoom.max) {
return false;
}
let e_panel_rect = this.e_panel.getBoundingClientRect();
Expand Down
70 changes: 35 additions & 35 deletions src/plugins/jsmind.draggable-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ if (!jsMind) {
throw new Error('jsMind is not defined');
}

const jm = jsMind;
const $ = jm.$;
const $ = jsMind.$;

const clear_selection =
'getSelection' in $.w
Expand All @@ -22,7 +21,7 @@ const clear_selection =
$.d.selection.empty();
};

const options = {
const DEFAULT_OPTIONS = {
line_width: 5,
line_color: 'rgba(0,0,0,0.3)',
lookup_delay: 500,
Expand All @@ -32,8 +31,9 @@ const options = {
};

class DraggableNode {
constructor(jm) {
constructor(jm, options) {
this.jm = jm;
this.options = jsMind.util.json.merge(options || {}, DEFAULT_OPTIONS);
this.e_canvas = null;
this.canvas_ctx = null;
this.shadow = null;
Expand All @@ -50,7 +50,7 @@ class DraggableNode {
this.hlookup_timer = 0;
this.capture = false;
this.moved = false;
this.canvas_draggable = this.jm.get_view_draggable();
this.canvas_draggable = jm.get_view_draggable();
this.view_panel = jm.view.e_panel;
this.view_panel_rect = null;
}
Expand Down Expand Up @@ -102,8 +102,8 @@ class DraggableNode {
}
_magnet_shadow(node) {
if (!!node) {
this.canvas_ctx.lineWidth = options.line_width;
this.canvas_ctx.strokeStyle = options.line_color;
this.canvas_ctx.lineWidth = this.options.line_width;
this.canvas_ctx.strokeStyle = this.options.line_color;
this.canvas_ctx.lineCap = 'round';
this._clear_lines();
this._canvas_lineto(node.sp.x, node.sp.y, node.np.x, node.np.y);
Expand Down Expand Up @@ -157,15 +157,15 @@ class DraggableNode {
continue;
}
distance = Math.abs(sx - nl.x - ns.w) + Math.abs(sy + sh / 2 - nl.y - ns.h / 2);
np = { x: nl.x + ns.w - options.line_width, y: nl.y + ns.h / 2 };
sp = { x: sx + options.line_width, y: sy + sh / 2 };
np = { x: nl.x + ns.w - this.options.line_width, y: nl.y + ns.h / 2 };
sp = { x: sx + this.options.line_width, y: sy + sh / 2 };
} else {
if (nl.x - sx - sw <= 0) {
continue;
}
distance = Math.abs(sx + sw - nl.x) + Math.abs(sy + sh / 2 - nl.y - ns.h / 2);
np = { x: nl.x + options.line_width, y: nl.y + ns.h / 2 };
sp = { x: sx + sw - options.line_width, y: sy + sh / 2 };
np = { x: nl.x + this.options.line_width, y: nl.y + ns.h / 2 };
sp = { x: sx + sw - this.options.line_width, y: sy + sh / 2 };
}
if (distance < min_distance) {
closest_node = node;
Expand Down Expand Up @@ -248,9 +248,9 @@ class DraggableNode {
this.view_panel_rect = this.view_panel.getBoundingClientRect();
this.active_node = node;
this.offset_x =
(e.clientX || e.touches[0].clientX) / jview.actualZoom - el.offsetLeft;
(e.clientX || e.touches[0].clientX) / jview.zoom_current - el.offsetLeft;
this.offset_y =
(e.clientY || e.touches[0].clientY) / jview.actualZoom - el.offsetTop;
(e.clientY || e.touches[0].clientY) / jview.zoom_current - el.offsetTop;
this.client_hw = Math.floor(el.clientWidth / 2);
this.client_hh = Math.floor(el.clientHeight / 2);
if (this.hlookup_delay != 0) {
Expand All @@ -264,8 +264,8 @@ class DraggableNode {
jd.hlookup_delay = 0;
jd.hlookup_timer = $.w.setInterval(function () {
jd.lookup_close_node.call(jd);
}, options.lookup_interval);
}, options.lookup_delay);
}, jd.options.lookup_interval);
}, this.options.lookup_delay);
this.capture = true;
}
}
Expand All @@ -280,41 +280,41 @@ class DraggableNode {
this.moved = true;
clear_selection();
var jview = this.jm.view;
var px = (e.clientX || e.touches[0].clientX) / jview.actualZoom - this.offset_x;
var py = (e.clientY || e.touches[0].clientY) / jview.actualZoom - this.offset_y;
var px = (e.clientX || e.touches[0].clientX) / jview.zoom_current - this.offset_x;
var py = (e.clientY || e.touches[0].clientY) / jview.zoom_current - this.offset_y;
// scrolling container axisY if drag nodes exceeding container
if (
e.clientY - this.view_panel_rect.top < options.scrolling_trigger_width &&
this.view_panel.scrollTop > options.scrolling_step_length
e.clientY - this.view_panel_rect.top < this.options.scrolling_trigger_width &&
this.view_panel.scrollTop > this.options.scrolling_step_length
) {
this.view_panel.scrollBy(0, -options.scrolling_step_length);
this.offset_y += options.scrolling_step_length / jview.actualZoom;
this.view_panel.scrollBy(0, -this.options.scrolling_step_length);
this.offset_y += this.options.scrolling_step_length / jview.zoom_current;
} else if (
this.view_panel_rect.bottom - e.clientY < options.scrolling_trigger_width &&
this.view_panel_rect.bottom - e.clientY < this.options.scrolling_trigger_width &&
this.view_panel.scrollTop <
this.view_panel.scrollHeight -
this.view_panel_rect.height -
options.scrolling_step_length
this.options.scrolling_step_length
) {
this.view_panel.scrollBy(0, options.scrolling_step_length);
this.offset_y -= options.scrolling_step_length / jview.actualZoom;
this.view_panel.scrollBy(0, this.options.scrolling_step_length);
this.offset_y -= this.options.scrolling_step_length / jview.zoom_current;
}
// scrolling container axisX if drag nodes exceeding container
if (
e.clientX - this.view_panel_rect.left < options.scrolling_trigger_width &&
this.view_panel.scrollLeft > options.scrolling_step_length
e.clientX - this.view_panel_rect.left < this.options.scrolling_trigger_width &&
this.view_panel.scrollLeft > this.options.scrolling_step_length
) {
this.view_panel.scrollBy(-options.scrolling_step_length, 0);
this.offset_x += options.scrolling_step_length / jview.actualZoom;
this.view_panel.scrollBy(-this.options.scrolling_step_length, 0);
this.offset_x += this.options.scrolling_step_length / jview.zoom_current;
} else if (
this.view_panel_rect.right - e.clientX < options.scrolling_trigger_width &&
this.view_panel_rect.right - e.clientX < this.options.scrolling_trigger_width &&
this.view_panel.scrollLeft <
this.view_panel.scrollWidth -
this.view_panel_rect.width -
options.scrolling_step_length
this.options.scrolling_step_length
) {
this.view_panel.scrollBy(options.scrolling_step_length, 0);
this.offset_x -= options.scrolling_step_length / jview.actualZoom;
this.view_panel.scrollBy(this.options.scrolling_step_length, 0);
this.offset_x -= this.options.scrolling_step_length / jview.zoom_current;
}
this.shadow.style.left = px + 'px';
this.shadow.style.top = py + 'px';
Expand Down Expand Up @@ -388,8 +388,8 @@ class DraggableNode {
}
}

var draggable_plugin = new jm.plugin('draggable_node', function (jm) {
var jd = new DraggableNode(jm);
var draggable_plugin = new jsMind.plugin('draggable_node', function (jm, options) {
var jd = new DraggableNode(jm, options);
jd.init();
jm.add_event_listener(function (type, data) {
jd.jm_event_handle.call(jd, type, data);
Expand Down
65 changes: 65 additions & 0 deletions tests/unit/jsmind.plugin.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { expect, test, jest } from '@jest/globals';
import { Plugin, register, apply } from '../../src/jsmind.plugin.js';

test('register and apply plugins', async () => {
const plugin1_fn_init = jest.fn();
const plugin2_fn_init = jest.fn();

const plugin1 = new Plugin('a', plugin1_fn_init);
const plugin2 = new Plugin('b', plugin2_fn_init);

register(plugin1);
register(plugin2);

const mock_jm = { jm: 'mock' };
const mock_options = {
a: {
a: 'A',
},
b: {
b: 'B',
},
};
apply(mock_jm, mock_options);

await new Promise(r => setTimeout(r, 10));
expect(plugin1_fn_init).toBeCalledWith(mock_jm, mock_options.a);
expect(plugin2_fn_init).toBeCalledWith(mock_jm, mock_options.b);
});

test('constructor invalid plugins', () => {
expect(() => {
new Plugin();
}).toThrow(new Error('plugin must has a name'));
expect(() => {
new Plugin(null);
}).toThrow(new Error('plugin must has a name'));
expect(() => {
new Plugin('', {});
}).toThrow(new Error('plugin must has a name'));
expect(() => {
new Plugin('a');
}).toThrow(new Error('plugin must has an init function'));
expect(() => {
new Plugin('a', {});
}).toThrow(new Error('plugin must has an init function'));
});

test('register invalid plugins', () => {
expect(() => {
register({});
}).toThrow(new Error('can not register plugin, it is not an instance of Plugin'));
expect(() => {
register('a');
}).toThrow(new Error('can not register plugin, it is not an instance of Plugin'));

const mock_fn_init = jest.fn();

const plugin1 = new Plugin('x', mock_fn_init);
const plugin2 = new Plugin('x', mock_fn_init);

register(plugin1);
expect(() => {
register(plugin2);
}).toThrow(new Error('can not register plugin x: plugin name already exist'));
});
18 changes: 9 additions & 9 deletions tests/unit/jsmind.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,29 +273,29 @@ describe('event handler', () => {
test('mousewheel', () => {
const evt = { preventDefault: jest.fn(), ctrlKey: true };
const jsmind = create_fake_mind();
jsmind.view = { zoomIn: jest.fn(), zoomOut: jest.fn() };
jsmind.view = { zoom_in: jest.fn(), zoom_out: jest.fn() };

jsmind.enable_event_handle('mousewheel');
evt.deltaY = 1;
jsmind.mousewheel_handle(evt);
expect(jsmind.view.zoomOut).toBeCalledTimes(1);
expect(jsmind.view.zoomIn).toBeCalledTimes(0);
expect(jsmind.view.zoom_out).toBeCalledTimes(1);
expect(jsmind.view.zoom_in).toBeCalledTimes(0);

evt.deltaY = -1;
jsmind.mousewheel_handle(evt);
expect(jsmind.view.zoomOut).toBeCalledTimes(1);
expect(jsmind.view.zoomIn).toBeCalledTimes(1);
expect(jsmind.view.zoom_out).toBeCalledTimes(1);
expect(jsmind.view.zoom_in).toBeCalledTimes(1);

evt.ctrlKey = false;
jsmind.mousewheel_handle(evt);
expect(jsmind.view.zoomIn).toBeCalledTimes(1);
expect(jsmind.view.zoomOut).toBeCalledTimes(1);
expect(jsmind.view.zoom_in).toBeCalledTimes(1);
expect(jsmind.view.zoom_out).toBeCalledTimes(1);

evt.ctrlKey = true;
jsmind.disable_event_handle('mousewheel');
jsmind.mousewheel_handle(evt);
expect(jsmind.view.zoomIn).toBeCalledTimes(1);
expect(jsmind.view.zoomOut).toBeCalledTimes(1);
expect(jsmind.view.zoom_in).toBeCalledTimes(1);
expect(jsmind.view.zoom_out).toBeCalledTimes(1);
});
});

Expand Down

0 comments on commit 78b1d7a

Please sign in to comment.