├── modules └── cache.node ├── web ├── root │ ├── css │ │ ├── elements.css │ │ ├── base.css │ │ └── gv.css │ ├── js │ │ ├── basic.js │ │ ├── server.js │ │ ├── hid.js │ │ └── gv.js │ └── index.html ├── engine.js └── server.js ├── admin.js ├── settings.js ├── README.md └── database.js /modules/cache.node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intersystems-community/GlobalsDB-NodeJS-Admin/master/modules/cache.node -------------------------------------------------------------------------------- /web/root/css/elements.css: -------------------------------------------------------------------------------- 1 | circle { 2 | stroke-width: 1px; 3 | stroke: rgba(0, 0, 0, 0.50); 4 | fill: #c2eaea; 5 | } -------------------------------------------------------------------------------- /web/engine.js: -------------------------------------------------------------------------------- 1 | module.exports = new function() { 2 | 3 | var db = require("./../database.js"); 4 | 5 | this.about = function() { 6 | return db.getInfo(); 7 | }; 8 | 9 | this.getGlobal = function() { 10 | 11 | }; 12 | 13 | }; -------------------------------------------------------------------------------- /web/root/css/base.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | padding: 0; 3 | margin: 0; 4 | height: 100%; 5 | background: #eeedc8; 6 | } 7 | 8 | .fullSized { 9 | width: 100%; 10 | height: 100%; 11 | } 12 | 13 | .inactive { 14 | color: rgba(128,128,128,0.5); 15 | } -------------------------------------------------------------------------------- /admin.js: -------------------------------------------------------------------------------- 1 | var core = new function() { 2 | 3 | var webServer = require("./web/server.js"), 4 | fs = require("fs"), 5 | db = require("./database.js"); 6 | 7 | if (!db.connect()) { 8 | console.log("Failed to initialize database."); 9 | return; 10 | } 11 | 12 | webServer.start(); 13 | console.log("Application initialized successfully."); 14 | 15 | }; -------------------------------------------------------------------------------- /settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module represents different setting for application. 3 | */ 4 | module.exports = new function() { 5 | 6 | /** 7 | * Because application represents only conceptual part of administrative tools, do not use it for real projects. 8 | * Check database.js file. Settings are hardcoded. 9 | * 10 | * Requirements: 11 | * - GlobalsDB with NodeJS adapter v1 (version is important) 12 | * - NodeJS 13 | * - Settings changes 14 | * 15 | * IMPORTANT! 16 | * Also check db.connect method! 17 | * Comment line with fillTestData evaluation if you have any data in your database. 18 | */ 19 | 20 | this.db = { 21 | globalsDBInstallationPath: process.env.GLOBALS_HOME, 22 | globalsDBDatabaseDirectory: "/mgr" 23 | 24 | }; 25 | this.app = { 26 | 27 | }; 28 | this.SERVER_PORT = 80; 29 | this.SERVER_DOMAIN = "127.0.0.1"; 30 | 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /web/root/js/basic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Some prototypes to simplify work with objects. 3 | */ 4 | 5 | /** 6 | * Foreach operator for object's properties. Usage: 7 | * someObject.foreach(function(property){ someActions(this[property]) }) 8 | * 9 | * @param callback {function} 10 | */ 11 | Object.prototype.foreach = function(callback) { 12 | for (var property in this) { 13 | if (!this.hasOwnProperty(property)) continue; 14 | callback.call(this, property) 15 | } 16 | }; 17 | 18 | /** 19 | * Applies property to object. Returns new generated property name. 20 | * 21 | * @param value {*} 22 | * @returns {string} 23 | */ 24 | Object.prototype.append = function(value) { 25 | var index = 0; 26 | while (this.hasOwnProperty(index + "")) index++; 27 | this[index + ""] = value; 28 | return index + ""; 29 | }; 30 | 31 | /** 32 | * Merges target object with current object. 33 | * All properties of current object will be rewritten by given object's properties. 34 | * 35 | * @param object {object} 36 | */ 37 | Object.prototype.merge = function(object) { 38 | 39 | var combine = function(target,object) { 40 | for (var property in object) { 41 | if (!object.hasOwnProperty(property)) continue; 42 | target[property] = object[property]; // note about properties-objects 43 | } 44 | }; 45 | 46 | combine(this,object); 47 | 48 | }; 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GlobalsDB-NodeJS-Admin concept 2 | ====================== 3 | 4 | ##### Note: this is a demo application. Code (engine) will be rewritten soon. 5 | 6 | #### Update: GlobalsDB Admin released! 7 | 8 | Easy visual interface for administrating GlobalsDB. Represents database in hierarchical tree-based view - one you haven't seen before. 9 | 10 | Note that modules/cache.node may be corrupted because of git file encoding. If NodeJS will throw error about cache.node module, download the latest one from [official site](http://www.globalsdb.org/downloads) and replace your current one. 11 | 12 | ### Installation 13 | 14 | Copy files to any directory and start from admin.js file. Customize url/port settings in settings.js. Note that application is in-development and represents only main concept, so it doesn't have security at all. Use at your own risk! 15 | 16 | ### Troubleshooting 17 | 18 | Make sure that: 19 | * NodeJS and GlobalsDB are installed and connected with NodeJS using v1 adapter (important) 20 | * You replaced basic settings in settings.js with yours 21 | * cache.node isn't corrupted 22 | 23 | If console throws error during startup about loading cache.node module, try to replace module file with latest module file in your GlobalsDB/bin directory. This file called like cacheXXX.node, copy and rename this file to project's /modules directory. 24 | -------------------------------------------------------------------------------- /web/root/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Admin 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 |
20 | USER 21 |
22 | 27 |
28 |
29 | GlobalsDB administrative tool concept.
30 | Currently in development. Use at your own risk!
31 | Check settings.js file before usage. 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /web/root/js/server.js: -------------------------------------------------------------------------------- 1 | var server = new function() { 2 | 3 | var getXmlHttp = function() { 4 | var xmlhttp; 5 | try { 6 | xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); 7 | } catch (e) { 8 | try { 9 | xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); 10 | } catch (E) { 11 | xmlhttp = false; 12 | } 13 | } 14 | if (!xmlhttp && typeof XMLHttpRequest!='undefined') { 15 | xmlhttp = new XMLHttpRequest(); 16 | } 17 | return xmlhttp; 18 | }; 19 | 20 | var makeRequest = function(url, caching, data, method, handler) { 21 | 22 | if (typeof caching === "undefined") caching = false; 23 | method = method.toLowerCase(); 24 | var req = getXmlHttp(); 25 | caching = (method == "get")?(((url.indexOf("?") === -1)?"?":"&") + "cache=" + 26 | ((caching)?1:new Date().getTime())):""; 27 | 28 | req.onreadystatechange = function() { 29 | 30 | if (req.readyState == 4) { //req.statusText 31 | 32 | if(req.status == 200) { 33 | //console.log(req.responseText); 34 | handler.call(null,req.responseText,1); 35 | } else { 36 | handler.call(null,null,0); 37 | console.log("Ajax GET error: ",req.statusText,req.responseText); 38 | } 39 | 40 | } 41 | 42 | }; 43 | 44 | req.open(((method === "get")?"get":"post"), url + caching, true); 45 | req.send(((method === "get")?null:data)); 46 | 47 | }; 48 | 49 | /** 50 | * Gets data from server and handles it with handler. 51 | * 52 | * @param url 53 | * @param handler 54 | * @param data 55 | * [ @param caching ] 56 | */ 57 | this.get = function(url, handler, data, caching) { 58 | 59 | makeRequest(url, caching, data, "get", handler); 60 | 61 | }; 62 | 63 | /** 64 | * Gets data from server and handles it with handler. 65 | * 66 | * @param url 67 | * @param handler 68 | * @param data 69 | */ 70 | this.post = function(url, handler, data) { 71 | 72 | makeRequest(url, false, data, "post", handler); 73 | 74 | }; 75 | 76 | this.initialize = function() { 77 | 78 | // nothing to do 79 | 80 | }; 81 | 82 | }; 83 | -------------------------------------------------------------------------------- /web/root/css/gv.css: -------------------------------------------------------------------------------- 1 | #gv-nodesContainer { 2 | position: relative; 3 | width: 10000px; 4 | height: 10000px; 5 | -moz-user-select: -moz-none; 6 | -khtml-user-select: none; 7 | -webkit-user-select: none; 8 | -ms-user-select: none; 9 | user-select: none; 10 | -webkit-backface-visibility: hidden; 11 | } 12 | 13 | #gv-container { 14 | position: fixed; 15 | overflow: hidden; 16 | 17 | } 18 | 19 | #gv-interface-namespace { 20 | position: fixed; 21 | left: 0; 22 | top: 0; 23 | padding: 8px 12px 10px 8px; 24 | border-radius: 0 0 50px/20px 0; 25 | background: rgba(0,0,0,0.5); 26 | color: white; 27 | cursor: pointer; 28 | font-size: 1.5em; 29 | /*-webkit-transition: all 0.3s ease; 30 | -moz-transition: all 0.3s ease; 31 | -o-transition: all 0.3s ease; 32 | transition: all 0.3s ease;*/ 33 | } 34 | 35 | #gv-interface-namespace:hover { 36 | color: #eeedc8; 37 | } 38 | 39 | .gv-namespacePart:after { 40 | content: " > "; 41 | } 42 | 43 | .gv-node { 44 | position: absolute; 45 | padding: 5px; 46 | background: rgba(213, 125, 238, 0.83); 47 | border: 1px solid rgb(0, 0, 0); 48 | border-radius: 50%; 49 | vertical-align: middle; 50 | text-align: center; 51 | font-size: 2em; 52 | cursor: pointer; 53 | text-decoration: none; 54 | } 55 | 56 | .gv-node:hover { 57 | opacity: 1 !important; 58 | box-shadow: 0 0 30px yellow; 59 | } 60 | 61 | .gv-nodeType-0 { 62 | background: rgba(238, 86, 0, 0.90); 63 | border: 1px solid rgb(0, 0, 0); 64 | } 65 | 66 | .gv-nodeType-1 { 67 | background: rgba(178, 174, 238, 0.82); 68 | border: 1px solid rgb(0, 0, 0); 69 | } 70 | 71 | 72 | .gv-nodeType-2 { 73 | background: rgba(188, 236, 238, 0.84); 74 | border: 1px solid rgb(0, 0, 0); 75 | } 76 | 77 | .gv-link { 78 | position: absolute; 79 | text-align: center; 80 | left: 0; 81 | top: 0; 82 | width: 0; 83 | height: 5px; 84 | overflow: visible; 85 | background: rgba(188, 236, 238, 0.84); 86 | border-top: 1px solid rgb(0, 0, 0); 87 | border-bottom: 1px solid rgb(0, 0, 0); 88 | } 89 | 90 | .gv-linkText { 91 | position: relative; 92 | top: -0.6em; 93 | padding: 0.1em; 94 | background: rgba(188, 236, 238, 1); 95 | border: 1px solid black; 96 | border-radius: 2px; 97 | text-overflow: clip; 98 | font-weight: 900; 99 | /*max-width: 100%; 100 | overflow: hidden;*/ 101 | display: inline-block; 102 | } 103 | 104 | #gv-footer { 105 | position: fixed; 106 | left: 0; 107 | bottom: 0; 108 | width: 100%; 109 | padding: 5px; 110 | } -------------------------------------------------------------------------------- /database.js: -------------------------------------------------------------------------------- 1 | module.exports = new function() { 2 | 3 | var dbModule = require("./modules/cache.node"), 4 | settings = require("./settings.js"), 5 | fs = require("fs"), // @debug 6 | db = new dbModule.Cache(), 7 | OPENED = false; 8 | 9 | this.getInfo = function() { 10 | return db.about(); 11 | }; 12 | 13 | this.fillTestData = function() { 14 | if (!OPENED) return; 15 | db.set("root", "2 ways"); 16 | db.set("root", "people", 0, "name", "Jacksssssssssssssssssssssssssssssssssssssssssssssssssss"); 17 | db.set("root", "people", 0, "age", 25); 18 | db.set("root", "people", 0, "gender", 0); 19 | db.set("root", "people", 1, "name", "Henry"); 20 | db.set("root", "people", 1, "age", 12); 21 | db.set("root", "people", 1, "gender", 0); 22 | db.set("root", "people", 2, "name", "Goose"); 23 | db.set("root", "people", 2, "age", 2); 24 | db.set("root", "people", 2, "gender", 1); 25 | db.set("root", "people", 2, "Goose"); 26 | db.set("root", "people", 3, "name", "Olga"); 27 | db.set("root", "people", 3, "age", 12); 28 | db.set("root", "people", 3, "gender", 1); 29 | db.set("root", "loot", 0, "baggage"); 30 | db.set("root", "loot", 1, "message"); 31 | db.set("root", "loot", 2, "plant"); 32 | db.set("root", "loot", 3, "box"); 33 | db.set("root", "loot", 3, "box", "9 items"); 34 | db.set("root", "loot", 3, "box", "9 items"); 35 | db.set("root", "loot", 3, "box", "item1", "knife"); 36 | db.set("root", "loot", 3, "box", "item2", "clock"); 37 | db.set("root", "loot", 3, "box", "item3", "pen"); 38 | db.set("root", "loot", 3, "box", "item4", "letter"); 39 | db.set("root", "loot", 3, "box", "item5", "egg"); 40 | db.set("root", "loot", 3, "box", "item6", "key"); 41 | db.set("root", "loot", 3, "box", "item7", "Guf"); 42 | db.set("root", "loot", 3, "box", "item8", "paper"); 43 | db.set("root", "loot", 3, "box", "item9", "wood"); 44 | console.log("Test data assigned."); 45 | }; 46 | 47 | /* 48 | 49 | global_directory() // to list globals in namespace 50 | increment(g,l,o,b,*n) // to increment global value simultaneously 51 | kill() / set() // to kill/set node 52 | lock()/unlock() // to protect 53 | merge({to: {}, from: {}}) 54 | next() previous() // subscripts 55 | next_node(node), previous_node() // defined: 0 if no next 56 | order(), next() 57 | retrieve() // 'array', 'list', 'object' 58 | 59 | */ 60 | 61 | this.getData = function(globalArray, callBack) { // sync! 62 | //console.log(globalArray); 63 | var global = globalArray[0]; 64 | if (globalArray == undefined) {callBack.call(this); return} 65 | db.get({ 66 | global: global, 67 | subscripts: globalArray.slice(1) 68 | }, callBack); 69 | }; 70 | 71 | this.getLevel = function(globalArray, callBack) { 72 | if (globalArray == false) { 73 | db.global_directory({}, callBack); 74 | return; 75 | } 76 | db.retrieve({ 77 | global: globalArray[0], 78 | subscripts: globalArray.slice(1) 79 | }, "list", callBack); 80 | }; 81 | 82 | this.connect = function() { 83 | var d = process.cwd(); // remember current directory 84 | var result = db.open({ 85 | path: settings.db.globalsDBInstallationPath + settings.db.globalsDBDatabaseDirectory, 86 | username: '_SYSTEM', 87 | password: 'SYS', 88 | namespace: 'USER'})["ok"]; 89 | 90 | if (result) OPENED = true; 91 | 92 | this.fillTestData(); // WARNING! COMMENT THIS LINE IF YOU HAVE ANY DATA IN YOUR DATABASE! 93 | 94 | process.chdir(d); // restore directory because of GlobalsDB changes it 95 | return result; 96 | }; 97 | 98 | this.disconnect = function() { 99 | db.close(function(error, result) { 100 | if (error) { 101 | console.log(result); 102 | } else { 103 | OPENED = false; 104 | console.log("Disconnected from database."); 105 | } 106 | }) 107 | }; 108 | 109 | }; -------------------------------------------------------------------------------- /web/server.js: -------------------------------------------------------------------------------- 1 | module.exports = new function() { 2 | 3 | var http = require("http"), 4 | fileSystem = require("fs"), 5 | settings = require("../settings.js"), 6 | queryString = require('querystring'), 7 | engine = require("./engine.js"), 8 | db = require("../database.js"), 9 | server = null; 10 | 11 | var FILE_TYPES = { 12 | "html": "text/html", 13 | "css": "text/css", 14 | "js": "text/javascript", 15 | "jpg": "image/jpeg", 16 | "png": "image/png", 17 | "gif": "image/gif" 18 | }; 19 | 20 | var getGlobal = function(request, responce, callback) { 21 | 22 | var generateBred = function() { 23 | 24 | var genName = function() { 25 | var s = "", l = Math.random()*10; 26 | for (var i = 0; i < l; i++) { 27 | s += String.fromCharCode(Math.floor(Math.random()*26 + 65)) 28 | } 29 | return s; 30 | }; 31 | 32 | var obj = { 33 | status: "ok", 34 | children: { 35 | 36 | } 37 | }; 38 | var l = Math.random()*6; 39 | for (var i = 0; i < l; i++) { 40 | obj.children[i+""] = { 41 | n: genName(), 42 | t: Math.floor(Math.random()*4) 43 | } 44 | } 45 | 46 | return obj; 47 | }; 48 | 49 | var body = ""; 50 | request.on('data', function (data) { 51 | body += data; 52 | }); 53 | request.on('end',function(){ 54 | var bred = generateBred(); 55 | var nodeArray = body.split(","); 56 | nodeArray.splice(0,1); 57 | var ro = { 58 | status: "ok", 59 | count: 0, 60 | children: { 61 | 62 | } 63 | }; 64 | 65 | db.getLevel(nodeArray, function(result, data) { 66 | for (var i = 0; i < data.length; i++) { 67 | (function(i){ 68 | db.getData(nodeArray.concat(data[i]), function(result, data2) { 69 | var t = (data2.data)?2:1; 70 | ro.children[i] = { 71 | n: data[i], 72 | t: t, 73 | v: data2.data || "" 74 | }; 75 | if (ro.count++ === data.length - 1) { 76 | callback.call(this, JSON.stringify(ro)); 77 | } 78 | }); 79 | })(i); 80 | } 81 | }); 82 | //db.getLevel(["root","people","0", "name"], console.log); 83 | 84 | }); 85 | }; 86 | 87 | var setGlobal = function(request, responce, callback) { 88 | 89 | var body = ""; 90 | request.on('data', function (data) { 91 | body += data; 92 | }); 93 | request.on('end',function(){ 94 | callback.call(this, JSON.stringify({status: "ok", result: 0})); 95 | }); 96 | 97 | }; 98 | 99 | /** 100 | * Returns contents of file located in root directory or empty string if file not found. 101 | * 102 | * @param request 103 | * @param response 104 | * @param callback 105 | */ 106 | var getWebFile = function(request, response, callback) { 107 | 108 | var args = request.url.indexOf("?"); 109 | var fileWebPath = request.url.substr(0,(args===-1)?request.url.length:args); 110 | if (fileWebPath.charAt(fileWebPath.length - 1) === "/") fileWebPath += "index.html"; 111 | var fileWebName = "web/root" + fileWebPath, 112 | data; 113 | 114 | if (fileWebPath === "/data") { 115 | getGlobal(request, response, function(data) { 116 | callback.call(this, { 117 | status: 200, 118 | contentType: "application/json", 119 | body: data 120 | }); 121 | }) 122 | } else if (fileWebPath === "/set") { 123 | setGlobal(request, response, function(data) { 124 | callback.call(this, { 125 | status: 200, 126 | contentType: "application/json", 127 | body: data 128 | }); 129 | }) 130 | } else if (fileSystem.existsSync(fileWebName)) { 131 | 132 | var fileContent = fileSystem.readFileSync(fileWebName); 133 | data = { 134 | status: 200, 135 | contentType: FILE_TYPES[fileWebPath.substr(fileWebPath.lastIndexOf(".") + 1)] || "text/plain", 136 | body: fileContent 137 | }; 138 | callback.call(this, data); 139 | 140 | } else { 141 | 142 | data = { 143 | status: 404, 144 | contentType: "text/html", 145 | body: "File " + fileWebPath + " not found on server!" // @improve: make constant 146 | }; 147 | callback.call(this, data); 148 | 149 | } 150 | 151 | }; 152 | 153 | /** 154 | * Client request handler. 155 | * 156 | * @param request 157 | * @param response 158 | */ 159 | var request = function(request, response) { 160 | 161 | getWebFile(request, response, function(data) { 162 | response.writeHead(data.status, { 163 | "Content-Type": data.contentType 164 | }); 165 | response.end(data.body); 166 | }); 167 | 168 | }; 169 | 170 | /** 171 | * Causes web server to listen requests. 172 | */ 173 | this.start = function() { 174 | server = http.createServer(request); 175 | server.listen(settings.SERVER_PORT, settings.SERVER_DOMAIN); 176 | console.log("Web Server started to listen on " + settings.SERVER_DOMAIN + ":" + settings.SERVER_PORT); 177 | return true; 178 | }; 179 | 180 | /** 181 | * Causes web server to stop listening requests. 182 | */ 183 | this.stop = function() { 184 | if (!server) console.log("Server not up!"); 185 | server.stop(); 186 | server = null; 187 | }; 188 | 189 | }; 190 | -------------------------------------------------------------------------------- /web/root/js/hid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Human input device wrapper object. 3 | * 4 | * Version: 0.4 5 | * 6 | * Required: 7 | * basic.js 8 | */ 9 | var hid = new function() { 10 | 11 | var specVar = "!hid", // special variable name that will be handled by html-objects 12 | pointer = { // pointer object 13 | stack: { 14 | 15 | }, 16 | STATES: { 17 | NONE: 0, 18 | PRESS: 1, 19 | MOVE: 2, 20 | RELEASE: 3 21 | } 22 | }; 23 | 24 | // updates pointer 25 | var updatePointer = function(id, pointerObject) { 26 | if (!pointer.stack.hasOwnProperty(id)) return; 27 | pointer.stack[id].merge(pointerObject); 28 | }; 29 | 30 | // @debug 31 | this.getInner = function() { return pointer }; 32 | 33 | // removes pointer 34 | var removePointer = function(id) { 35 | if (!pointer.stack.hasOwnProperty(id)) return; 36 | delete pointer.stack[id]; 37 | }; 38 | 39 | var blockEvent = function(e) { 40 | e.returnValue = false; 41 | e.cancelBubble = true; 42 | if (e.preventDefault) { 43 | e.preventDefault(); 44 | e.stopPropagation(); 45 | } 46 | }; 47 | 48 | // call binders for event for event target and it's parents 49 | var callBinders = function(eventName, currentPointer) { 50 | 51 | var forObject = function(object) { 52 | if (object.hasOwnProperty(specVar) && object[specVar].valid) { 53 | object[specVar].binds[eventName].foreach(function(prop){ 54 | this[prop].call(object, currentPointer); 55 | blockEvent(currentPointer.originalEvent); 56 | }) 57 | } 58 | return object.parentNode; 59 | }; 60 | 61 | var object = currentPointer.target; 62 | while (object) { // down recursively calling handler for every parent of target 63 | object = forObject(object); 64 | } 65 | return 1; 66 | 67 | }; 68 | 69 | var handlers = { 70 | 71 | pointerStart: function(event, id, x, y, cX, cY, target) { 72 | 73 | var currentPointer = { 74 | id: id, 75 | x: x, 76 | y: y, 77 | originX: x, 78 | originY: y, 79 | pageX: x, 80 | pageY: y, 81 | clientX: cX, 82 | clientY: cY, 83 | originalEvent: event, 84 | state: pointer.STATES.PRESS, 85 | target: target 86 | }; 87 | pointer.principal = currentPointer; 88 | pointer.stack[id] = currentPointer; 89 | //console.log(currentPointer.originalEvent); 90 | callBinders("pointerStart", currentPointer); 91 | 92 | }, 93 | 94 | pointerMove: function(event, id, x, y, cX, cY) { 95 | 96 | if (!pointer.stack[id]) return; 97 | var currentPointer = { 98 | id: id, 99 | x: x, 100 | y: y, 101 | pageX: x, 102 | pageY: y, 103 | clientX: cX, 104 | clientY: cY, 105 | originalEvent: event, 106 | state: pointer.STATES.MOVE 107 | }; 108 | 109 | updatePointer(id, currentPointer); 110 | callBinders("pointerMove", pointer.stack[id]); 111 | 112 | }, 113 | 114 | pointerEnd: function(event, id, x, y, cX, cY) { 115 | 116 | if (!pointer.stack[id]) return; 117 | pointer.stack[id].merge({ 118 | x: x, 119 | y: y, 120 | pageX: x, 121 | pageY: y, 122 | clientX: cX, 123 | clientY: cY, 124 | //originalEvent: event, 125 | state: pointer.STATES.RELEASE 126 | }); 127 | if (!pointer.stack.hasOwnProperty(id)) return; 128 | if (pointer.stack[id].originX === x && pointer.stack[id].originY === y) { 129 | callBinders("pointerPress", pointer.stack[id]); 130 | } 131 | callBinders("pointerEnd", pointer.stack[id]); 132 | removePointer(id); 133 | }, 134 | 135 | pointerPress: function() { // todo: re-organize calls 136 | 137 | } 138 | 139 | }; 140 | 141 | /** 142 | * Binds cross-application pointer(s) event. 143 | * 144 | * @param eventName 145 | * @param target 146 | * @param handler 147 | */ 148 | this.bind = function(eventName, target, handler) { 149 | if (handlers.hasOwnProperty(eventName)) { 150 | if (!target) { 151 | console.log("Target not specified for event " + eventName); 152 | return; 153 | } 154 | if (!target.hasOwnProperty(specVar) || !target[specVar].valid) { 155 | target[specVar] = { 156 | binds: { 157 | pointerStart: {}, 158 | pointerMove: {}, 159 | pointerEnd: {}, 160 | pointerPress: {}, 161 | keyDown: {}, 162 | keyUp: {}, 163 | keyPress: {}, 164 | hoverStart: {}, 165 | hoverEnd: {} 166 | }, 167 | valid: true 168 | } 169 | } 170 | target[specVar].binds[eventName].append(handler); 171 | } else console.log("No such event \"" + eventName + "\" for hid.bind") 172 | }; 173 | 174 | /** 175 | * Cross-browser binding of browser events. For cross-browser application events use hid.bind method. 176 | * 177 | * @param event 178 | * @param element 179 | * @param handler 180 | */ 181 | this.bindBrowserEvent = function (event, element, handler) { 182 | if (element.addEventListener) { 183 | element.addEventListener(event,handler,false); 184 | } else if (element.attachEvent) { 185 | element.attachEvent("on"+event, handler); 186 | } else { 187 | element[event] = handler; 188 | } 189 | }; 190 | 191 | this.initialize = function() { 192 | 193 | var fixEvent = function(e) { 194 | return e || window.event; 195 | }; 196 | 197 | // todo unselectable="on" as attribute for opera 198 | 199 | this.bindBrowserEvent('touchstart', document, function(e){ 200 | e = fixEvent(e); 201 | var target = e.target || e.srcElement; 202 | var touch = e.touches[e.touches.length - 1]; 203 | handlers.pointerStart(e, touch.identifier, touch.pageX, 204 | touch.pageY, touch.clientX, touch.clientY, target); 205 | }); 206 | 207 | this.bindBrowserEvent('touchmove', document, function(e){ 208 | e = fixEvent(e); 209 | e.changedTouches.foreach(function(el) { 210 | handlers.pointerMove(e, this[el].identifier, this[el].pageX, this[el].pageY, this[el].clientX, this[el].clientY); 211 | }); 212 | }); 213 | 214 | this.bindBrowserEvent('touchend', document, function(e){ 215 | e = fixEvent(e); 216 | e.changedTouches.foreach(function(el) { 217 | handlers.pointerEnd(e, this[el].identifier, this[el].pageX, this[el].pageY, this[el].clientX, this[el].clientY); 218 | }); 219 | }); 220 | 221 | this.bindBrowserEvent('mousedown', document, function(e){ 222 | e = fixEvent(e); 223 | var target = e.target || e.toElement; 224 | handlers.pointerStart(e, 1, e.pageX, e.pageY, e.clientX, e.clientY, target); 225 | }); 226 | 227 | this.bindBrowserEvent('mouseup', document, function(e){ 228 | e = fixEvent(e); 229 | handlers.pointerEnd(e, 1, e.pageX, e.pageY, e.clientX, e.clientY) 230 | }); 231 | 232 | this.bindBrowserEvent('mousemove', document, function(e){ 233 | e = fixEvent(e); 234 | handlers.pointerMove(e, 1, e.pageX, e.pageY, e.clientX, e.clientY) 235 | }); 236 | 237 | }; 238 | 239 | }; -------------------------------------------------------------------------------- /web/root/js/gv.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global viewer object - engine for application. 3 | * 4 | * Required: 5 | * hid.js 6 | * basic.js 7 | */ 8 | var gv = new function() { 9 | 10 | var LAST_NODE_ID = 0, 11 | LAST_LAYER = 0; 12 | 13 | this.world = { 14 | 15 | }; 16 | 17 | var dom = new function() { 18 | 19 | this.element = function(id) { 20 | return document.getElementById(id); 21 | }; 22 | 23 | this.getWorld = function() { 24 | return this.element("gv-nodesContainer"); 25 | }; 26 | 27 | this.getContainer = function() { 28 | return this.element("gv-container"); 29 | }; 30 | 31 | this.setMultipath = function(pathArray) { 32 | var f = document.getElementById("gv-footer"); 33 | if (!f) return; 34 | var gets = function(pathArray) { 35 | var s = ""; 36 | for (var i = 0; i < pathArray.length; i++) { 37 | s += "" + pathArray[i] + ""; 38 | } 39 | return s; 40 | }; 41 | f.innerHTML = gets(pathArray); 42 | }; 43 | 44 | this.attachLink = function(name) { 45 | var link = document.createElement("div"); 46 | var linkText = document.createElement("div"); 47 | linkText.className = "gv-linkText"; 48 | linkText.innerHTML = name; 49 | link.className = "gv-link"; 50 | link.appendChild(linkText); 51 | link.style.zIndex = LAST_LAYER; 52 | this.getWorld().appendChild(link); 53 | return link; 54 | }; 55 | 56 | this.detachLink = function(domLink) { 57 | domLink.parentNode.removeChild(domLink); 58 | } 59 | 60 | }; 61 | 62 | var handlers = { 63 | nodeMove: function(pointer) { 64 | this.style.left = pointer.x - this.clientWidth/2 + "px"; 65 | this.style.top = pointer.y - this.clientHeight/2 + "px"; 66 | } 67 | }; 68 | 69 | var addNode = function(id, name) { 70 | 71 | 72 | 73 | }; 74 | 75 | var Node = function(name, value, type) { 76 | 77 | var MAX_LEN = 15; 78 | 79 | this.originalID = value + "\\" + name; 80 | this.name = (value.length < MAX_LEN)?value:value.substr(0, MAX_LEN)+"..."; 81 | if (value === "") {this.name = "null"} 82 | this.nodeName = name; 83 | this.parent = {}; 84 | this.child = {}; 85 | this.type = (type === undefined)?1:type; 86 | this.x = 0; 87 | this.level = 0; 88 | this.y = 0; 89 | this.active = 0; 90 | this.r = 0; 91 | this.domElement = null; 92 | this.id = LAST_NODE_ID++; 93 | gv.world.append(this); 94 | 95 | }; 96 | 97 | Node.prototype = { 98 | 99 | domElement: null, 100 | name: "", 101 | type: 1, 102 | originalID: "", 103 | nodeName: "", 104 | id: -1, 105 | level: 0, 106 | active: 0, 107 | x: 0, 108 | y: 0, 109 | r: 0, 110 | physics: { // proto 111 | distance: 0, 112 | angle: 0, 113 | timer: 0 114 | }, 115 | parent: {}, 116 | child: {}, 117 | check: function(node) { 118 | return node instanceof Node; 119 | }, 120 | addChild: function(node) { 121 | if (!this.check(node)) return; 122 | var link = dom.attachLink(node.nodeName); 123 | this.parent.append({ 124 | node: this, 125 | linkElement: link 126 | }); 127 | node.child.append({ 128 | node: node, 129 | linkElement: link 130 | }); 131 | }, 132 | physicsStep: function(obj) { 133 | obj.x += obj.physics.distance*Math.cos(Math.PI/2-obj.physics.angle)/2; 134 | obj.y += obj.physics.distance*Math.sin(Math.PI/2-obj.physics.angle)/2; 135 | obj.physics.distance /= 2; 136 | obj.update(); 137 | if (obj.physics.distance < 1) { 138 | clearInterval(obj.physics.timer); 139 | obj.physics.timer = 0; 140 | } 141 | }, 142 | hasChilds: function() { 143 | for (var a in this.child) { 144 | if (!this.child.hasOwnProperty(a)) continue; 145 | return 1; 146 | } 147 | return 0; 148 | }, 149 | // todo check for existence 150 | addParent: function(node) { 151 | if (!this.check(node)) return; 152 | this.level = node.level + 1; 153 | var link = dom.attachLink(this.nodeName); 154 | this.parent.append({ 155 | node: node, 156 | linkElement: link 157 | }); 158 | node.child.append({ 159 | node: this, 160 | linkElement: link 161 | }); 162 | }, 163 | hasChild: function() { 164 | var h = false; 165 | for (var c in this.child) { 166 | if (!this.child.hasOwnProperty(c)) continue; 167 | h = true; 168 | } 169 | return h; 170 | }, 171 | clearChildren: function() { 172 | // todo 173 | var recDel = function(node, a) { 174 | console.log("Clearing children ", node); 175 | if (node && node.hasOwnProperty("child")) { 176 | var c = node.child; 177 | for (var cc in c) { 178 | if (!c.hasOwnProperty(cc)) continue; 179 | //c[cc].linkElement.parentNode.removeChild(c[cc].linkElement); 180 | dom.detachLink(c[cc].linkElement); 181 | recDel(c[cc].node, 1); 182 | } 183 | // delete itself 184 | if (a) node.domElement.parentNode.removeChild(node.domElement); 185 | node.child = {}; 186 | // todo 187 | // delete node; 188 | } 189 | }; 190 | recDel(this, 0); 191 | }, 192 | gextHTMLRoot: function() { 193 | 194 | }, 195 | slide: function(a, distance) { // make as light as possible 196 | //if (!a || !b) return; 197 | var b = this, dx = a.x - b.x, dy = a.y - b.y, l = Math.sqrt(dx*dx + dy*dy) - (distance) - 10; 198 | if (l > a.r + b.r) return; // not colliding 199 | var f = (a.r + b.r - l)/5, d = Math.PI/2 - Math.atan2(dx, dy), 200 | fx = f*Math.cos(d), fy = f*Math.sin(d); 201 | //a.x += fx; b.x -= fx; 202 | //a.y += fy; b.y -= fy; 203 | a.x += fx*4; a.y += fy*4; 204 | if (Math.abs(fx) + Math.abs(fy) > 0.001) setTimeout(function(){a.slide(b, distance)}, 25); 205 | a.updateView(); 206 | }, 207 | updateView: function() { 208 | if (!this.domElement) return; 209 | this.domElement.style.left = this.x - this.r + "px"; 210 | this.domElement.style.top = this.y - this.r + "px"; 211 | var i = this; 212 | i.domElement.style.zIndex = LAST_LAYER; 213 | var updateLink = function(property) { 214 | var l = this[property].linkElement; 215 | var o = this[property].node; 216 | var or = o.r; 217 | var ir = i.r; 218 | var dx = o.x - i.x; 219 | var dy = o.y - i.y; 220 | var len = Math.sqrt(dx*dx + dy*dy) - ir - or + 1; 221 | var dir = Math.PI/2 - Math.atan2(o.x - i.x, o.y - i.y); 222 | if (dir < 0) dir += Math.PI*2; 223 | if (dir > Math.PI*2) dir -= Math.PI*2; 224 | if (dir > Math.PI*2) dir -= Math.PI*2; 225 | if (dir > Math.PI/2 && dir < Math.PI*1.5) { 226 | dir -= Math.PI; 227 | var t = or; 228 | or = ir; 229 | ir = t; 230 | } 231 | l.style["transform"] = l.style["-ms-transform"] = l.style["-o-transform"] = l.style["-moz-transform"] = 232 | l.style["-webkit-transform"] = "rotate("+dir+"rad)"; 233 | l.style.width = Math.max(len, 0) + "px"; 234 | l.style.opacity = Math.min(i.domElement.style.opacity, o.domElement.style.opacity) || 1; 235 | l.style.left = (o.x - or*Math.cos(dir) + i.x + ir*Math.cos(dir))/2 - len/2 + "px"; 236 | l.style.top = (o.y - or*Math.sin(dir) + i.y + ir*Math.sin(dir))/2 - l.clientHeight/2 - 1 + "px"; 237 | l.style.zIndex = LAST_LAYER; 238 | }; 239 | LAST_LAYER++; 240 | this.child.foreach(updateLink); 241 | this.parent.foreach(updateLink); 242 | }, 243 | /** 244 | * Updates node position. 245 | */ 246 | update: function() { 247 | var me = this; 248 | this.parent.foreach(function(you){ 249 | me.slide(this[you].node, this[you].linkElement.childNodes[0].clientWidth) 250 | }); 251 | this.child.foreach(function(you){ 252 | me.slide(this[you].node, this[you].linkElement.childNodes[0].clientWidth) 253 | }); 254 | this.updateView(); 255 | }, 256 | _repeatClickHandler: function() { 257 | // todo 258 | }, 259 | _startMoveHandler: function(pointer) { 260 | var i = this, 261 | level = this._parentNode.level; 262 | this._parentNode.wasActive = !!(this._parentNode.active == 1); 263 | gv.world.foreach(function(p){ 264 | this[p].active = false; 265 | var oo = this[p].domElement.style.opacity; 266 | if (this[p].level === level || this[p].level === level + 1) { 267 | this[p].domElement.style.opacity = 1; 268 | } else { 269 | this[p].domElement.style.opacity = 1/((level - this[p].level + 1)*(level - this[p].level + 1)); 270 | } 271 | if (oo !== this[p].domElement.style.opacity) { 272 | this[p].updateView(); 273 | } 274 | }); 275 | setTimeout(function(){i._parentNode.active = true}, 1); 276 | dom.setMultipath(this._parentNode.getTreePath()); 277 | pointer.origX = pointer.x - i._parentNode.x; 278 | pointer.origY = pointer.y - i._parentNode.y; 279 | }, 280 | _moveHandler: function(pointer) { 281 | this._parentNode.x = pointer.x - (pointer.origX || 0); 282 | this._parentNode.y = pointer.y - (pointer.origY || 0); 283 | this._parentNode.update(); 284 | }, 285 | _clickHandler: function() { 286 | var i = this; 287 | if (i._parentNode.hasChild() && i._parentNode.wasActive) { 288 | this._parentNode.clearChildren(); 289 | } else { 290 | if (i._parentNode.active && i._parentNode.hasChilds()) { 291 | i._parentNode._repeatClickHandler(); 292 | } 293 | server.post("data",function(data){ 294 | try {eval("data = " + data)} catch(e) {} 295 | i._parentNode.demo_expand(data); 296 | }, this._parentNode.getTreePath().toString()); 297 | } 298 | }, 299 | getTreePath: function() { 300 | 301 | var path = []; 302 | 303 | var fall = function(node) { 304 | path.push(node.nodeName); 305 | if (node.parent["0"]) { 306 | fall(node.parent["0"].node); 307 | } 308 | }; 309 | 310 | fall(this); 311 | 312 | return path.reverse(); 313 | 314 | }, 315 | attachToScene: function() { 316 | var node = document.createElement("div"); 317 | var MAX_LEN = 15; 318 | node.className = "gv-node gv-nodeType-"+this.type; 319 | node._parentNode = this; 320 | node.id = "node" + this.id; 321 | var span = document.createElement("div"); 322 | span.innerHTML = this.name;//(this.name.length < MAX_LEN)?this.name:this.name.substr(0, MAX_LEN)+"..."; 323 | dom.getWorld().appendChild(node); 324 | node.appendChild(span); 325 | var w = span.clientWidth + 10; 326 | var sh = span.clientHeight; 327 | node.style.width = node.style.height = w + "px"; 328 | span.style.marginTop = w/2 - sh/2 + "px"; 329 | node.style.zIndex = LAST_LAYER; 330 | this.r = (node.clientWidth + 2)/2; 331 | hid.bind("pointerMove", node, this._moveHandler); 332 | hid.bind("pointerPress", node, this._clickHandler); 333 | hid.bind("pointerStart", node, this._startMoveHandler); 334 | this.domElement = node; 335 | this.update(); 336 | }, 337 | detachFromScene: function() { 338 | // todo 339 | }, 340 | demo_expand: function(testObj) { 341 | if (!testObj || !testObj.status || !testObj.children) return; 342 | var i = this; 343 | var n = 0, c = 0; 344 | testObj.children.foreach(function(){n++}); 345 | testObj.children.foreach(function(p){ 346 | var it = this[p]; 347 | var ok = true; 348 | i.child.foreach(function(x){if (this[x].node.originalID === it.v + "\\" + it.n) ok = false}); 349 | if (!ok) return; 350 | var node = gv.createNode(it.n, it.v, it.t); 351 | var d = c*Math.PI*2/n; 352 | node.x = i.x + Math.cos(d)*50; node.y = i.y + Math.sin(d)*50; 353 | node.addParent(i); 354 | node.attachToScene(); 355 | c++; 356 | }) 357 | } 358 | 359 | }; 360 | 361 | this.createNode = function(label, value, type) { 362 | return new Node(label, value, type); 363 | }; 364 | 365 | this._worldScrollHandler = function(pointer) { 366 | //pointer.originX = pointer.x += 367 | }; 368 | 369 | this.initialize = function() { 370 | 371 | if (hid) hid.initialize(); 372 | if (server) server.initialize(); 373 | 374 | var node = this.createNode("USER", "user", 0); 375 | node.x = 3300; 376 | node.y = 3300; 377 | node.attachToScene(); 378 | 379 | var world = dom.getContainer(); 380 | world.scrollLeft = 3000; 381 | world.scrollTop = 3000; 382 | 383 | hid.bind("pointerMove", world, this._worldScrollHandler); 384 | 385 | }; 386 | 387 | }; 388 | --------------------------------------------------------------------------------