Add mindmap editor playground tests

This commit is contained in:
Paulo Gustavo Veiga 2021-12-05 09:25:16 -08:00
parent 02362b28e8
commit 69054fcd9c
40 changed files with 639 additions and 75 deletions

112
libraries/jquery.hotkeys.js Normal file
View File

@ -0,0 +1,112 @@
/*
* jQuery Hotkeys Plugin
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Based upon the plugin by Tzury Bar Yochay:
* http://github.com/tzuryby/hotkeys
*
* Original idea by:
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
*/
/*
* One small change is: now keys are passed by object { keys: '...' }
* Might be useful, when you want to pass some other data to your handler
*/
function initHotKeyPluggin(jQuery){
jQuery.hotkeys = {
version: "0.8",
specialKeys: {
8: "backspace", 9: "tab", 10: "return", 13: "enter", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 186: ";", 191: "/",
220: "\\", 222: "'", 224: "meta"
},
shiftNums: {
"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
".": ">", "/": "?", "\\": "|"
}
};
function keyHandler( handleObj ) {
if ( typeof handleObj.data === "string" ) {
handleObj.data = { keys: handleObj.data };
}
// Only care when a possible input has been specified
if ( !handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== "string" ) {
return;
}
var origHandler = handleObj.handler,
keys = handleObj.data.keys.toLowerCase().split(" "),
textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color", "tel"];
handleObj.handler = function( event ) {
// Don't fire in text-accepting inputs that we didn't directly bind to
if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) {
return;
}
var special = jQuery.hotkeys.specialKeys[ event.keyCode ],
character = String.fromCharCode( event.which ).toLowerCase(),
modif = "", possible = {};
// check combinations (alt|ctrl|shift+anything)
if ( event.altKey && special !== "alt" ) {
modif += "alt+";
}
if ( event.ctrlKey && special !== "ctrl" ) {
modif += "ctrl+";
}
// TODO: Need to make sure this works consistently across platforms
if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
modif += "meta+";
}
if ( event.shiftKey && special !== "shift" ) {
modif += "shift+";
}
if ( special ) {
possible[ modif + special ] = true;
}
if ( character ) {
possible[ modif + character ] = true;
possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
if ( modif === "shift+" ) {
possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
}
}
for ( var i = 0, l = keys.length; i < l; i++ ) {
if ( possible[ keys[i] ] ) {
return origHandler.apply( this, arguments );
}
}
};
}
jQuery.each([ "keydown", "keyup", "keypress" ], function() {
jQuery.event.special[ this ] = { add: keyHandler };
});
};
export default initHotKeyPluggin;

View File

@ -13,7 +13,6 @@
"$assert": "readonly",
"$defined": "readonly",
"$msg": "readonly",
"$notify": "readonly",
"_": "readonly"
},
"plugins": ["only-warn"],

View File

