define([
'require'
, 'jquery'
, 'nbextensions/visualpython/src/common/constant'
, 'nbextensions/visualpython/src/container/vpContainer'
], function (requirejs, $, vpConst, vpContainer) {
"use strict";
/**
* TEST: minju: api board test
* API Board
* 1. set draggable event to .vp-ab-box
* 2. set drag box information
* 1)
*/
/** FIXME: move to constants.js : VpBoard const variables */
const VP_PARENT_CONTAINER = ''; //'.vp-option-page';
const VP_DRAGGABLE = '.vp-ab-box';
const VP_DRAGGABLE_INBOX = '.vp-ab-inbox';
const VP_CONTAINER = '.vp-ab-container';
const VP_DROPPABLE_BOX = '.vp-ab-droppable-box';
const VP_BOX_MENU_ID = '#vp_abBoxMenu';
const VP_BOX_SELECTOR_ID = '#vp_abBoxSelector';
const VP_BOX_MENU_TAG = ``;
const VP_BOX_SELECTOR_TAG = ``;
const VP_CONTAINER_TAG = `
`;
const VP_DRAGGABLE_TAG = `{code}`
const VP_BOX_ID_PREFIX = 'vp_ab_box_';
/**
* @class VpBoard
* @param {string} parentContainer
* @constructor
*/
var VpBoard = function (page, parentContainer = VP_PARENT_CONTAINER) {
this.page = page;
/**
* api stack global variable
* format:
* {
* code: 'df' / '.concat(opt=1)' / '[]',
* type: 'var' / 'oper' / 'brac' / 'api' / 'code'
* metadata: {},
*
* // to load/save
* prev: -1,
* next: -1,
* child: -1 / left: -1, right: -1
* }
*/
this._apiBoard = {};
this._apiHead = 0;
this._boardNewNumber = Object.keys(this._apiBoard).length;
this.parentContainer = page.wrapSelector(parentContainer); // '#vp_optionBook'
this.draggable = VP_DRAGGABLE;
this.draggableInbox = VP_DRAGGABLE_INBOX;
this.container = VP_CONTAINER;
this.droppableBox = VP_DROPPABLE_BOX;
this.popBoxSelector = VP_BOX_SELECTOR_ID;
this.popBoxMenu = VP_BOX_MENU_ID;
this.containerTag = VP_CONTAINER_TAG;
this.draggableTag = VP_DRAGGABLE_TAG;
}
VpBoard.prototype.clear = function() {
this._apiHead = 0;
this._apiBoard = {};
this._boardNewNumber = 0;
$(this.parentContainer).find(this.draggable).remove();
this.syncApiStack();
}
/**
* Clear apiStack Link
*/
VpBoard.prototype.clearLink = function() {
Object.keys(this._apiBoard).forEach(key => {
delete this._apiBoard[key].next;
delete this._apiBoard[key].child;
delete this._apiBoard[key].left;
delete this._apiBoard[key].right;
delete this._apiBoard[key].checksum;
})
}
/**
* remove not using keys
*/
VpBoard.prototype.clearUnchecked = function() {
var keys = Object.keys(this._apiBoard);
for(var i = 0; i < keys.length; i++) {
if (this._apiBoard[keys[i]].checksum != true) {
delete this._apiBoard[keys[i]];
}
}
}
VpBoard.prototype.loadBoard = function(apiBoard) {
this._apiBoard = apiBoard;
// find last key
var boardKeys = Object.keys(apiBoard).sort().reverse();
this._apiHead = 0;
this._boardNewNumber = boardKeys[0] + 1;
}
/**
* Load Box recursively
* @param {number} pointer
* @param {string} prevBox Tag Element string
*/
VpBoard.prototype.loadBox = function(pointer, prevBox = '') {
if (pointer == undefined || pointer < 0 || Object.keys(this._apiBoard).length <= 0) {
return "";
}
var block = this._apiBoard[pointer];
if (block == undefined) return "";
var box = VP_DRAGGABLE_TAG;
box = box.replace('{id}', VP_BOX_ID_PREFIX + pointer);
box = box.replaceAll('{type}', block.type);
box = box.replace('{idx}', pointer);
var code = block.code;
if (block.type == "brac") {
var child = this.loadBox(block.child);
// brackets [{child}] ({child})
if (block.code.indexOf('{child}') < 0) {
code = `${block.code}
${child}`
} else {
var codes = block.code.split('{child}');
code = `${codes[0]}
${child}
${codes[1]}`
}
} else if (block.type == "oper") {
// operator + - / * & | == != < <= > >=
var left = this.loadBox(block.left);
var right = this.loadBox(block.right);
// code = `(${left} ${block.code} ${right})`
code = `
(
${left}
${block.code}
${right}
)
`;
} else if (block.type == "var" || block.type == "api") {
var left = this.loadBox(block.left);
var right = this.loadBox(block.right);
// code = 'api(${left})${right}'
var codes = block.code.split('{left}').join('{}').split('{right}').join('{}').split('{}');
code = `
${codes[0]}
${left}`;
if (codes[1] != undefined && codes[1] != '') {
code += `${codes[1]}`;
}
code += `${right}`;
if (codes[2] != undefined && codes[2] != '') {
code += `${codes[2]}`;
}
} else {
// code
code = `${block.code}`
}
box = box.replace('{code}', code);
box = prevBox + box;
if (block.next != undefined && block.next >= 0
&& block.next != pointer // prevent infinite loop
) {
box = this.loadBox(block.next, box);
}
return box;
}
/**
* Synchronize container & _apiBoard
* @param {HTMLElement} parentTag
*/
VpBoard.prototype.syncBoard = function(parentTag) {
var parentKey = parentTag.data('idx');
var parentType = parentTag.data('type');
var parentLink = parentTag.data('link');
if (parentLink == undefined) {
parentLink = 'child';
}
// get childrenTag
var childrenTag = parentTag.children(VP_DRAGGABLE);
// loop and recursive function to get children
var childrenCount = childrenTag.length;
if (childrenCount > 0) {
var prevKey = -1;
for (var i = 0; i < childrenCount; i++) {
var childTag = childrenTag[i];
var key = $(childTag).data('idx');
var type = $(childTag).data('type');
this._apiBoard[key].checksum = true;
if (i == 0) {
// first child set to child/left/right
if (parentKey == undefined) {
this._apiHead = key;
} else {
this._apiBoard[parentKey][parentLink] = key;
}
}
// if type is brac or oper
if (type == 'brac') {
var boxTag = $(childTag).children(VP_DROPPABLE_BOX);
// link to child
this.syncBoard($(boxTag));
} else if (type == 'oper') {
var leftTag = $(childTag).children(VP_DROPPABLE_BOX + '.left');
var rightTag = $(childTag).children(VP_DROPPABLE_BOX + '.right');
// link to left/right children
this.syncBoard($(leftTag));
this.syncBoard($(rightTag));
}
if (prevKey >= 0) {
this._apiBoard[prevKey].next = key;
}
// _apiBoard[key].prev = prevKey;
prevKey = key;
}
} else {
// no child
if (parentKey == undefined) {
this._apiHead = key;
} else {
this._apiBoard[parentKey][parentLink] = -1;
}
}
}
VpBoard.prototype.getCode = function(container = this.container) {
// TODO: run cell : $('.vp-ab-container .vp-ab-code').text().replaceAll(/\r?\n|\r/g, '').trim()
var code = $(this.parentContainer).find(container).find('.vp-ab-code').text().replaceAll(/\r?\n|\r/g, '').trim();
if (code == undefined) {
return "";
}
return code;
}
/**
* Load container & apiStack boxes
*/
VpBoard.prototype.load = function() {
// clean container
$(this.parentContainer).find('.vp-ab-area').remove();
this.page.loadCss(Jupyter.notebook.base_url + vpConst.BASE_PATH + vpConst.STYLE_PATH + "file_io/vpBoard.css");
this.loadContainer();
this.loadApiStack();
}
/**
* Load api board droppable container
* default class : .vp-ab-container
*/
VpBoard.prototype.loadContainer = function(parentContainer = this.parentContainer) {
var that = this;
var droppableEle = $(this.containerTag);
var droppableBoxEle = $(this.droppableBox);
// load container
$(parentContainer).prepend(droppableEle);
}
/**
* Load apiStack
*/
VpBoard.prototype.loadApiStack = function() {
$(this.parentContainer).find(this.container).html('');
var apiStackLength = Object.keys(this._apiBoard).length;
if (apiStackLength === 0) {
// no _apiBoard available
return;
}
var box = this.loadBox(this._apiHead);
var boxEle = $(box);
$(this.parentContainer).find(this.container).append(boxEle);
}
/*** Get/Set ApiStack ***/
/** Get api stack
* @returns {object} _apiBoard
*/
VpBoard.prototype.getApiStack = function() {
return this._apiBoard;
}
/**
* sync api stack with vp-ab-container
*/
VpBoard.prototype.syncApiStack = function(parentTag = $(this.parentContainer).find(this.container)) {
this.clearLink();
this.syncBoard(parentTag);
this.clearUnchecked();
}
//// Lock Visualize ///////////////////////////
VpBoard.prototype.BeginUpdate = function() {
this.lock = true;
}
VpBoard.prototype.OnUpdate = function() {
if (this.lock == undefined) {
this.lock = false;
}
return this.lock == true;
}
VpBoard.prototype.EndUpdate = function() {
this.lock = false;
}
//// End Lock Visualize /////////////////////////
/**
* add block
* @param {Object} blockObj { code: '', type: '', metadata: {} }
* @returns {number} number of added index
*/
VpBoard.prototype.addBlock = function(blockObj, linkKey = -1, linkType = 'next') {
// add on api stack
// blockObj : { code: '', type: '', metadata: {} }
// link with prev block
var prevKey = this.getLastBlockKey();
if (linkKey >= 0) {
prevKey = linkKey;
}
var newKey = this._boardNewNumber;
try {
if (newKey == 0) {
this._apiHead = 0;
}
// add block
this._apiBoard[newKey] = {
code: blockObj.code
, type: blockObj.type
, metadata: blockObj.metadata
};
this._boardNewNumber++;
if (prevKey >= 0 && blockObj != undefined) {
this._apiBoard[prevKey][linkType] = newKey;
}
} catch {
this._boardNewNumber = newKey;
return -1;
}
// appendTo container
// var box = this.loadBox(idx);
// var boxEle = $(box);
// $(this.parentContainer).find(this.container).append(boxEle);
// bind event
//this.setDroppableBox(boxEle.find(this.droppableBox));
//this.setDraggable(boxEle);
if (!this.OnUpdate()) {
this.loadApiStack();
}
return newKey;
}
VpBoard.prototype.addBlockLoop = function(blockObjs, root = -1, link = 'next') {
var prevKey = root;
var linkType = link;
blockObjs.forEach((obj, idx) => {
prevKey = this.addBlock(obj, prevKey, linkType);
// child
if (obj.child != undefined && obj.child.length > 0) {
this.addBlockLoop(obj.child, prevKey, 'child');
}
// left
if (obj.left != undefined && obj.left.length > 0) {
this.addBlockLoop(obj.left, prevKey, 'left');
}
// right
if (obj.right != undefined && obj.right.length > 0) {
this.addBlockLoop(obj.right, prevKey, 'right');
}
if (idx == 0) {
// change link type after first element
linkType = 'next';
}
});
}
/**
* add blocks
* @param {Array} blockObjs [{ code: '', type: '', metadata: {} }]
*/
VpBoard.prototype.addBlocks = function(blockObjs, root = -1, link = 'next') {
try {
this.BeginUpdate();
// add blocks
this.addBlockLoop(blockObjs, root, link);
this.EndUpdate();
this.loadApiStack();
} catch {
this.EndUpdate();
}
}
VpBoard.prototype.getBoardSize = function() {
var keys = Object.keys(this._apiBoard);
return keys.length;
}
VpBoard.prototype.getBoard = function() {
return JSON.parse(JSON.stringify(this._apiBoard));
}
VpBoard.prototype.setBoard = function(board) {
this._apiBoard = board;
this._apiHead = 0;
this.loadApiStack();
}
VpBoard.prototype.setBlockMetadata = function(key, metadata) {
this._apiBoard[key].metadata = metadata;
}
VpBoard.prototype.getBlockMetadata = function(key) {
return this._apiBoard[key].metadata;
}
VpBoard.prototype.setBlockCode = function(key, code) {
this._apiBoard[key].code = code;
}
VpBoard.prototype.getParent = function(childKey) {
var keys = Object.keys(this._apiBoard);
var parentKey = -1;
for (var i = 0; i < keys.length(); i++) {
var k = keys[i];
if ((this._apiBoard[k].left == childKey)
|| (this._apiBoard[k].right == childKey)
|| (this._apiBoard[k].child == childKey)) {
parentKey = k;
break;
}
}
return parentKey;
}
/**
* Get last block's key
*/
VpBoard.prototype.getLastBlockKey = function() {
var keys = Object.keys(this._apiBoard);
if (keys.length > 0) {
var key = keys[0];
var lastKey;
while (key >= 0 && key != undefined) {
lastKey = key;
key = this._apiBoard[key].next;
}
return lastKey;
}
return -1;
}
VpBoard.prototype.getLastBlockValue = function(key) {
try {
var lastKey = this.getLastBlockKey();
var blockValue = this._apiBoard[lastKey][key];
} catch {
return -1;
}
return blockValue;
}
VpBoard.prototype.removeLastBlock = function() {
var lastBlockKey = this.getLastBlockKey();
this.removeBlock(lastBlockKey);
this.loadApiStack();
}
VpBoard.prototype.removeBlock = function(key, isChild = false) {
var keys = Object.keys(this._apiBoard);
// remove link
keys.forEach(k => {
if (this._apiBoard[k].next == key) {
this._apiBoard[k].next = this._apiBoard[key].next;
} else if (this._apiBoard[k].left == key) {
this._apiBoard[k].left = -1;
} else if (this._apiBoard[k].right == key) {
this._apiBoard[k].right = -1;
} else if (this._apiBoard[k].child == key) {
this._apiBoard[k].child = -1;
}
});
// find child
var block = this._apiBoard[key];
if (isChild && block.next != undefined && block.next >= 0) {
this.removeBlock(block.next, true);
}
if (block.child != undefined && block.child >= 0) {
this.removeBlock(block.child, true);
}
if (block.left != undefined && block.left >= 0) {
this.removeBlock(block.left, true);
}
if (block.right != undefined && block.right >= 0) {
this.removeBlock(block.right, true);
}
if (key == 0) {
this._apiHead = 0;
this._apiBoard = {};
this._boardNewNumber = 0;
} else {
delete this._apiBoard[key];
}
}
VpBoard.prototype.setBlockClickEvent = function(callback = undefined) {
var that = this;
var draggable = this.draggable;
var draggableInbox = this.draggableInbox;
// click - selection
$(document).on('click', draggable, function(event) {
event.stopPropagation();
if ($(this).hasClass(draggableInbox.substr(1))) {
$(draggable).removeClass('selected');
$(this).addClass('selected');
if (callback != undefined) {
callback(this, event);
}
}
});
}
return VpBoard;
});