diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e2dd1af --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +/coverage +/build +/dist +/node_modules +/test/* \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..63478df --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,33 @@ +module.exports = { + "parserOptions": { + "ecmaVersion": 2017 + }, + + + "env": { + "browser": true, + "node": true, + "jest": true, + "commonjs": true, + "es6": true, + }, + "settings": { + "react": { + "version": "detect" + }, + "propWrapperFunctions": [ + // The names of any function used to wrap propTypes, e.g. `forbidExtraProps`. If this isn't set, any propTypes wrapped in a function will be skipped. + "forbidExtraProps", + // {"property": "freeze", "object": "Object"}, + // {"property": "myFavoriteWrapper"} + ], + }, + "rules": { + "no-unused-vars":1, + "no-useless-escape":1, + "no-extra-semi":1, + "no-var": 1, + }, + +}; + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..44350cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +*.lerna_backup +private.xml +*.log +*.code-workspace +package-lock.json +node_modules +build +start.sh +yarn.lock diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b29c86 --- /dev/null +++ b/README.md @@ -0,0 +1,155 @@ +### GraphXR Iframe example(V2.8.0) + +> Please refer the graphXR.injection.js in
tag at first + +``` + +``` + +iframeElem is a iframe dom element. + +``` +let iframeElem = document.getElementById("injection-graphXR-iframe-id"); +``` + +Embed iframe +``` + +``` + + + +### How run it(For Develop) + +``` +yarn && yarn start +``` +then add graphXR share link as iframe embedGraphURL. +You can change defaultEmbedGraphURL in index.html line 116 + +``` +http://localhost:3000?embedGraphURL=https://graphxr.kineviz.com/share/5c633dfe197b00001e855294/VC%20investment%202004-2013/5c65e7be851f2c0036ef27c9 +``` + +### 1. ApiCommand + +#### 1.1 getGraph resData.content {nodes,edges} + +``` +graphXR.injectionApiCommand(':getGraph', iframeElem) +.then((resData) => { + console.warn("Receive graphData:", resData.content) +}) +``` + +#### 1.2 getGraphStat resData.content {nodes:number,edges:number} + +``` +graphXR.injectionApiCommand(':getGraphStat', iframeElem) +.then((resData) => { + console.warn("Receive getGraphStat:", resData.content) +}) +``` + +#### 1.3 clearGraph resData.content {} + +``` +graphXR.injectionApiCommand(':clearGraph', iframeElem) +.then((resData) => { + console.warn("Receive clearGraph:", resData.content) +}) +``` + +#### 1.4 selected graph resData.content {nodes:number,edges:number} + +``` +graphXR.injectionApiCommand(':selected', iframeElem) +.then((resData) => { + console.warn("Receive query data:", resData.content) +}) +``` + +#### 1.5 query resData.content {nodes:number,edges:number} + +``` +graphXR.injectionApiCommand('MATCH (n)-[r]-(m) RETURN * LIMIT 100', iframeElem) +.then((resData) => { + console.warn("Receive query data:", resData.content) +}) +``` + + +### 2. Injection codes + +#### 2.1 select all + +``` +graphXR.injectionApiCommand(':getGraph', iframeElem) +.then((resData) => { + console.warn("Receive all data:", resData.content) + const {nodes, edges} = resData.content; + + //select all + graphXR.injectionCode(` + _GXR.NodesSelectManager.selectWithNodeIds(${JSON.stringify(nodes.map(n => n._GXRID))},'new') + ` , iframeElem).then(resData => { + console.warn("Injection success:", resData) + }) +}) +``` + + +### 3. event, only support ['change','select'] + + +#### 3.1 graphxr change event + +``` +graphXR.injectionOn("change", () => { + console.warn("receive change event"); + // Please use :getGraph got all data + // graphXR.injectionApiCommand(':getGraph', iframeElem) + // .then((resData) => { + // console.warn("Receive graphData:", resData.content) + // }) +}, iframeElem, "iframe-unique-name") +``` + +#### 3.2 graphxr select event + +``` +graphXR.injectionOn("select", () => { + console.warn("receive select event") + //please use :selected got all selected nodes + graphXR.injectionApiCommand(':selected', iframeElem) + .then((resData) => { + console.warn("Receive selected:", resData.content) + }) +}, iframeElem, "iframe-unique-name") + +``` + + +#### 4. Layout + +Support force, line, grid, circle, cube + +force Layout e.g. + +``` + graphXR.injectionCode(` + _app.controller.API.setLayout("force") + ` , iframeElem).then(resData => { + console.warn("use force success:", resData) + }) +``` + +circle Layout e.g. + +``` + graphXR.injectionCode(` + _app.controller.API.setLayout("circle") + ` , iframeElem).then(resData => { + console.warn("use circle success:", resData) + }) +``` \ No newline at end of file diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..2dd3f6d Binary files /dev/null and b/favicon.ico differ diff --git a/graphXR.injection.js b/graphXR.injection.js new file mode 100644 index 0000000..d79a05e --- /dev/null +++ b/graphXR.injection.js @@ -0,0 +1,240 @@ +; (function ($) { + 'use strict' + + const version = '2.8.0'; + + const GIframeOnMessageHandlerMap = { + //id: {id, iframeElement, messageHandler} + } + + const GEvents = { + "change": {}, + "select": {} + } + + let GTempResponse = { + index: -1, + status: 0, + message: null, + content: {} + }; + + function getIframeElem(){ + return document.querySelectorAll("iframe")[0] + } + + function clearIframeMessage(id) { + + if (GIframeOnMessageHandlerMap[id] ) { + GIframeOnMessageHandlerMap[id].iframeElement.contentWindow.parent.removeEventListener( + 'message', + GIframeOnMessageHandlerMap[id].messageHandler, + false); + delete GIframeOnMessageHandlerMap[id] + } + } + function handleIframeAddMessage(id,iframeElement = getIframeElem()) { + + if (!iframeElement || !id) { + return console.error("miss iframeElement or id") + } + + if (!GIframeOnMessageHandlerMap[id]) { + GIframeOnMessageHandlerMap[id] = { + id, + iframeElement, + messageHandler: function (e) { + let data = e.data && typeof (e.data) === 'object' ? e.data : {}; + let type = String(data.type).toLocaleLowerCase(); + if (type === 'codes') { + GTempResponse = data.response; + } else if (type === 'api-response' || type === 'api') { + GTempResponse = { + index: data.command, + status: 0, + content: data.response && data.response.data ? data.response.data : {} + } + } + } + } + iframeElement.contentWindow.parent.addEventListener('message', GIframeOnMessageHandlerMap[id].messageHandler, false); + } + } + + function untilCheck(untilFunc, maxTime = 3000) { + return new Promise(function (resolve, reject) { + let __useTime = 0; + let __timer = setInterval(function () { + if (untilFunc()) { + clearInterval(__timer); + clearIframeMessage(GTempResponse.index); + resolve(GTempResponse); + } else if (__useTime > maxTime) { + clearInterval(__timer); + reject(new Error(`Timed out in ${maxTime}ms.`)) + } + }, 60); + }); + } + + function injectionCode( code, iframeElement = getIframeElem(), index = Date.now(), type = 'codes') { + if (!iframeElement || iframeElement.tagName !== "IFRAME") { + let err = new Error(`Only support iframe element, please try document.getElementById("your_iframe_id");`); + console.error(err.message); + return Promise.reject(err); + } + index = String(index + Date.now()); + //reset GTempResponse + GTempResponse = { + index: -1, + status: 0, + message: null + }; + + + let postMessageBody = { + + }; + + if (type === 'api') { + index = code; + postMessageBody = { + type: "api", + command: code + } + } else { + postMessageBody = { + type: "codes", + codes: [{ + index: index, + code: code + }] + } + } + + handleIframeAddMessage(index, iframeElement); + + // * allow cross origin post message + iframeElement.contentWindow.postMessage(postMessageBody, "*"); + + return untilCheck(() => { return GTempResponse && String(GTempResponse.index).toLowerCase() === String(index).toLowerCase() }); + } + + function injectionCodes(codes, iframeElement = getIframeElem()) { + + if (!Array.isArray(codes)) { + let err = new Error(`The codes must be as array`); + console.error(err.message); + return Promise.reject(err); + } + + //keep the async waterfall + return codes.reduce((promiseChain, currentCodeItem, currentIndex) => { + return promiseChain.then((chainResults) => { + injectionCode( currentCodeItem.code, iframeElement, currentIndex).then((currentResult) => { + [...chainResults, currentResult] + }) + }) + }, Promise.resolve([])); + } + + function injectionApiCommand(command, iframeElement = getIframeElem()) { + + let newCommand = String(command).trim(); + if (!newCommand && !command) { + let err = new Error(`Please try use those commands [:clearGraph, :getGraphStat, :getGraph]`); + console.error(err.message); + return Promise.reject(err); + } + return injectionCode( newCommand, iframeElement, newCommand , 'api') + } + + function injectionApiCommands(commands, iframeElement = getIframeElem()) { + if (!Array.isArray(commands)) { + let err = new Error(`The commands must be as array`); + console.error(err.message); + return Promise.reject(err); + } + + //keep the async waterfall + return commands.reduce((promiseChain, commandItem, currentIndex) => { + return promiseChain.then((chainResults) => { + injectionApiCommand(commandItem, iframeElement).then((currentResult) => { + [...chainResults, currentResult] + }) + }) + }, Promise.resolve([])); + } + + + function _handleInjectionOn(e) { + let data = e.data && typeof (e.data) === 'object' ? e.data : {}; + let type = String(data.type).toLocaleLowerCase(); + let eventName = String(data.eventName).toLocaleLowerCase(); + if (type === 'events-response' && GEvents && Object.keys(GEvents[eventName] || {}).length > 0) { + Object.values(GEvents[eventName] || {}) + .forEach(cb => { + cb(eventName, data.response || {}); + }); + } + } + + function injectionOn(eventName = 'change', callback = () => { }, iframeElement = getIframeElem(), uniqueName = '') { + if (!['change', 'select'].includes(eventName) || !callback) { + let err = new Error(`Miss eventName['change','select'] ro callback function`); + return console.error(err.message); + } + + //try remove at first, then add eventLister + iframeElement.contentWindow.parent.removeEventListener('message', _handleInjectionOn, false); + iframeElement.contentWindow.parent.addEventListener('message', _handleInjectionOn, false); + + if (GEvents[eventName] && callback) { + uniqueName = uniqueName || callback.name; + GEvents[eventName][uniqueName] = callback; + + let codeIndex = Date.now(); + let code = ` + //convert to global + _app.controller.API.on('${eventName}', (data) => { + if (window.parent) { + window.parent.postMessage({ + type: "events-response", + eventName:"${eventName}", + response: data + }, "*"); + } + }, '${uniqueName}') + ` + injectionCode( code, iframeElement, codeIndex); + + } else if ( + callback[eventName] && + !callback && + GEvents[eventName][uniqueName] + ) { + return delete GEvents[eventName][uniqueName]; + } else { + return console.warn("Only support the events :", Object.keys(callback)); + } + } + + const graphXR = { + version, + injectionCode, + injectionCodes, + injectionApiCommand, + injectionApiCommands, + injectionOn + } + + if (typeof define === 'function' && define.amd) { + define(function () { + return graphXR + }) + } else if (typeof module === 'object' && module.exports) { + module.exports = graphXR + } else { + $.graphXR = graphXR + } +})(this) \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..e51806a --- /dev/null +++ b/index.html @@ -0,0 +1,134 @@ + + + + + +