@ -14,4 +14,15 @@ context('Playground', () => {
cy.visit(`${BASE_URL}/viewmode.html`);
cy.matchImageSnapshot('viewmode');
});
it('the playground container.html page should match its snapshot', () => {
cy.visit(`${BASE_URL}/container.html`);
cy.matchImageSnapshot('container');
});
it('the playground editor.html page should match its snapshot', () => {
cy.visit(`${BASE_URL}/editor.html`);
// TODO: wait for #load modal to hide instead of arbitrary wait time
cy.wait(5000);
// TODO: why is the editor appearing twice in the snapshot?
cy.matchImageSnapshot('editor');
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@ -46,6 +46,7 @@ import EventBusDispatcher from './layout/EventBusDispatcher';
import LayoutManager from './layout/LayoutManager';
import INodeModel, { TopicShape } from './model/INodeModel';
import { $notify } from './widget/ToolbarNotifier';
class Designer extends Events {
constructor(options, divElement) {
@ -101,6 +102,9 @@ class Designer extends Events {
TopicEventDispatcher.configure(this.isReadOnly());
this._clipboard = [];
// Hack: There are static reference to designer variable. Needs to be reviewed.
global.designer = this;
}
/**

View File

@ -17,6 +17,7 @@
*/
import { $assert } from '@wisemapping/core-js';
import Keyboard from './Keyboard';
import $ from 'jquery';
class DesignerKeyboard extends Keyboard {
constructor(designer) {

View File

@ -17,6 +17,7 @@
*/
import { $assert, $defined } from '@wisemapping/core-js';
import Events from './Events';
import { $notify } from './widget/ToolbarNotifier';
class DesignerModel extends Events {
constructor(options) {

View File

@ -113,6 +113,8 @@ class DragTopic {
$assert(parent, 'Parent connection node can not be null.');
// Where it should be connected ?
// @todo: This is a hack for the access of the editor. It's required to review why this is needed forcing the declaration of a global variable.
const predict = designer._eventBussDispatcher._layoutManager.predict(
parent.getId(),
this._draggedNode.getId(),

View File

@ -15,14 +15,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import $ from 'jquery';
class Keyboard {
// eslint-disable-next-line class-methods-use-this
addShortcut(shortcuts, callback) {
if (!$.isArray(shortcuts)) {
if (!Array.isArray(shortcuts)) {
shortcuts = [shortcuts];
}
_.each(shortcuts, (shortcut) => {
shortcuts.forEach((shortcut) => {
$(document).bind('keydown', shortcut, callback);
});
}

View File

@ -86,7 +86,7 @@ class MainTopic extends Topic {
/** */
disconnect(workspace) {
this.parent(workspace);
super.disconnect(workspace);
const size = this.getSize();
const model = this.getModel();

View File

@ -15,8 +15,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { $defined } from '@wisemapping/core-js';
import Events from './Events';
import ActionDispatcher from './ActionDispatcher';
import $ from 'jquery';
import initHotKeyPluggin from '@libraries/jquery.hotkeys';
initHotKeyPluggin($);
class MultilineTextEditor extends Events {
constructor() {
@ -25,7 +30,7 @@ class MultilineTextEditor extends Events {
this._timeoutId = -1;
}
_buildEditor() {
static _buildEditor() {
const result = $('<div></div>')
.attr('id', 'textContainer')
.css({
@ -51,7 +56,7 @@ class MultilineTextEditor extends Events {
_registerEvents(containerElem) {
const textareaElem = this._getTextareaElem();
const me = this;
textareaElem.on('keydown', function (event) {
textareaElem.on('keydown', function keydown (event) {
switch (jQuery.hotkeys.specialKeys[event.keyCode]) {
case 'esc':
me.close(false);
@ -128,7 +133,7 @@ class MultilineTextEditor extends Events {
const lines = textElem.val().split('\n');
let maxLineLength = 1;
_.each(lines, (line) => {
lines.forEach((line) => {
if (maxLineLength < line.length) maxLineLength = line.length;
});
@ -143,7 +148,7 @@ class MultilineTextEditor extends Events {
}
isVisible() {
return $defined(this._containerElem) && this._containerElem.css('display') == 'block';
return $defined(this._containerElem) && this._containerElem.css('display') === 'block';
}
_updateModel() {
@ -165,7 +170,7 @@ class MultilineTextEditor extends Events {
this._topic = topic;
if (!this.isVisible()) {
// Create editor ui
const containerElem = this._buildEditor();
const containerElem = MultilineTextEditor._buildEditor();
$('body').append(containerElem);
this._containerElem = containerElem;

View File

@ -75,7 +75,7 @@ class Relationship extends ConnectionLine {
}
setStroke(color, style, opacity) {
this.parent(color, style, opacity);
super.setStroke(color, style, opacity);
this._startArrow.setStrokeColor(color);
}
@ -176,7 +176,7 @@ class Relationship extends ConnectionLine {
workspace.append(this._startArrow);
if (this._endArrow) workspace.append(this._endArrow);
this.parent(workspace);
super.addToWorkspace(workspace);
this._positionArrows();
this.redraw();
}
@ -193,9 +193,10 @@ class Relationship extends ConnectionLine {
workspace.removeChild(this._startArrow);
if (this._endArrow) workspace.removeChild(this._endArrow);
this.parent(workspace);
super.removeFromWorkspace(workspace);
}
// eslint-disable-next-line class-methods-use-this
getType() {
return Relationship.type;
}
@ -248,13 +249,13 @@ class Relationship extends ConnectionLine {
}
setVisibility(value) {
this.parent(value);
super.setVisibility(value);
if (this._showEndArrow) this._endArrow.setVisibility(this._showEndArrow);
this._startArrow.setVisibility(this._showStartArrow && value);
}
setOpacity(opacity) {
this.parent(opacity);
super.setOpacity(opacity);
if (this._showEndArrow) this._endArrow.setOpacity(opacity);
if (this._showStartArrow) this._startArrow.setOpacity(opacity);
}

View File

@ -20,12 +20,12 @@ import PersistenceManager from './PersistenceManager';
class RESTPersistenceManager extends PersistenceManager{
constructor(options) {
this.parent();
$assert(options.documentUrl, 'documentUrl can not be null');
$assert(options.revertUrl, 'revertUrl can not be null');
$assert(options.lockUrl, 'lockUrl can not be null');
$assert(options.session, 'session can not be null');
$assert(options.timestamp, 'timestamp can not be null');
super()
this.documentUrl = options.documentUrl;
this.revertUrl = options.revertUrl;

View File

@ -17,6 +17,7 @@
*/
import { $assert, $defined } from '@wisemapping/core-js';
import * as web2d from '@wisemapping/web2d';
import $ from 'jquery';
import NodeGraph from './NodeGraph';
import TopicConfig from './TopicConfig';
@ -1236,7 +1237,7 @@ class Topic extends NodeGraph {
/** */
createDragNode(layoutManager) {
const result = this.parent(layoutManager);
const result = super.createDragNode(layoutManager);
// Is the node already connected ?
const targetTopic = this.getOutgoingConnectedTopic();

View File

@ -15,6 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { $assert, $defined } from '@wisemapping/core-js';
import Command from '../Command';
class AddFeatureToTopicCommand extends Command {

View File

@ -15,6 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { $assert, $defined } from '@wisemapping/core-js';
import Command from '../Command';
class AddRelationshipCommand extends Command {

View File

@ -15,6 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { $assert, $defined } from '@wisemapping/core-js';
import Command from '../Command';
class AddTopicCommand extends Command {

View File

@ -15,6 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { $assert, $defined } from '@wisemapping/core-js';
import Command from '../Command';
class ChangeFeatureToTopicCommand extends Command {

View File

@ -15,6 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { $assert, $defined } from '@wisemapping/core-js';
import Command from '../Command';
class DeleteCommand extends Command {

View File

@ -15,6 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { $assert, $defined } from '@wisemapping/core-js';
import Command from '../Command';
class DragTopicCommand extends Command {
@ -29,11 +30,13 @@ class DragTopicCommand extends Command {
*/
constructor(topicId, position, order, parentTopic) {
$assert(topicId, 'topicId must be defined');
super();
this._topicsId = topicId;
if ($defined(parentTopic)) this._parentId = parentTopic.getId();
if ($defined(parentTopic)) {
this._parentId = parentTopic.getId();
}
this.parent();
this._position = position;
this._order = order;
}

View File

@ -15,7 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { $defined, $assert } from '@wisemapping/core-js';
import { $assert, $defined } from '@wisemapping/core-js';
import Command from '../Command';
class GenericFunctionCommand extends Command {

View File

@ -15,6 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { $assert, $defined } from '@wisemapping/core-js';
import Command from '../Command';
class MoveControlPointCommand extends Command {

View File

@ -15,6 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { $assert, $defined } from '@wisemapping/core-js';
import Command from '../Command';
class RemoveFeatureFromTopicCommand extends Command {

View File

@ -45,4 +45,4 @@ class BootstrapDialogRequest extends BootstrapDialog {
}
}
export default BootstrapDialogRequest;
export default BootstrapDialogRequest;

View File

@ -17,6 +17,7 @@
* limitations under the License.
*/
import { $assert, $defined } from '@wisemapping/core-js';
import $ from 'jquery';
class INodeModel {
constructor(mindmap) {

View File

@ -17,6 +17,7 @@
*/
import { $assert } from "@wisemapping/core-js";
import PersistenceManager from '../PersistenceManager';
import { $notify } from './ToolbarNotifier';
class IMenu {

View File

@ -16,6 +16,7 @@
* limitations under the License.
*/
import { $assert } from '@wisemapping/core-js';
import $ from 'jquery';
class ToolbarNotifier {

View File

@ -72,7 +72,7 @@ class ToolbarPaneItem extends ToolbarItem {
show() {
if (!this.isVisible()) {
this.parent();
super.show();
this._tip.show();
this.getButtonElem().className = 'buttonExtActive';
}
@ -80,7 +80,7 @@ class ToolbarPaneItem extends ToolbarItem {
hide() {
if (this.isVisible()) {
this.parent();
super.hide();
this._tip.hide();
this.getButtonElem().className = 'buttonExtOn';
}

View File

@ -13,8 +13,8 @@
<ul>
<li><a href="layout.html">Layout Renders</a></li>
<li><a href="viewmode.html">View Only Mindmap:</a> This test load the mindmap canvas in this simple setup in read only mode.</li>
<!-- <li><a href="/map-render/html/container.html">Embedding the mindmap in a page (WIP)</a></li>
<li><a href="/map-render/html/editor.html">Standard Editor (WIP)</a></li> -->
<li><a href="container.html">Embedding the mindmap in a page</a></li>
<li><a href="editor.html">Standard Editor</a></li>
</ul>
</div>
</body>

View File

@ -2,10 +2,15 @@ import TestSuite from './TestSuite';
import BalancedTestSuite from './BalancedTestSuite';
import SymmetricTestSuite from './SymmetricTestSuite';
import FreeTestSuite from './FreeTestSuite';
import Raphael from './lib/raphael-min';
import { drawGrid } from './lib/raphael-plugins';
global.Raphael = Raphael;
global.Raphael.fn.drawGrid = drawGrid;
window.addEventListener('DOMContentLoaded', () => {
new TestSuite();
new BalancedTestSuite();
new SymmetricTestSuite();
new FreeTestSuite();
new TestSuite();
new BalancedTestSuite();
new SymmetricTestSuite();
new FreeTestSuite();
});

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
Raphael.fn.drawGrid = function (x, y, w, h, wv, hv, color) {
export function drawGrid (x, y, w, h, wv, hv, color) {
color = color || '#999';
let path = ['M', x, y, 'L', x + w, y, x + w, y + h, x, y + h, x, y];
const rowHeight = h / hv;

View File

@ -28,8 +28,8 @@
}
</style>
<link rel="icon" href="../images/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="../images/favicon.ico" type="image/x-icon">
<link rel="icon" href="favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
</head>
<body>

View File

@ -2,7 +2,6 @@
<html>
<head>
<base href="../">
<title>WiseMapping - Editor </title>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8"/>
@ -12,31 +11,11 @@
<link rel="stylesheet/less" type="text/css" href="css/editor.less"/>
<script type='text/javascript' src='js/jquery.js'></script>
<script type='text/javascript' src='js/jquery-mousewheel.js'></script>
<script type='text/javascript' src='js/hotkeys.js'></script>
<script type="text/javascript" language="javascript" src="bootstrap/js/bootstrap.js"></script>
<script src="js/less.js" type="text/javascript"></script>
<script type='text/javascript' src='js/mootools-core.js'></script>
<script type='text/javascript' src='js/core.js'></script>
<script type='text/javascript' src='js/less.js'></script>
<link rel="icon" href="images/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
<script type="text/javascript">
var mapId = 'welcome';
$(document).bind('loadcomplete', function(resource) {
var options = loadDesignerOptions();
var designer = buildDesigner(options);
// Load map from XML file persisted on disk...
var persistence = mindplot.PersistenceManager.getInstance();
var mindmap = mindmap = persistence.load(mapId);
designer.loadMap(mindmap);
});
</script>
<link rel="icon" href="favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
</head>
<body>
@ -139,6 +118,5 @@
</div>
<div id="mindplot" onselectstart="return false;"></div>
<script type="text/javascript" src="js/editor.js"></script>
</body>
</html>

View File

@ -2,7 +2,6 @@
<html>
<head>
<base href="../">
<title>WiseMapping - Editor </title>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8"/>
<!--[if lt IE 9]>
@ -132,6 +131,5 @@
</div>
<div id="mindplot" onselectstart="return false;"></div>
<script type="text/javascript" src="js/editor.js"></script>
</body>
</html>

View File

@ -103,8 +103,13 @@ function buildDesigner(options) {
// Configure default persistence manager ...
let persistence;
if (options.persistenceManager) {
if (options.persistenceManager instanceof String) {
persistence = eval(`new ${options.persistenceManager}()`);
if (typeof options.persistenceManager === 'string') {
const managerClass = /^mindplot\.(\w+)/.exec(options.persistenceManager);
if (managerClass){
persistence = new mindplot[managerClass[1]]('samples/{id}.xml');
} else {
persistence = eval(`new ${options.persistenceManager}()`);
}
} else {
persistence = options.persistenceManager;
}
@ -122,14 +127,13 @@ function buildDesigner(options) {
menu.clear();
};
}
return designer;
}
function loadDesignerOptions(jsonConf) {
// Load map options ...
let result;
const me = this;
if (jsonConf) {
$.ajax({
url: jsonConf,
@ -137,10 +141,9 @@ function loadDesignerOptions(jsonConf) {
async: false,
method: 'get',
success(options) {
me.options = options;
result = options;
},
});
result = this.options;
} else {
// Set workspace screen size as default. In this way, resize issues are solved.
const containerSize = {
@ -172,11 +175,11 @@ $(() => {
var mapId = 'welcome';
// Set readonly option ...
var options = loadDesignerOptions();
options.readOnly = true;
// options.readOnly = true;
var designer = buildDesigner(options);
// Load map from XML file persisted on disk...
var persistence = PersistenceManager.getInstance();
const persistence = PersistenceManager.getInstance();
var mindmap = persistence.load(mapId);
designer.loadMap(mindmap);
// from viewmode.html ---------

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@ const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
entry: {
layout: path.resolve(__dirname, './test/playground/layout/context-loader'),
'map-render': path.resolve(__dirname, './test/playground/map-render/js/editor'),
editor: path.resolve(__dirname, './test/playground/map-render/js/editor'),
},
output: {
path: path.resolve(__dirname, 'dist', 'test'),
@ -36,6 +36,7 @@ module.exports = {
/node_modules/,
path.resolve(__dirname, '../../libraries/mootools-core-1.4.5'),
path.resolve(__dirname, '../../libraries/underscore-min'),
/lib\/raphael/ig,
],
},
],
@ -55,8 +56,12 @@ module.exports = {
{ from: 'test/playground/map-render/images', to: 'images' },
{ from: 'test/playground/map-render/icons', to: 'icons' },
{ from: 'test/playground/map-render/css', to: 'css' },
{ from: 'test/playground/map-render/js', to: 'js' },
{ from: 'test/playground/map-render/samples', to: 'samples' },
{ from: 'test/playground/map-render/bootstrap', to: 'bootstrap' },
{ from: 'test/playground/index.html', to: 'index.html' },
{ from: 'test/playground/map-render/html/container.json', to: 'html/container.json' },
{ from: 'test/playground/map-render/html/container.html', to: 'container.html' },
],
}),
new HtmlWebpackPlugin({
@ -65,9 +70,19 @@ module.exports = {
template: 'test/playground/layout/index.html',
}),
new HtmlWebpackPlugin({
chunks: ['map-render'],
chunks: ['editor'],
filename: 'viewmode.html',
template: 'test/playground/map-render/html/viewmode.html',
}),
new HtmlWebpackPlugin({
chunks: ['editor'],
filename: 'embedded.html',
template: 'test/playground/map-render/html/embedded.html',
}),
new HtmlWebpackPlugin({
chunks: ['editor'],
filename: 'editor.html',
template: 'test/playground/map-render/html/editor.html',
}),
],
};