|
| 1 | +class TLJS { |
| 2 | + static eventsWithInteractions = { |
| 3 | + "tap": "click", |
| 4 | + "cxttap": "rightClick", |
| 5 | + "mouseover": "mouseOver", |
| 6 | + "mouseout": "mouseOut" |
| 7 | + } |
| 8 | + |
| 9 | + static getWebSocket(wsPortOrNull, webSocketMapOrNull) { |
| 10 | + const webSocketMap = webSocketMapOrNull || this.webSocketMap |
| 11 | + const wsPort = wsPortOrNull || "1701" |
| 12 | + if (!this.__webSocketMap.has(wsPort)) { |
| 13 | + TLJS.__initWebSocketOnPort(wsPort, webSocketMap) |
| 14 | + } |
| 15 | + return webSocketMap.get(wsPort) |
| 16 | + } |
| 17 | + |
| 18 | + static resetWebSocket(wsPortOrNull, webSocketMapOrNull) { |
| 19 | + const webSocketMap = webSocketMapOrNull || this.webSocketMap |
| 20 | + const wsPort = wsPortOrNull || "1701" |
| 21 | + TLJS.__initWebSocketOnPort(wsPort, webSocketMap) |
| 22 | + } |
| 23 | + |
| 24 | + static get webSocketMap() { |
| 25 | + if (!this.__webSocketMap) { |
| 26 | + this.__webSocketMap = new Map() |
| 27 | + } |
| 28 | + return this.__webSocketMap |
| 29 | + } |
| 30 | + |
| 31 | + static get wsProtocol() { |
| 32 | + if (location.protocol === "https:") { |
| 33 | + return "wss://" |
| 34 | + } else { |
| 35 | + return "ws://" |
| 36 | + } |
| 37 | + } |
| 38 | + |
| 39 | + static __initWebSocketOnPort(wsPort, webSocketMap) { |
| 40 | + const websocket = new WebSocket(TLJS.wsProtocol + location.hostname + ":" + wsPort + "/ws-TLCytoscape") |
| 41 | + websocket.onclose = function (evt) { |
| 42 | + TLJS.singleton.onClose(evt) |
| 43 | + } |
| 44 | + websocket.onmessage = function (evt) { |
| 45 | + TLJS.singleton.onMessage(evt) |
| 46 | + } |
| 47 | + websocket.onerror = function (evt) { |
| 48 | + TLJS.singleton.onError(evt) |
| 49 | + } |
| 50 | + websocket.onopen = function (evt) { |
| 51 | + const instance = TLJS.singleton |
| 52 | + instance.sendGenerationCommand(instance.visusWaitingConnectToGenerate) |
| 53 | + } |
| 54 | + webSocketMap.set(wsPort, websocket) |
| 55 | + } |
| 56 | + |
| 57 | + static get singleton() { |
| 58 | + if (!this.__singleton) { |
| 59 | + this.__singleton = new TLJS() |
| 60 | + } |
| 61 | + return this.__singleton |
| 62 | + } |
| 63 | + |
| 64 | + static initVisus() { |
| 65 | + TLJS.singleton.initVisus() |
| 66 | + } |
| 67 | + |
| 68 | + constructor() { |
| 69 | + this.visus = new WeakMap() |
| 70 | + this.commandsAction = new Map() |
| 71 | + this.visusWaitingConnectToGenerate = [] |
| 72 | + this.waitingDivByVisu = {} |
| 73 | + this.toNotifyMessage = {} |
| 74 | + this.id = 0 |
| 75 | + this.timeoutId=null |
| 76 | + this.setUpCommand() |
| 77 | + } |
| 78 | + |
| 79 | + initVisus(htmlElement) { |
| 80 | + let idsNeedGeneration = [] |
| 81 | + for (let visuElement of (htmlElement || document).getElementsByClassName("visualization")) { |
| 82 | + const container = visuElement.parentNode |
| 83 | + if (!this.visus.has(container)) { |
| 84 | + this.waitingDivByVisu[container.getAttribute("id")] = container.getElementsByClassName("tlWaiting")[0] |
| 85 | + const visu = cytoscape({ |
| 86 | + pixelRatio: 1, |
| 87 | + container: visuElement, |
| 88 | + layout: {name: "preset"}, |
| 89 | + }) |
| 90 | + this.visus.set(container, visu) |
| 91 | + idsNeedGeneration.push(container.id) |
| 92 | + this.parametrizeInteractionsListenerForVisu({visuId: container.id, visu}) |
| 93 | + } |
| 94 | + } |
| 95 | + this.sendGenerationCommand(idsNeedGeneration) |
| 96 | + } |
| 97 | + |
| 98 | + parametrizeInteractionsListenerForVisu(visuWithId) { |
| 99 | + visuWithId.protectDoubleFire = {} |
| 100 | + const evtFunction = this.createEventFunction(visuWithId) |
| 101 | + const evtFunctionWithDelais = this.delaisEvtFunction(evtFunction) |
| 102 | + visuWithId.visu.on("tap mouseout cxttap", evtFunction) |
| 103 | + visuWithId.visu.on("mouseover", evtFunctionWithDelais) |
| 104 | + visuWithId.visu.on("free", "node", this.createDragEventFunction(visuWithId)) |
| 105 | + } |
| 106 | + |
| 107 | + createDragEventFunction(visuWithId) { |
| 108 | + const that = this |
| 109 | + return function (evt) { |
| 110 | + //We only send a moveNode command if it is not considered as a drop |
| 111 | + if (!that.isDropActionOnANode(visuWithId, evt.cy.elements(), this.renderedPosition(), evt.target)) |
| 112 | + that.sendCommand([{id: visuWithId.visuId, nodeId: evt.target.id(), command: "moveNode", position: evt.target.position()}], false); |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + isDropActionOnANode(visuWithId, candidates, pos, droppedNode) { |
| 117 | + var target; |
| 118 | + |
| 119 | + for (let i = 0; i < candidates.length; i++) { |
| 120 | + var node = candidates[i]; |
| 121 | + var bound = node.renderedBoundingBox(); |
| 122 | + if (node != droppedNode && bound.x1 < pos.x && bound.x2 > pos.x && bound.y1 < pos.y && bound.y2 > pos.y) { |
| 123 | + //here we found a node correctly positionned and *WARNING* we keep the last one so with the closest zIndex |
| 124 | + target = node; |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + // if we found a target and this one has a drop interaction then we request the server |
| 129 | + if (target && target.dropInteraction) { |
| 130 | + this.sendCommand([{id: visuWithId.visuId, nodeId: droppedNode.id(), command: "dropNode", targetNode: target.id()}]); |
| 131 | + return true; |
| 132 | + } else |
| 133 | + return false; |
| 134 | + } |
| 135 | + |
| 136 | + // I manage the events I receive from Cytoscape to send to Telescope. |
| 137 | + // I implement some mecanism to make the management of the events better for the user. |
| 138 | + // First, I implement a mecanisme to avoid to double fire an event. |
| 139 | + // Second, I implement an mecanisme to workaround a bug where cytoscape do not send a mouse out event sometimes. |
| 140 | + // To do that, I keep the current mouseover event and when I mouseover another element, I send an emulated mouseout event for this saved event to the websocket if there was no mouseout event received before. |
| 141 | + // This allows TelescopeCytoscape to not accumulate mouse over interactions when we do not receive a mouse out event. |
| 142 | + createEventFunction(visuWithId) { |
| 143 | + const that = this |
| 144 | + return function (evt) { |
| 145 | + that.clearOverInteraction() |
| 146 | + var visu = visuWithId.visu |
| 147 | + if (evt.target.id != null && !visuWithId.protectDoubleFire[evt.type] && (!visu.animated() || (evt.type == "mouseout"))) { |
| 148 | + // Sometimes cytoscape fail to send a mouseout event. This can cause multiple tooltip to stay visible when they should not. |
| 149 | + // In order to improve the usability of Telescope, when we go over of any element, we also hide the other tooltips that are visible and that should show on a mouse over. |
| 150 | + if (evt.type == "mouseover") { |
| 151 | + visu.elements().each(function (element) { |
| 152 | + var qtipAPI = element.qtip("api") |
| 153 | + if (element != evt.target && qtipAPI && qtipAPI.tooltip && qtipAPI.tooltip.is(":visible") && qtipAPI.options.show.event == "mouseover") { |
| 154 | + qtipAPI.hide() |
| 155 | + } |
| 156 | + }) |
| 157 | + } |
| 158 | + |
| 159 | + // Server interaction processing |
| 160 | + if (!((!evt.target["mouseOverInteraction"]) && ((evt.type == "mouseover") || (evt.type == "mouseout")))) { |
| 161 | + visuWithId.protectDoubleFire[evt.type] = true |
| 162 | + setTimeout(function () { |
| 163 | + visuWithId.protectDoubleFire[evt.type] = false |
| 164 | + }, self.reactTime * 4) |
| 165 | + |
| 166 | + // If we have a mouseout event and a previously saved mouseover event, we remove the saved event. |
| 167 | + if ((evt.type == "mouseout") && visu.currentOveredElement != null && visu.currentOveredElement.target.id() == evt.target.id()) { |
| 168 | + visu.currentOveredElement = null |
| 169 | + } |
| 170 | + |
| 171 | + // If we receive a mouseover event, we discard the potentially saved mouseover event for which we did not received a mouseout event from cytoscape. Then save the new mouseover event. |
| 172 | + if (evt.type == "mouseover") { |
| 173 | + if (visu.currentOveredElement != null) { |
| 174 | + that.sendCommand([{ |
| 175 | + id: visuWithId.visuId, |
| 176 | + drawableId: visu.currentOveredElement.target.id(), |
| 177 | + command: "interaction", |
| 178 | + kind: (TLJS.eventsWithInteractions["mouseout"]) |
| 179 | + }]) |
| 180 | + visu.currentOveredElement = null |
| 181 | + } |
| 182 | + visu.currentOveredElement = evt |
| 183 | + } |
| 184 | + |
| 185 | + that.sendCommand([{ |
| 186 | + id: visuWithId.visuId, |
| 187 | + drawableId: evt.target.id(), |
| 188 | + command: "interaction", |
| 189 | + kind: (TLJS.eventsWithInteractions[evt.type]) |
| 190 | + }]) |
| 191 | + // menu management |
| 192 | + if (evt.type == "cxttap" && evt.target["menu"]) { |
| 193 | + that.displayMenuForElement(evt.target, visuWithId.visuId, { |
| 194 | + x: evt.originalEvent.clientX, |
| 195 | + y: evt.originalEvent.clientY |
| 196 | + }) |
| 197 | + visu.container().style.cursor = "" |
| 198 | + } |
| 199 | + } |
| 200 | + } |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + delaisEvtFunction(evtFunction) { |
| 205 | + const that = this |
| 206 | + return function (evt) { |
| 207 | + that.clearOverInteraction(); |
| 208 | + that.timeoutId = setTimeout(function() { |
| 209 | + evt.target.onmouseover= null; |
| 210 | + evtFunction(evt); |
| 211 | + }, self.reactTime); |
| 212 | + } |
| 213 | + } |
| 214 | + |
| 215 | + clearOverInteraction(){ |
| 216 | + if(this.timeoutId!=null){ |
| 217 | + clearTimeout(this.timeoutId); |
| 218 | + this.timeoutId=null; |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + sendGenerationCommand(visusIds, port) { |
| 223 | + const websocket = TLJS.getWebSocket(port) |
| 224 | + if (websocket.readyState == 0) { |
| 225 | + this.visusWaitingConnectToGenerate = this.visusWaitingConnectToGenerate.concat(visusIds) |
| 226 | + } else { |
| 227 | + const messages = visusIds.reduce((acc, id) => { |
| 228 | + if (document.getElementById(id) != null) { |
| 229 | + acc.push({id, command: "generate"}) |
| 230 | + } |
| 231 | + return acc |
| 232 | + }, []) |
| 233 | + websocket.send(JSON.stringify(messages)) |
| 234 | + this.visusWaitingConnectToGenerate = [] |
| 235 | + } |
| 236 | + } |
| 237 | + |
| 238 | + sendCommand(jsonArr, progress, port) { |
| 239 | + if (progress !== false) // strict equality to accept null as true value for progress |
| 240 | + for (var i = 0; i < jsonArr.length; i++) |
| 241 | + this.visuWithId(jsonArr[0].id).container().style.cursor = "progress" |
| 242 | + TLJS.getWebSocket(port).send(JSON.stringify(jsonArr)) |
| 243 | + } |
| 244 | + |
| 245 | + |
| 246 | + // I am called when the websocket has an error event |
| 247 | + onError(evt) { |
| 248 | + this.tryReconnect = false |
| 249 | + } |
| 250 | + |
| 251 | + // I am called when the websocket is closing. |
| 252 | + onClose(evt) { |
| 253 | + this.tryReconnect = this.tryReconnect && this.pageHaveVisu() |
| 254 | + if (this.tryReconnect) { |
| 255 | + TLJS.resetWebSocket(visus) |
| 256 | + } |
| 257 | + } |
| 258 | + |
| 259 | + onMessage(evt) { |
| 260 | + const commands = JSON.parse(evt.data) |
| 261 | + const toUpdate = {} |
| 262 | + const needNotify = {} |
| 263 | + commands.forEach(command => { |
| 264 | + if (command.visuId) { |
| 265 | + needNotify[command.visuId] = true |
| 266 | + } |
| 267 | + const visu = this.visuWithId(command.visuId) |
| 268 | + if (this.commandsAction.has(command.command)) { |
| 269 | + this.commandsAction.get(command.command)(command, visu, toUpdate) |
| 270 | + } else { |
| 271 | + console.log("unsupported command: " + command.command) |
| 272 | + } |
| 273 | + }) |
| 274 | + //customize in one visu rendering all element toUpdate |
| 275 | + this.customizeAll(toUpdate) |
| 276 | + Object.keys(needNotify).forEach(visuId => { |
| 277 | + this.notifyMessageEnd(needNotify[visuId]) |
| 278 | + this.visuWithId(visuId).container().style.cursor = "" |
| 279 | + }) |
| 280 | + this.notifyMessageEnd("onAll") |
| 281 | + } |
| 282 | + |
| 283 | + notifyMessageEnd(visuId) { |
| 284 | + if (this.toNotifyMessage[visuId] != null) |
| 285 | + for (let i = 0; i < this.toNotifyMessage[visuId].length; i++) { |
| 286 | + this.toNotifyMessage[visuId][i]() |
| 287 | + } |
| 288 | + } |
| 289 | + |
| 290 | + //Return true if the page has at least one visualization |
| 291 | + pageHaveVisu() { |
| 292 | + return this.visus.size > 0 |
| 293 | + } |
| 294 | + |
| 295 | + static visuWithId(aVisuId) { |
| 296 | + return this.singleton.visuWithId(aVisuId) |
| 297 | + } |
| 298 | + |
| 299 | + visuWithId(aVisuId) { |
| 300 | + return this.visus.get(document.getElementById(aVisuId)) |
| 301 | + } |
| 302 | + |
| 303 | + toBatchCommand(command, visu, toUpdate) { |
| 304 | + toUpdate[command.visuId] = toUpdate[command.visuId] || [] |
| 305 | + toUpdate[command.visuId].push(command) |
| 306 | + } |
| 307 | + |
| 308 | + customizeAll(toUpdate) { |
| 309 | + const visuIDs = Object.keys(toUpdate) |
| 310 | + for (var i = 0; i < visuIDs.length; i++) { |
| 311 | + const cmds = toUpdate[visuIDs[i]] |
| 312 | + const visu = this.visuWithId(visuIDs[i]) |
| 313 | + visu.startBatch() |
| 314 | + for (var j = 0; j < cmds.length; j++) { |
| 315 | + this.commandsActionBatch[cmds[j].command](cmds[j], visu) |
| 316 | + } |
| 317 | + visu.endBatch() |
| 318 | + } |
| 319 | + } |
| 320 | + |
| 321 | + removeWaitingForVisuId(aVisuId) { |
| 322 | + try { |
| 323 | + this.waitingDivByVisu[aVisuId].parentNode.removeChild(this.waitingDivByVisu[aVisuId]) |
| 324 | + this.waitingDivByVisu[aVisuId] = null |
| 325 | + } catch (err) { |
| 326 | + //Here the waiting has been removed a previous time |
| 327 | + console.log(err) |
| 328 | + } |
| 329 | + } |
| 330 | + |
| 331 | + customizeElement(element, commandParametersForElement) { |
| 332 | + // here we define the attribute for mouse over to avoid sending request to the server if unnecessary |
| 333 | + if ((commandParametersForElement.mouseOverInteraction !== null)) |
| 334 | + element["mouseOverInteraction"] = commandParametersForElement.mouseOverInteraction |
| 335 | + // here we define the attribute for mouse over to avoid sending request to the server if unnecessary |
| 336 | + if ((commandParametersForElement.dropInteraction !== null)) |
| 337 | + element["dropInteraction"] = commandParametersForElement.dropInteraction |
| 338 | + // here we define a popup if the element has one |
| 339 | + if (commandParametersForElement.popUp) { |
| 340 | + element.popUp = commandParametersForElement.popUp |
| 341 | + element.qtip(commandParametersForElement.popUp) |
| 342 | + } |
| 343 | + // here we define a menu if element has one |
| 344 | + if (commandParametersForElement.menu) { |
| 345 | + element["menu"] = commandParametersForElement.menu |
| 346 | + } |
| 347 | + } |
| 348 | + |
| 349 | + addStaticLegendEntry(visuId, html) { |
| 350 | + var div = document.getElementById(visuId + "legend") |
| 351 | + if (!div) { |
| 352 | + var legendInfoId = "legend" + this.id |
| 353 | + this.id++ |
| 354 | + div = $("<div>", {id: visuId + "legend", class: "tlLegend"}) |
| 355 | + .html("<span style=\"font-weight:bold;\">Legend</span> <div style=\"float: right; margin-left: 15px;\"><span id=\"" + legendInfoId + "\" style=\"cursor: help; margin-right: 5px;\">�</span><div style=\"display: none\"><strong>Box selection</strong><hr>It is possible to move multiple nodes at a time in the visualization by maintaining the SHIFT key (or three finger swipe on tablet) while doing a box selection. You can then move the selected node in group. You can cancel the selection by clicking somewhere in the visualization.</div><img data-fold style=\"cursor: pointer;\" src=\"/files/CYSFileLibrary/arrowUp.png\" onclick=\"TLJS.toggleLegend(this);\"></div><table></table>")[0] |
| 356 | + document.getElementById(visuId).appendChild(div) |
| 357 | + $("#" + legendInfoId).qtip({ |
| 358 | + content: { |
| 359 | + text: $("#" + legendInfoId).next() |
| 360 | + } |
| 361 | + }) |
| 362 | + } |
| 363 | + |
| 364 | + div.getElementsByTagName("table")[0].insertRow(-1).innerHTML = html |
| 365 | + } |
| 366 | + |
| 367 | + setUpCommand() { |
| 368 | + this.commandsAction.set("add", (command, visu) => { |
| 369 | + // Since v3.3.0 of cytoscape, it is not possible anymore to create a node with a style in the parameters. |
| 370 | + // Here we create one stylesheet for each element and my add the related class to objet to create. |
| 371 | + // Performance wise, it is betten than creating the nodes then applying a style bypass to each of them. |
| 372 | + // Maybe in the future we can do better but it would mean a good refactoring of the connector. |
| 373 | + // We would need to group the nodes to add and update by stylesheet. This mean that the multiple add command would no longer be a simple composite and actions should keep state to keep the list of stylesheets. |
| 374 | + let elementClass |
| 375 | + command.parameters.forEach(parameter => { |
| 376 | + elementClass = "element" + this.id |
| 377 | + this.id++ |
| 378 | + visu.style().selector("." + elementClass).css(parameter.style) |
| 379 | + parameter.style = null |
| 380 | + parameter.classes = [elementClass] |
| 381 | + }) |
| 382 | + |
| 383 | + const elements = visu.add(command.parameters) |
| 384 | + for (let elementId = 0; elementId < elements.length; elementId++) { |
| 385 | + this.customizeElement(elements[elementId], command.parameters[elementId]) |
| 386 | + } |
| 387 | + }) |
| 388 | + |
| 389 | +//just register visu to remove cursor progress |
| 390 | + let toBatchCommand = this.toBatchCommand.bind(this) |
| 391 | + this.commandsAction.set("acknoledgeReceipt", toBatchCommand) |
| 392 | + this.commandsActionBatch = {} |
| 393 | + this.commandsActionBatch.acknoledgeReceipt = function () { |
| 394 | + } |
| 395 | + |
| 396 | + this.commandsAction.set("remove", toBatchCommand) |
| 397 | + this.commandsActionBatch.remove = function (command, visu) { |
| 398 | + visu.remove(visu.getElementById(command.nodeId)) |
| 399 | + } |
| 400 | + |
| 401 | + this.commandsAction.set("positioning", (command, visu) => { |
| 402 | + visu.layout(command.layout).run() |
| 403 | + }) |
| 404 | + |
| 405 | + this.commandsAction.set("customize", toBatchCommand) |
| 406 | + this.commandsActionBatch.customize = function (command, visu) { |
| 407 | + let element = visu.getElementById(command.elementId) |
| 408 | + element.style(command.style) |
| 409 | + element.mouseOverInteraction = command.mouseOverInteraction |
| 410 | + } |
| 411 | + |
| 412 | + this.commandsAction.set("addStaticLegendEntry", (command) => { |
| 413 | + this.addStaticLegendEntry(command.visuId, command.html) |
| 414 | + }) |
| 415 | + |
| 416 | + this.commandsAction.set("removeLegend", (command) => { |
| 417 | + $("#" + command.visuId + "legend").find("table").children().remove() |
| 418 | + }) |
| 419 | + |
| 420 | + this.commandsAction.set("refreshNode", (command, visu) => { |
| 421 | + visu.$("#" + command.data.id).changeData(command) |
| 422 | + }) |
| 423 | + |
| 424 | + this.commandsAction.set("callbackUrl", (command, visu) => { |
| 425 | + sendCallBack(command.callbackUrl, command.openInNewTab) |
| 426 | + }) |
| 427 | + |
| 428 | +// Called when we execute a Seaside ajax callback interaction |
| 429 | + this.commandsAction.set("ajax", (command, visu) => { |
| 430 | + $(command.cssQuery).load(command.callbackUrl) |
| 431 | + }) |
| 432 | + |
| 433 | + this.commandsAction.set("generated", (command) => { |
| 434 | + this.removeWaitingForVisuId(command.visuId) |
| 435 | + }) |
| 436 | + |
| 437 | + this.commandsAction.set("error", (command) => { |
| 438 | + if (handleServerError[command.detail]) { |
| 439 | + handleServerError[command.detail](command)//usefull to display messages |
| 440 | + } else if (this.waitingDivByVisu[command.visuId] != null) { |
| 441 | + this.waitingDivByVisu[command.visuId].innerHTML = "An error has occured." |
| 442 | + notify("An error has occured") |
| 443 | + console.log("message error have no display") |
| 444 | + console.log(command) |
| 445 | + } |
| 446 | + onError()//generic handle Error |
| 447 | + }) |
| 448 | + } |
| 449 | + |
| 450 | + static toggleLegend(button) { |
| 451 | + var table = $(button.parentNode.parentNode.getElementsByTagName("table")[0]) |
| 452 | + if (button.dataset.fold) { |
| 453 | + table.fadeIn() |
| 454 | + button.setAttribute("src", "/files/CYSFileLibrary/arrowUp.png") |
| 455 | + button.dataset.fold = "" |
| 456 | + } else { |
| 457 | + table.fadeOut() |
| 458 | + button.setAttribute("src", "/files/CYSFileLibrary/arrowDown.png") |
| 459 | + button.dataset.fold = "true" |
| 460 | + } |
| 461 | + } |
| 462 | + |
| 463 | + static disableUserNotification() { |
| 464 | + notify = function (message) { |
| 465 | + console.log(message) |
| 466 | + } |
| 467 | + } |
| 468 | + |
| 469 | + static notifyMessageEnd(visuId) { |
| 470 | + this.singleton.notifyMessageEnd() |
| 471 | + } |
| 472 | + |
| 473 | + notifyMessageEnd(visuId) { |
| 474 | + if (this.toNotifyMessage[visuId] != null) |
| 475 | + for (let i = 0; i < this.toNotifyMessage[visuId].length; i++) { |
| 476 | + this.toNotifyMessage[visuId][i]() |
| 477 | + } |
| 478 | + } |
| 479 | + |
| 480 | + static enableUserNotification() { |
| 481 | + this.singleton.enableUserNotification() |
| 482 | + } |
| 483 | + |
| 484 | + enableUserNotification() { |
| 485 | + function showNotification(message) { |
| 486 | + new Notification(message) |
| 487 | + } |
| 488 | + |
| 489 | + if (!("Notification" in window)) { |
| 490 | + TLJS.disableUserNotification() |
| 491 | + } else if (Notification.permission === "granted") { |
| 492 | + notify = showNotification |
| 493 | + } else if (Notification.permission !== "denied") { |
| 494 | + Notification.requestPermission(function (permission) { |
| 495 | + if (permission === "granted") { |
| 496 | + notify = showNotification |
| 497 | + } else { |
| 498 | + TLJS.disableUserNotification() |
| 499 | + } |
| 500 | + }) |
| 501 | + } else { |
| 502 | + TLJS.disableUserNotification() |
| 503 | + } |
| 504 | + } |
| 505 | + |
| 506 | + static onMessageEnd(callback, id) { |
| 507 | + this.singleton.onMessageEnd(callback, id) |
| 508 | + } |
| 509 | + |
| 510 | + onMessageEnd(callback, id) { |
| 511 | + if (id != null) { |
| 512 | + if (this.toNotifyMessage[id] == null) |
| 513 | + this.toNotifyMessage[id] = [] |
| 514 | + this.toNotifyMessage[id].push(callback) |
| 515 | + } else { |
| 516 | + this.onMessageEnd(callback, "onAll") |
| 517 | + } |
| 518 | + } |
| 519 | + |
| 520 | + static useExternalTrigger(evt, visuId, triggerId) { |
| 521 | + return this.singleton.useExternalTrigger(evt, visuId, triggerId) |
| 522 | + } |
| 523 | + |
| 524 | + useExternalTrigger(evt, visuId, triggerId) { |
| 525 | + this.sendCommand([{ |
| 526 | + id: visuId, |
| 527 | + command: "externalTrigger", |
| 528 | + triggerId: triggerId, |
| 529 | + kind: (TLJS.eventsWithInteractions[evt.type] || evt.type) |
| 530 | + }]) |
| 531 | + } |
| 532 | + |
| 533 | +} |
| 534 | + |
| 535 | +window.addEventListener("load", () => { |
| 536 | + console.log("loaded") |
| 537 | + TLJS.initVisus() |
| 538 | +}, false) |
| 539 | + |
| 540 | + |
| 541 | +var notify |
| 542 | +TLJS.enableUserNotification() |
| 543 | + |
| 544 | + |
| 545 | +function changeData(command) { |
| 546 | + let self = this |
| 547 | + if (command.style) { |
| 548 | + self.style(command.style) |
| 549 | + } |
| 550 | + visu = self.cy() |
| 551 | + newNode = { |
| 552 | + group: self.group(), |
| 553 | + position: self.position(), |
| 554 | + data: command.data, |
| 555 | + style: self.style() |
| 556 | + } |
| 557 | + edges = self.connectedEdges() |
| 558 | + children = self.children() |
| 559 | +} |
| 560 | + |
| 561 | + |
| 562 | +cytoscape("collection", "changeData", changeData) |
| 563 | + |
| 564 | +console.warn("telescope is deprecated. Use TLJS instead.") |
| 565 | +let telescope = TLJS |
| 566 | + |
| 567 | + |
| 568 | +visuWithId = function (id) { |
| 569 | + console.log("visuWithId is deprecated. Use telescope.visuWithId instead.") |
| 570 | + return telescope.visuWithId(id) |
| 571 | +} |
| 572 | + |
| 573 | +telescope.loadVisuIn = (htmlElement) => { |
| 574 | + TLJS.singleton.initVisus(htmlElement) |
| 575 | +} |
0 commit comments