| <!DOCTYPE html> |
| <!-- |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| --> |
| <div id="bodydiv" class="body"> |
| |
| <div id="viewcontent" class="viewcontent" style="width: 100%;"> |
| <div id="graphdiv" class="graphcontent" style="top: 0px; left: -2500px; width: 5000px; height: 5000px;"></div> |
| <div id="playdiv" style="position: absolute; top: 0x; left: 0px; width: 2500px; height: 5000px; display: none"></div> |
| </div> |
| |
| <script type="text/javascript"> |
| (function graphbody() { |
| |
| /** |
| * Get the current app name. |
| */ |
| var appname = ui.fragmentParams(location)['app']; |
| var ispalette = false; |
| if (isNull(appname)) { |
| appname = ui.fragmentParams(location)['palette']; |
| |
| // Edit a palette instead of a regular app |
| if (!isNull(appname)) |
| ispalette = true; |
| } |
| |
| /** |
| * Set page title. |
| */ |
| document.title = config.windowtitle() + ' - ' + config.logic() + ' - ' + appname; |
| |
| /** |
| * Set header div. |
| */ |
| $('viewhead').innerHTML = '<span id="appTitle" class="cmenu">' + appname + '</span>' + |
| '<input type="button" id="deleteCompButton" title="Delete a component" class="redbutton plusminus" style="position: absolute; top: 4px; left: 5px;" disabled="true" value="-"/>' + |
| '<span style="position: absolute; top: 0px; left: 45px; right: 115px; padding: 0px; background: transparent;"><input id="compValue" type="text" value="" class="flatentry" title="Component value" autocapitalize="off" placeholder="Value" style="position: absolute; left: 0px; top: 4px; width: 100%; display: none;" readonly="readonly"/></span>' + |
| '<input type="button" id="playCompButton" title="View component value" class="graybutton plusminus" style="position: absolute; top: 4px; right: 75px;" disabled="true" value=">"/>' + |
| '<input type="button" id="copyCompButton" title="Copy a component" class="bluebutton" style="position: absolute; top: 4px; right: 40px;" disabled="true" value="C"/>' + |
| '<input type="button" id="addCompButton" title="Add a component" class="bluebutton plusminus" style="position: absolute; top: 4px; right: 5px;" disabled="true" value="+"/>'; |
| |
| if (!ui.isMobile()) |
| $('viewcontent').className = 'viewcontent flatscrollbars'; |
| |
| /** |
| * Track the current app composite, author, and saved XML content. |
| */ |
| var author = ''; |
| var editable = false; |
| var composite; |
| var savedcomposxml = ''; |
| |
| /** |
| * Component value field, add, delete and play buttons. |
| */ |
| var cvalue = $('compValue'); |
| var atitle = $('appTitle'); |
| var cadd = $('addCompButton'); |
| var cdelete = $('deleteCompButton'); |
| var ccopy = $('copyCompButton'); |
| var cplay = $('playCompButton'); |
| |
| /** |
| * Init componnent references. |
| */ |
| var editorComp = sca.component("Editor"); |
| var palettes = sca.reference(editorComp, "palettes"); |
| var composites = sca.reference(editorComp, ispalette? "palettes" : "composites"); |
| |
| /** |
| * Composite rendering functions. |
| */ |
| var graph = {}; |
| |
| /** |
| * Basic colors |
| */ |
| graph.colors = {}; |
| graph.colors.black = '#000000'; |
| graph.colors.blue = '#0000ff'; |
| graph.colors.cyan = '#00ffff'; |
| graph.colors.gray = '#808080' |
| graph.colors.lightgray = '#dcdcdc' |
| graph.colors.green = '#00ff00'; |
| graph.colors.magenta = '#ff00ff'; |
| graph.colors.orange = '#ffa500'; |
| graph.colors.pink = '#ffc0cb'; |
| graph.colors.purple = '#800080'; |
| graph.colors.red = '#ff0000'; |
| graph.colors.white = '#ffffff'; |
| graph.colors.yellow = '#ffff00'; |
| graph.colors.link = '#357ae8'; |
| |
| graph.colors.orange1 = '#ffd666'; |
| graph.colors.green1 = '#bbe082'; |
| graph.colors.blue1 = '#66dbdf'; |
| graph.colors.yellow1 = '#fdf57a'; |
| graph.colors.cyan1 = '#e6eafb'; |
| graph.colors.lightgray1 = '#eaeaea' |
| graph.colors.pink1 = '#ffd9e0'; |
| graph.colors.red1 = '#d03f41'; |
| graph.colors.white1 = '#ffffff'; |
| |
| graph.colors.orange2 = '#ffbb00'; |
| graph.colors.green2 = '#96d333'; |
| //graph.colors.blue2 = '#0d7cc1'; |
| graph.colors.blue2 = '#00c3c9'; |
| graph.colors.red2 = '#d03f41'; |
| graph.colors.yellow2 = '#fcee21'; |
| graph.colors.magenta2 = '#c0688a'; |
| graph.colors.cyan2 = '#d5dcf9'; |
| graph.colors.lightgray2 = '#dcdcdc' |
| graph.colors.pink2 = '#ffc0cb'; |
| graph.colors.white2 = '#ffffff'; |
| |
| graph.colors.orange3 = '#ffc700'; |
| graph.colors.green3 = '#92e120'; |
| graph.colors.blue3 = '#008fd1'; |
| graph.colors.yellow3 = '#fdf400'; |
| graph.colors.cyan3 = '#b4d3fd'; |
| graph.colors.lightgray3 = '#e3e3e3' |
| graph.colors.pink3 = '#da749b'; |
| graph.colors.red3 = '#ed3f48'; |
| graph.colors.white3 = '#ffffff'; |
| |
| /** |
| * Default positions and sizes. |
| */ |
| graph.palcx = 2500; |
| graph.proxcx = 20; |
| graph.proxcy = 20; |
| graph.buttoncx = 55; |
| graph.buttoncy = 23; |
| graph.curvsz = 4; |
| graph.tabsz = 2; |
| graph.titlex = 4; |
| graph.titley = 0; |
| |
| /** |
| * Make a composite graph editor. |
| */ |
| graph.mkedit = function(graphdiv, pos, atitle, cvalue, cadd, ccopy, cdelete, onchange, onselect) { |
| |
| // Track element dragging and selection |
| graph.dragging = null; |
| graph.dragged = false; |
| graph.selected = null; |
| cvalue.readOnly = true; |
| cvalue.style.display = 'none'; |
| atitle.style.display = 'block'; |
| ccopy.disabled = true; |
| cdelete.disabled = true; |
| cadd.disabled = !editable; |
| |
| // Register event listeners |
| graph.oncomposchange = onchange; |
| graph.oncompselect = onselect; |
| |
| /** |
| * Find the first draggable element in a hierarchy of elements. |
| */ |
| function draggable(n) { |
| //debug('draggable', n); |
| if (n == graphdiv || n == null) |
| return null; |
| if (n.className == 'g' && !isNull(n.id) && n.id != '') |
| return n; |
| return draggable(n.parentNode); |
| } |
| |
| /** |
| * Render component move animation. |
| */ |
| function onmoveanimation() { |
| //debug('onmoveanimation'); |
| |
| // Stop animation if we're not dragging an element anymore |
| if (graph.dragging == null) |
| return true; |
| |
| // Request the next animation frame |
| ui.animation(onmoveanimation); |
| |
| // Nothing to do if the selected component has not moved |
| if (graph.moveX == graph.dragX && graph.moveY == graph.dragY) |
| return true; |
| |
| // Remember that the selected component has been dragged |
| graph.dragged = true; |
| |
| // Cut wire to the dragged component |
| if (graph.dragging.parentNode != graphdiv) { |
| var compos = scdl.composite(graphdiv.compos); |
| setElement(compos, graph.sortcompos(graph.cutwire(graph.dragging, compos, graphdiv))); |
| |
| // Bring component to the top |
| graph.bringtotop(graph.dragging, graphdiv); |
| } |
| |
| // Calculate new position of the dragged component |
| var gpos = graph.relpos(graph.dragging); |
| var newX = gpos.x + (graph.moveX - graph.dragX); |
| var newY = gpos.y + (graph.moveY - graph.dragY); |
| if (newX >= graph.palcx) |
| graph.dragX = graph.moveX |
| else |
| newX = graph.palcx; |
| if (newY >= 0) |
| graph.dragY = graph.moveY; |
| else |
| newY = 0; |
| |
| // Move it |
| graph.move(graph.dragging, graph.mkpath().pos(newX, newY)); |
| |
| return false; |
| }; |
| |
| /** |
| * Handle a mouse down or touch start event. |
| */ |
| function onmousedown(e) { |
| |
| // Remember mouse or touch position |
| var pos = typeof e.touches != "undefined" ? e.touches[0] : e; |
| graph.downX = pos.screenX; |
| graph.downY = pos.screenY; |
| graph.moveX = pos.screenX; |
| graph.moveY = pos.screenY; |
| |
| // Engage the click component selection right away |
| // on mouse controlled devices |
| if (typeof e.touches == 'undefined') |
| onclick(e); |
| |
| // Find and remember draggable component |
| var dragging = draggable(e.target); |
| if (dragging == null || dragging != graph.selected) |
| return true; |
| graph.dragging = dragging; |
| graph.dragged = false; |
| |
| // Remember current drag position |
| graph.dragX = pos.screenX; |
| graph.dragY = pos.screenY; |
| |
| // Start move animation |
| ui.animation(onmoveanimation); |
| |
| e.preventDefault(); |
| return true; |
| }; |
| |
| if (!ui.isMobile()) { |
| graphdiv.onmousedown = function(e) { |
| //debug('onmousedown'); |
| return onmousedown(e); |
| } |
| } else { |
| graphdiv.ontouchstart = function(e) { |
| //debug('ontouchstart'); |
| return onmousedown(e); |
| } |
| } |
| |
| /** |
| * Handle a mouse up or touch end event. |
| */ |
| function onmouseup(e) { |
| |
| // Engage the click component selection now on touch devices |
| if (ui.isMobile()) { |
| if (!graph.dragged && graph.moveX == graph.downX && graph.moveY == graph.downY) |
| return onclick(e); |
| } |
| |
| // Stop here if the component was not dragged |
| if (graph.dragging == null) |
| return true; |
| if (!graph.dragged) { |
| graph.dragging = null; |
| return true; |
| } |
| |
| if (graph.dragging.parentNode == graphdiv && graph.dragging.id.substring(0, 8) != 'palette:') { |
| |
| // Add new dragged component to the composite |
| if (isNull(graph.dragging.compos)) { |
| var compos = scdl.composite(graphdiv.compos); |
| setElement(compos, graph.sortcompos(graph.addcomps(mklist(graph.dragging.comp), compos))); |
| graph.dragging.compos = graphdiv.compos; |
| } |
| |
| // Update component position |
| setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.abspos(graph.draggingg))); |
| |
| // Wire component to neighboring reference |
| if (!isNull(graph.dragging.svcpos)) { |
| var compos = scdl.composite(graphdiv.compos); |
| setElement(compos, graph.sortcompos(graph.clonerefs(graph.wire(graph.dragging, compos, graphdiv)))); |
| } |
| |
| // Snap top level component position to grid |
| if (graph.dragging.parentNode == graphdiv) { |
| var gpos = graph.relpos(graph.dragging); |
| setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.mkpath().pos(graph.gridsnap(gpos.x), graph.gridsnap(gpos.y)))); |
| } |
| } |
| |
| // Forget current dragged component |
| graph.dragging = null; |
| graph.dragged = false; |
| |
| // Refresh the composite |
| //debug('onmouseup refresh'); |
| var nodes = graph.refresh(graphdiv); |
| |
| // Reselect the previously selected component |
| if (!isNull(graph.selected)) { |
| graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes); |
| graph.compselect(graph.selected, true, atitle, cvalue, ccopy, cdelete); |
| |
| // Trigger component select event |
| graph.oncompselect(graph.selected); |
| } |
| |
| // Trigger composite change event |
| graph.oncomposchange(false); |
| return true; |
| }; |
| |
| if (!ui.isMobile()) { |
| graphdiv.onmouseup = function(e) { |
| //debug('onmouseup'); |
| return onmouseup(e); |
| } |
| } else { |
| graphdiv.ontouchend = function(e) { |
| //debug('ontouchend'); |
| return onmouseup(e); |
| } |
| } |
| |
| /** |
| * Handle a mouse or touch click event. |
| */ |
| function onclick(e) { |
| //debug('onclick logic'); |
| |
| // Find selected component |
| var selected = draggable(e.target); |
| if (selected == null) { |
| if (graph.selected != null) { |
| |
| // Reset current selection |
| graph.compselect(graph.selected, false, atitle, cvalue, ccopy, cdelete); |
| graph.selected = null; |
| |
| // Trigger component select event |
| graph.oncompselect(null); |
| } |
| |
| // Dismiss the palette |
| if (e.target == graphdiv && ui.numpos(graphdiv.style.left) != (graph.palcx * -1)) |
| graphdiv.style.left = ui.pixpos(graph.palcx * -1); |
| |
| return true; |
| } |
| |
| |
| // Ignore duplicate click events |
| if (selected == graph.selected) |
| return true; |
| if (selected.id.substring(0, 8) == 'palette:' && ui.numpos(graphdiv.style.left) != 0) |
| return true; |
| |
| // Deselect previously selected component |
| graph.compselect(graph.selected, false, atitle, cvalue, ccopy, cdelete); |
| |
| // Clone component from the palette |
| if (selected.id.substring(0, 8) == 'palette:') { |
| var compos = scdl.composite(graphdiv.compos); |
| var comp = graph.clonepalette(selected, compos); |
| setElement(compos, graph.sortcompos(graph.addcomps(mklist(comp), compos))); |
| |
| // Move into the editing area and hide the palette |
| graphdiv.style.left = ui.pixpos(graph.palcx * -1); |
| |
| // Refresh the composite |
| //debug('onclick refresh'); |
| var nodes = graph.refresh(graphdiv); |
| |
| // Reselect the previously selected component |
| graph.selected = graph.findcompnode(scdl.name(comp), nodes); |
| graph.compselect(graph.selected, true, atitle, cvalue, ccopy, cdelete); |
| |
| // Trigger component select event |
| graph.oncompselect(graph.selected); |
| |
| // Trigger composite change event |
| graph.oncomposchange(true); |
| |
| } else { |
| graph.selected = selected; |
| |
| // Select the component |
| graph.compselect(graph.selected, true, atitle, cvalue, ccopy, cdelete); |
| |
| // Trigger component select event |
| graph.oncompselect(graph.selected); |
| } |
| |
| //debug('comp selected'); |
| |
| e.preventDefault(); |
| return true; |
| } |
| |
| if (!ui.isMobile()) { |
| graphdiv.onclick = function(e) { |
| //debug('onclick'); |
| return onclick(e); |
| } |
| } else { |
| graphdiv.onclick = function(e) { |
| //debug('onclick'); |
| return onclick(e); |
| } |
| } |
| |
| /** |
| * Handle a mouse or touch move event. |
| */ |
| if (!ui.isMobile()) { |
| window.onmousemove = function(e) { |
| //debug('onmousemove'); |
| |
| // Record mouse position |
| graph.moveX = e.screenX; |
| graph.moveY = e.screenY; |
| if (graph.dragging == null) |
| return true; |
| return false; |
| } |
| } else { |
| graphdiv.ontouchmove = function(e) { |
| //debug('ontouchmove'); |
| |
| // Record touch position |
| var pos = e.touches[0]; |
| graph.moveX = pos.screenX; |
| graph.moveY = pos.screenY; |
| if (graph.dragging == null) |
| return true; |
| return false; |
| } |
| } |
| |
| /** |
| * Handle field on change events. |
| */ |
| function onvaluechange() { |
| if (graph.selected == null) |
| return false; |
| if (graphdiv.parentNode.style.display == 'none') |
| return false; |
| |
| // Change component name and refactor references to it |
| function changename() { |
| var compos = scdl.composite(graphdiv.compos); |
| cvalue.value = graph.ucid(cvalue.value, compos, false); |
| graph.selected.id = cvalue.value; |
| setElement(compos, graph.sortcompos(graph.renamecomp(graph.selected.comp, compos, cvalue.value))); |
| |
| // Refresh the composite |
| //debug('onchangename refresh'); |
| var nodes = graph.refresh(graphdiv); |
| |
| // Reselected the previously selected component |
| graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes); |
| graph.compselect(graph.selected, true, atitle, cvalue, ccopy, cdelete); |
| |
| // Trigger component select event |
| graph.oncompselect(graph.selected); |
| |
| // Trigger composite change event |
| graph.oncomposchange(true); |
| return false; |
| } |
| |
| // Change the component property value |
| function changeprop() { |
| graph.setproperty(graph.selected.comp, cvalue.value); |
| var hasprop = graph.hasproperty(graph.selected.comp); |
| cvalue.readOnly = (hasprop? false : true) || !editable; |
| cvalue.style.display = hasprop? 'block' : 'none'; |
| atitle.style.display = hasprop? 'none' : 'block'; |
| cvalue.value = graph.property(graph.selected.comp); |
| |
| // Refresh the composite |
| //debug('onchangeprop refresh'); |
| var nodes = graph.refresh(graphdiv); |
| |
| // Reselected the previously selected component |
| graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes); |
| graph.compselect(graph.selected, true, atitle, cvalue, ccopy, cdelete); |
| |
| // Trigger component select event |
| graph.oncompselect(graph.selected); |
| |
| // Trigger composite change event |
| graph.oncomposchange(true); |
| return false; |
| } |
| |
| return graph.hasproperty(graph.selected.comp)? changeprop() : changename(); |
| }; |
| |
| cvalue.onchange = function() { |
| return onvaluechange(); |
| } |
| |
| // Handle delete event |
| function ondeleteclick() { |
| if (graph.selected == null) |
| return false; |
| if (graph.selected.id.substring(0, 8) != 'palette:') { |
| |
| // Remove selected component |
| var compos = scdl.composite(graphdiv.compos); |
| if (isNull(graph.selected.compos)) |
| setElement(compos, graph.sortcompos(graph.cutwire(graph.selected, compos, graphdiv))); |
| setElement(compos, graph.sortcompos(graph.clonerefs(graph.gcollect(graph.removecomp(graph.selected.comp, compos))))); |
| |
| // Reset current selection |
| graph.compselect(graph.selected, false, atitle, cvalue, ccopy, cdelete); |
| graph.selected = null; |
| |
| // Refresh the composite |
| //debug('ondelete refresh'); |
| graph.refresh(graphdiv); |
| |
| // Trigger component select event |
| graph.oncompselect(null); |
| |
| // Trigger composite change event |
| graph.oncomposchange(true); |
| } |
| return false; |
| }; |
| |
| cdelete.onclick = function() { |
| return ondeleteclick(); |
| }; |
| |
| // Handle copy event |
| function oncopyclick() { |
| if (graph.selected == null) |
| return false; |
| if (graph.selected.id.substring(0, 8) == 'palette:') |
| return false; |
| |
| // Clone the selected component |
| var compos = scdl.composite(graphdiv.compos); |
| var comps = graph.clonecomp(graph.selected, compos); |
| setElement(compos, graph.sortcompos(graph.addcomps(comps, compos))); |
| |
| // Refresh the composite |
| //debug('oncopyclick refresh'); |
| var nodes = graph.refresh(graphdiv); |
| |
| // Select the component clone |
| graph.selected = graph.findcompnode(scdl.name(car(comps)), nodes); |
| graph.compselect(graph.selected, true, atitle, cvalue, ccopy, cdelete); |
| |
| // Trigger component select event |
| graph.oncompselect(graph.selected); |
| |
| // Trigger composite change event |
| graph.oncomposchange(true); |
| |
| return false; |
| }; |
| |
| ccopy.onclick = function() { |
| return oncopyclick(); |
| }; |
| |
| // Handle add event |
| cadd.onclick = function() { |
| |
| // Show the palette |
| graphdiv.style.left = ui.pixpos(0); |
| return false; |
| }; |
| |
| // Create a hidden element to help compute the width of |
| // component and reference titles |
| graph.offtitles = document.createElement('span'); |
| graph.offtitles.style.visibility = 'hidden'; |
| graph.offtitles.style.display = 'block'; |
| graph.offtitles.position = 'absolute'; |
| graph.offtitles.top = -500; |
| graph.offtitles.width = 500; |
| graph.offtitles.height = 50; |
| graphdiv.appendChild(graph.offtitles); |
| |
| return graphdiv; |
| }; |
| |
| /** |
| * Point class. |
| */ |
| graph.Point = function(x, y) { |
| this.x = x; |
| this.y = y; |
| }; |
| |
| graph.mkpoint = function(x, y) { |
| return new graph.Point(x, y); |
| }; |
| |
| /** |
| * Path class. |
| */ |
| graph.Path = function() { |
| this.x = 0; |
| this.y = 0; |
| this.xmin = null; |
| this.xmax = null; |
| this.xmin = -8; |
| this.ymax = null; |
| this.draw = function(ctx) { |
| return ctx; |
| }; |
| } |
| graph.Path.prototype.pos = function(x, y) { |
| this.x = x; |
| this.y = y; |
| if (this.xmin == null || x < this.xmin) this.xmin = x; |
| if (this.xmax == null || x > this.xmax) this.xmax = x; |
| if (this.ymin == null || y < this.ymin) this.ymin = y; |
| if (this.ymax == null || y > this.ymax) this.ymax = y; |
| return this; |
| }; |
| graph.Path.prototype.rmove = function(x, y) { |
| return this.move(this.x + x, this.y + y); |
| }; |
| graph.Path.prototype.rline = function(x, y) { |
| return this.line(this.x + x, this.y + y); |
| }; |
| graph.Path.prototype.rcurve = function(x1, y1, x, y) { |
| return this.curve(this.x + x1, this.y + y1, this.x + x1 + x, this.y + y1 + y); |
| }; |
| graph.Path.prototype.clone = function() { |
| return graph.mkpath().pos(this.x, this.y); |
| }; |
| graph.Path.prototype.move = function(x, y) { |
| var d = this.draw; |
| this.draw = function(ctx) { |
| d(ctx); |
| ctx.moveTo(x, y); |
| return ctx; |
| }; |
| return this.pos(x, y); |
| }; |
| graph.Path.prototype.line = function(x, y) { |
| var d = this.draw; |
| this.draw = function(ctx) { |
| d(ctx); |
| ctx.lineTo(x, y); |
| return ctx; |
| }; |
| return this.pos(x, y); |
| }; |
| graph.Path.prototype.curve = function(x1, y1, x, y) { |
| var d = this.draw; |
| this.draw = function(ctx) { |
| d(ctx); |
| ctx.quadraticCurveTo(x1, y1, x, y); |
| return ctx; |
| }; |
| return this.pos(x, y); |
| }; |
| graph.Path.prototype.end = function() { |
| var d = this.draw; |
| this.draw = function(ctx) { |
| ctx.beginPath(); |
| d(ctx); |
| ctx.fill(); |
| ctx.beginPath(); |
| d(ctx); |
| ctx.stroke(); |
| }; |
| return this; |
| }; |
| graph.Path.prototype.bounds = function() { |
| var width = this.xmin == null || this.xmax == null? 0 : this.xmax - this.xmin + 1; |
| var height = this.ymin == null || this.ymax == null? 0 : this.ymax - this.ymin + 1; |
| return graph.mkpath().pos(width, height); |
| }; |
| |
| graph.mkpath = function() { |
| return new graph.Path(); |
| }; |
| |
| /** |
| * Translate the position of an element. |
| */ |
| graph.translate = function(g, x, y) { |
| var t = 'translate(' + ui.pixpos(x) + ',' + ui.pixpos(y) + ')'; |
| g.style.setProperty('-webkit-transform', t, null); |
| g.style.setProperty('-moz-transform', t, null); |
| g.style.setProperty('-o-transform', t, null); |
| g.style.setProperty('transform', t, null); |
| g.ctmx = x; |
| g.ctmy = y; |
| return g; |
| }; |
| |
| /** |
| * Apply a path to an element. |
| */ |
| graph.drawshape = function(g) { |
| // Set shape element size |
| var b = g.path.bounds(); |
| g.width = b.x + 4; |
| g.height = b.y + 4; |
| |
| // Get canvas context |
| var ctx = g.getContext('2d'); |
| ctx.save(); |
| |
| // Apply translation |
| ctx.translate((g.path.xmin * -1) + 2, (g.path.ymin * -1) + 2); |
| |
| // Draw the shape |
| ctx.fillStyle = g.fillStyle; |
| ctx.strokeStyle = !isNull(g.strokeStyle)? g.strokeStyle : graph.colors.gray; |
| ctx.lineWidth = !isNull(g.lineWidth)? g.lineWidth : 1; |
| g.path.draw(ctx); |
| |
| // Reset canvas context |
| ctx.restore(); |
| return g; |
| } |
| |
| /** |
| * Return an element representing a title. |
| */ |
| graph.mktitle = function(t, x, y) { |
| var title = document.createElement('span'); |
| title.className = 'gtitle'; |
| title.style.left = ui.pixpos(x); |
| title.style.top = ui.pixpos(y); |
| title.appendChild(document.createTextNode(t)); |
| graph.offtitles.appendChild(title); |
| title.style.width = ui.pixpos(title.clientWidth + 2); |
| title.style.height = ui.pixpos(title.clientHeight + 2); |
| return title; |
| }; |
| |
| /** |
| * Return an element representing the title of a component. |
| */ |
| graph.comptitle = function(comp) { |
| return memo(comp, 'title', function() { |
| var ct = graph.title(comp); |
| var pt = graph.propertytitle(comp); |
| if (ct == '' && pt == '') |
| return null; |
| return graph.mktitle((ct != '' && pt != '')? ct + ' ' + pt : ct + pt, graph.titlex, graph.titley); |
| }); |
| }; |
| |
| /** |
| * Return the width of the title of a component. |
| */ |
| graph.comptitlewidth = function(comp) { |
| var title = graph.comptitle(comp); |
| if (isNull(title)) |
| return 0; |
| return title.clientWidth; |
| }; |
| |
| /** |
| * Draw a component shape selection. |
| */ |
| graph.compselect = function(g, s, atitle, cvalue, ccopy, cdelete) { |
| if (isNull(g) || !s) { |
| cvalue.value = ''; |
| cvalue.readOnly = true; |
| cvalue.style.display = 'none'; |
| atitle.style.display = 'block'; |
| ccopy.disabled = true; |
| cdelete.disabled = true; |
| if (isNull(g)) |
| return true; |
| g.shape.strokeStyle = null; |
| g.shape.lineWidth = null; |
| graph.drawshape(g.shape); |
| return true; |
| } |
| |
| cvalue.value = graph.hasproperty(g.comp)? graph.property(g.comp) : g.id; |
| cvalue.readOnly = false || !editable; |
| cvalue.style.display = 'block'; |
| atitle.style.display = 'none'; |
| ccopy.disabled = false || !editable; |
| cdelete.disabled = false || !editable; |
| |
| g.shape.strokeStyle = graph.colors.link; |
| g.shape.lineWidth = 2; |
| graph.drawshape(g.shape); |
| g.parentNode.appendChild(g); |
| return true; |
| }; |
| |
| /** |
| * Draw a palette shape selection. |
| */ |
| graph.paletteselect = function(g, s) { |
| if (isNull(g)) |
| return true; |
| if (!s) { |
| g.shape.strokeStyle = null; |
| g.shape.lineWidth = null; |
| graph.drawshape(g.shape); |
| return true; |
| } |
| |
| g.shape.strokeStyle = graph.colors.link; |
| g.shape.lineWidth = 2; |
| graph.drawshape(g.shape); |
| g.parentNode.appendChild(g); |
| return true; |
| }; |
| |
| /** |
| * Return a node representing a component. |
| */ |
| graph.compnode = function(comp, cassoc, pos) { |
| //debug('compnode', graph.title(comp)); |
| |
| // Create the component shape |
| var shape = document.createElement('canvas'); |
| shape.className = 'path'; |
| shape.fillStyle = graph.color(comp); |
| shape.path = graph.comppath(comp, cassoc); |
| graph.drawshape(shape); |
| |
| // Make the component title element |
| var title = graph.comptitle(comp); |
| |
| // Create a span group element and add the shape and title to it |
| var g = document.createElement('span'); |
| g.className = 'g'; |
| g.comp = comp; |
| g.id = scdl.name(comp); |
| graph.translate(g, pos.x, pos.y); |
| g.pos = pos.clone(); |
| g.appendChild(shape); |
| g.shape = shape; |
| g.style.width = ui.pixpos(shape.width); |
| g.style.height = ui.pixpos(shape.height); |
| if (!isNull(title)) { |
| title.style.left = ui.pixpos(shape.path.xmin * -1); |
| g.appendChild(title); |
| } |
| |
| // Store the positions of the services and references |
| g.refpos = reverse(shape.path.refpos); |
| g.svcpos = reverse(shape.path.svcpos); |
| |
| return g; |
| }; |
| |
| /** |
| * Find the node representing a component. |
| */ |
| graph.findcompnode = function(name, nodes) { |
| if (isNull(nodes)) |
| return null; |
| if (isNull(car(nodes).comp)) |
| return graph.findcompnode(name, cdr(nodes)); |
| if (name == scdl.name(car(nodes).comp)) |
| return car(nodes); |
| var node = graph.findcompnode(name, nodeList(car(nodes).childNodes)); |
| if (!isNull(node)) |
| return node; |
| return graph.findcompnode(name, cdr(nodes)); |
| } |
| |
| /** |
| * Return a graphical group. |
| */ |
| graph.mkgroup = function(pos) { |
| var g = document.createElement('div'); |
| g.className = 'g'; |
| graph.translate(g, pos.x, pos.y); |
| g.pos = pos.clone(); |
| return g; |
| }; |
| |
| /** |
| * Return a node representing a button. |
| */ |
| graph.mkbutton = function(t, pos) { |
| |
| // Create the main button shape |
| var shape = document.createElement('canvas'); |
| shape.className = 'path'; |
| shape.fillStyle = graph.colors.lightgray1; |
| shape.path = graph.buttonpath(); |
| graph.drawshape(shape); |
| |
| // Make the button title |
| var title = graph.mktitle(t, graph.titlex, graph.titley); |
| |
| // Create a group and add the button shape and title to it |
| var g = document.createElement('span'); |
| g.className = 'g'; |
| graph.translate(g, pos.x, pos.y); |
| g.pos = pos.clone(); |
| g.appendChild(shape); |
| g.appendChild(title); |
| |
| // Store the button shape in the group |
| g.shape = shape; |
| |
| return g; |
| }; |
| |
| /** |
| * Return the position of a node relative to its parent. |
| */ |
| graph.relpos = function(g) { |
| var curX = g.ctmx? g.ctmx : 0; |
| var curY = g.ctmy? g.ctmy : 0; |
| return graph.mkpath().pos(curX, curY); |
| }; |
| |
| /** |
| * Move a node. |
| */ |
| graph.move = function(g, pos) { |
| g.pos = pos.clone(); |
| graph.translate(g, g.pos.x, g.pos.y); |
| return g; |
| }; |
| |
| /** |
| * Return the absolute position of a component node. |
| */ |
| graph.abspos = function(e) { |
| if (isNull(e) || e == graphdiv) |
| return graph.mkpath(); |
| var gpos = graph.relpos(e); |
| var pgpos = graph.abspos(e.parentNode); |
| return graph.mkpath().pos(gpos.x + pgpos.x, gpos.y + pgpos.y); |
| }; |
| |
| /** |
| * Bring a component node to the top. |
| */ |
| graph.bringtotop = function(n, g) { |
| if (n == g) |
| return null; |
| graph.move(n, graph.abspos(n)); |
| g.appendChild(n); |
| } |
| |
| /** |
| * Return the title of a SCDL element. |
| */ |
| graph.title = function(e) { |
| var t = scdl.title(e); |
| if (t != null) { |
| if (t == 'gt') |
| return '>' |
| if (t == 'lt') |
| return '<'; |
| if (t.indexOf('{propval}') != -1) |
| return ''; |
| if (t.indexOf('{compname}') == -1) |
| return t; |
| return t.replace('{compname}', scdl.name(e)); |
| } |
| return scdl.name(e); |
| }; |
| |
| /** |
| * Return the property value of a SCDL component. |
| */ |
| graph.property = function(e) { |
| var p = scdl.properties(e); |
| if (isNull(p)) |
| return ''; |
| if (scdl.visible(car(p)) == 'false') |
| return ''; |
| var pv = scdl.propertyValue(car(p)); |
| return pv; |
| }; |
| |
| /** |
| * Return the title of a property of a SCDL component. |
| */ |
| graph.propertytitle = function(e) { |
| var pv = graph.property(e); |
| var t = scdl.title(e); |
| if (t.indexOf('{propval}') == -1) |
| return pv; |
| return t[0] == ' '? t.substr(1).replace('{propval}', pv) : t.replace('{propval}', pv); |
| }; |
| |
| /** |
| * Return true if a SCDL component has a property. |
| */ |
| graph.hasproperty = function(e) { |
| var p = scdl.properties(e); |
| if (isNull(p)) |
| return false; |
| if (scdl.visible(car(p)) == 'false') |
| return false; |
| return true; |
| }; |
| |
| /** |
| * Change the property value of a SCDL component. |
| */ |
| graph.setproperty = function(e, value) { |
| var p = scdl.properties(e); |
| if (isNull(p)) |
| return ''; |
| if (scdl.visible(car(p)) == 'false') |
| return ''; |
| var name = scdl.name(car(p)); |
| setElement(car(p), mklist(element, "'property", mklist(attribute, "'name", name != null? name : "property"), value)); |
| return value; |
| }; |
| |
| /** |
| * Return the color of a SCDL component. |
| */ |
| graph.color = function(comp) { |
| return memo(comp, 'color', function() { |
| var c = scdl.color(comp); |
| return c == null? graph.colors.blue1 : graph.colors[c]; |
| }); |
| }; |
| |
| /** |
| * Return the services on the left side of a component. |
| */ |
| graph.lsvcs = function(comp) { |
| return memo(comp, 'lsvcs', function() { |
| var svcs = scdl.services(comp); |
| if (isNull(svcs)) |
| return mklist(mklist("'element","'service","'attribute","'name",scdl.name(comp))); |
| var l = filter(function(s) { |
| var a = scdl.align(s); |
| var v = scdl.visible(s); |
| return (a == null || a == 'left') && v != 'false'; |
| }, svcs); |
| if (isNull(l)) |
| return mklist(); |
| return mklist(car(l)); |
| }); |
| }; |
| |
| /** |
| * Return the references on the right side of a component. |
| */ |
| graph.rrefs = function(comp) { |
| return memo(comp, 'rrefs', function() { |
| return filter(function(r) { |
| var a = scdl.align(r); |
| var v = scdl.visible(r); |
| return (a == null || a == 'right') && v != 'false'; |
| }, scdl.references(comp)); |
| }); |
| }; |
| |
| /** |
| * Return the height of a reference on the right side of a component. |
| */ |
| graph.rrefheight = function(ref, cassoc) { |
| return memo(ref, 'rheight', function() { |
| var target = assoc(scdl.target(ref), cassoc); |
| if (isNull(target)) |
| return graph.tabsz * 8; |
| return graph.compclosureheight(cadr(target), cassoc); |
| }); |
| }; |
| |
| /** |
| * Return the total height of the references on the right side of a component. |
| */ |
| graph.rrefsheight = function(refs, cassoc) { |
| if (isNull(refs)) |
| return 0; |
| return graph.rrefheight(car(refs), cassoc) + graph.rrefsheight(cdr(refs), cassoc); |
| }; |
| |
| /** |
| * Return the height of a component node. |
| */ |
| graph.compheight = function(comp, cassoc) { |
| return memo(comp, 'height', function() { |
| var lsvcs = graph.lsvcs(comp); |
| var lsvcsh = Math.max(1, length(lsvcs)) * (graph.tabsz * 8) + (graph.tabsz * 4); |
| var rrefs = graph.rrefs(comp); |
| var rrefsh = graph.rrefsheight(rrefs, cassoc) + (graph.tabsz * 2); |
| return Math.max(lsvcsh, rrefsh); |
| }); |
| }; |
| |
| /** |
| * Return the height of a component and the components wired to its bottom side. |
| */ |
| graph.compclosureheight = function(comp, cassoc) { |
| return memo(comp, 'closureheight', function() { |
| return graph.compheight(comp, cassoc); |
| }); |
| }; |
| |
| /** |
| * Return the max width of the references on the right side of a component. |
| */ |
| graph.rrefswidth = function(refs, cassoc) { |
| if (isNull(refs)) |
| return 0; |
| return Math.max(graph.rrefwidth(car(refs), cassoc), graph.rrefswidth(cdr(refs), cassoc)); |
| }; |
| |
| /** |
| * Return the width of a component. |
| */ |
| graph.compwidth = function(comp, cassoc) { |
| return memo(comp, 'width', function() { |
| var ctw = graph.comptitlewidth(comp); |
| var rrefsw = (isNull(graph.rrefs(comp))? 0 : (graph.tabsz * 4)); |
| var twidth = (graph.titlex * 2) + ctw + rrefsw; |
| var width = Math.max(twidth, (graph.tabsz * 8) + (graph.tabsz * 4)); |
| return width; |
| }); |
| }; |
| |
| /** |
| * Return a path representing a reference positioned to the right of a component. |
| */ |
| graph.rrefpath = function(ref, cassoc, path, maxheight) { |
| var height = graph.rrefheight(ref, cassoc); |
| |
| // Record reference position in the path |
| var xpos = path.x; |
| var ypos = path.y; |
| path.refpos = cons(mklist(ref, graph.mkpath().pos(xpos, ypos + (graph.tabsz * 5))), path.refpos); |
| |
| // Compute the reference path |
| return path.rline(0,graph.tabsz * 2).rcurve(0,graph.tabsz,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,-graph.tabsz/2.0).rcurve(0,-graph.tabsz/2.0,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,graph.tabsz/2.0).rline(0,graph.tabsz * 3).rcurve(0,graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,-graph.tabsz/2.0).rcurve(0,-graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,graph.tabsz).line(path.x, Math.min(ypos + height, maxheight)); |
| }; |
| |
| /** |
| * Return a path representing a service positioned to the left of a component. |
| */ |
| graph.lsvcpath = function(svc, cassoc, path, minheight) { |
| var height = graph.tabsz * 8; |
| |
| // Record service position in the path |
| var xpos = path.x; |
| var ypos = path.y; |
| path.svcpos = cons(mklist(svc, graph.mkpath().pos(xpos, ypos - (graph.tabsz * 6))), path.svcpos); |
| |
| // Compute the service path |
| return path.rline(0, -(graph.tabsz * 2)).rcurve(0,-graph.tabsz,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,graph.tabsz/2.0).rcurve(0,graph.tabsz/2.0,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,-graph.tabsz/2.0).rline(0,-(graph.tabsz * 3)).rcurve(0,-graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,graph.tabsz/2.0).rcurve(0,graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,-graph.tabsz).line(path.x, Math.max(ypos - height, minheight)); |
| }; |
| |
| /** |
| * Return a path representing a component node. |
| */ |
| graph.comppath = function(comp, cassoc) { |
| |
| // Calculate the width and height of the component node |
| var width = graph.compwidth(comp, cassoc); |
| var height = graph.compheight(comp, cassoc); |
| |
| /** |
| * Apply a path rendering function to a list of services or references. |
| */ |
| function renderpath(x, f, cassoc, path, height) { |
| if (isNull(x)) |
| return path; |
| return renderpath(cdr(x), f, cassoc, f(car(x), cassoc, path, height), height); |
| } |
| |
| var path = graph.mkpath().move(graph.curvsz,0); |
| |
| // Store the positions of services and references in the path |
| path.refpos = mklist(); |
| path.svcpos = mklist(); |
| |
| // Render the references on the right side of the component |
| var rrefs = graph.rrefs(comp); |
| path = path.line(width - graph.curvsz,path.y).rcurve(graph.curvsz,0,0,graph.curvsz); |
| path = renderpath(rrefs, graph.rrefpath, cassoc, path, height - graph.curvsz); |
| |
| // Render the references on the bottom side of the component |
| var boffset = graph.curvsz; |
| path = path.line(path.x,height - graph.curvsz).rcurve(0,graph.curvsz,graph.curvsz * -1,0).line(boffset, path.y); |
| |
| // Render the services on the left side of the component |
| var lsvcs = graph.lsvcs(comp); |
| var loffset = graph.curvsz + (length(lsvcs) * (graph.tabsz * 8)); |
| path = path.line(graph.curvsz,path.y).rcurve(graph.curvsz * -1,0,0,graph.curvsz * -1).line(path.x, loffset); |
| path = renderpath(lsvcs, graph.lsvcpath, cassoc, path, graph.curvsz); |
| |
| // Close the component node path |
| path = path.line(0,graph.curvsz).rcurve(0,graph.curvsz * -1,graph.curvsz,0); |
| |
| return path.end(); |
| }; |
| |
| /** |
| * Return the position of a component. |
| */ |
| graph.comppos = function(comp, pos) { |
| var x = scdl.x(comp); |
| var y = scdl.y(comp); |
| return graph.mkpath().pos(x != null? Number(x) + graph.palcx : pos.x, y != null? Number(y) : pos.y); |
| }; |
| |
| /** |
| * Return a path representing a button node. |
| */ |
| graph.buttonpath = function(t) { |
| var path = graph.mkpath().move(graph.curvsz,0); |
| path = path.line(graph.buttoncx - graph.curvsz,path.y).rcurve(graph.curvsz,0,0,graph.curvsz); |
| path = path.line(path.x,graph.buttoncy - graph.curvsz).rcurve(0,graph.curvsz,-graph.curvsz,0).line(graph.curvsz, path.y); |
| path = path.line(graph.curvsz,path.y).rcurve(-graph.curvsz,0,0,-graph.curvsz).line(path.x, graph.curvsz); |
| path = path.line(0,graph.curvsz).rcurve(0,-graph.curvsz,graph.curvsz,0); |
| return path.end(); |
| }; |
| |
| /** |
| * Render a SCDL composite into a list of component nodes. |
| */ |
| graph.composite = function(compos, pos, aspalette) { |
| var name = scdl.name(scdl.composite(compos)); |
| var comps = scdl.components(compos); |
| var cassoc = scdl.nameToElementAssoc(comps); |
| var proms = scdl.promotions(compos); |
| |
| // Unmemoize any memoized info about components and their references. |
| map(function(c) { |
| unmemo(c); |
| map(function(r) { |
| unmemo(r); |
| }, scdl.references(c)); |
| }, comps); |
| |
| /** |
| * Render a component. |
| */ |
| function rendercomp(comp, cassoc, pos) { |
| |
| /** |
| * Render the references on the right side of a component. |
| */ |
| function renderrrefs(refs, cassoc, pos, gcomp) { |
| |
| /** |
| * Render a reference on the right side of a component. |
| */ |
| function renderrref(ref, cassoc, pos, gcomp) { |
| var target = assoc(scdl.target(ref), cassoc); |
| if (isNull(target)) |
| return null; |
| |
| // Render the component target of the reference |
| return rendercomp(cadr(target), cassoc, pos); |
| } |
| |
| /** |
| * Move the rendering cursor down below a reference. |
| */ |
| function rendermove(ref, cassoc, pos) { |
| return pos.clone().rmove(0, graph.rrefheight(ref, cassoc)); |
| } |
| |
| if (isNull(refs)) |
| return mklist(); |
| |
| // Return list of (ref, comp rendering) pairs |
| var grefcomp = renderrref(car(refs), cassoc, pos, gcomp); |
| return cons(mklist(car(refs), grefcomp), renderrrefs(cdr(refs), cassoc, rendermove(car(refs), cassoc, pos), gcomp)); |
| } |
| |
| // Compute the component shape |
| var gcomp = graph.compnode(comp, cassoc, pos); |
| |
| // Render the components wired to the component references |
| var rrefs = graph.rrefs(comp); |
| var rpos = graph.mkpath().rmove(graph.compwidth(comp, cassoc), 0); |
| var grrefs = renderrrefs(rrefs, cassoc, rpos, gcomp); |
| |
| // Store list of (ref, pos, component rendering) triplets in the component |
| function refposgcomp(refpos, grefs) { |
| if (isNull(refpos)) |
| return mklist(); |
| |
| // Append component rendering to component |
| var gref = cadr(car(grefs)); |
| if (gref != null) |
| gcomp.appendChild(gref); |
| return cons(mklist(car(car(refpos)), cadr(car(refpos)), gref), refposgcomp(cdr(refpos), cdr(grefs))); |
| } |
| |
| gcomp.refpos = refposgcomp(gcomp.refpos, grrefs); |
| |
| return gcomp; |
| } |
| |
| /** |
| * Render a list of promoted service components. |
| */ |
| function renderproms(svcs, cassoc, pos) { |
| |
| /** |
| * Return the component promoted by a service. |
| */ |
| function promcomp(svc, cassoc) { |
| var c = assoc(scdl.promote(svc), cassoc); |
| if (isNull(c)) |
| return mklist(); |
| return cadr(c); |
| } |
| |
| /** |
| * Move the rendering cursor down below a component. |
| */ |
| function rendermove(comp, cassoc, pos) { |
| return pos.clone().rmove(0, graph.compclosureheight(comp, cassoc) + Math.max((graph.tabsz * 2), 8)); |
| } |
| |
| if (isNull(svcs)) |
| return mklist(); |
| |
| // Render the first promoted component in the list |
| // then recurse to render the rest of the list |
| var comp = promcomp(car(svcs), cassoc); |
| if (isNull(comp)) |
| return renderproms(cdr(svcs), cassoc, rendermove(car(svcs), cassoc, pos)); |
| |
| var cpos = graph.comppos(comp, pos); |
| return cons(rendercomp(comp, cassoc, cpos), renderproms(cdr(svcs), cassoc, rendermove(comp, cassoc, cpos))); |
| } |
| |
| // Render the promoted service components |
| var rproms = renderproms(proms, cassoc, pos.clone().rmove(graph.tabsz * 4, graph.tabsz * 4)); |
| |
| if (aspalette) { |
| |
| // Prefix ids of palette component elements with 'palette:' and |
| // move them to the palette area |
| return map(function(r) { |
| r.id = 'palette:' + r.id; |
| var gpos = r.pos; |
| graph.move(r, graph.mkpath().pos(gpos.x - graph.palcx, gpos.y)); |
| return r; |
| }, rproms); |
| |
| } else { |
| |
| // Link app component elements to the containing composite |
| return map(function(r) { r.compos = compos; return r; }, rproms); |
| } |
| }; |
| |
| /** |
| * Return a component unique id. |
| */ |
| graph.ucid = function(prefix, compos1, compos2, clone) { |
| |
| // Build an assoc list keyed by component name |
| var comps = map(function(c) { return mklist(scdl.name(c), c); }, append(namedElementChildren("'component", compos1), namedElementChildren("'component", compos2))); |
| |
| if (!clone && isNull(assoc(prefix, comps))) |
| return prefix; |
| |
| /** |
| * Find a free component id. |
| */ |
| function ucid(p, id) { |
| if (isNull(assoc(p + id, comps))) |
| return p + id; |
| return ucid(p, id + 1); |
| } |
| |
| /** |
| * Remove trailing digits from a prefix. |
| */ |
| function untrail(p) { |
| if (p.length < 2 || p[p.length - 1] < '0' || p[p.length - 1] > '9') |
| return p; |
| return untrail(p.substring(0, p.length - 1)); |
| } |
| |
| return ucid(prefix == ''? 'comp' : (clone? untrail(prefix) : prefix), 1); |
| }; |
| |
| /** |
| * Clone a palette component node. |
| */ |
| graph.clonepalette = function(e, compos) { |
| |
| // Clone the SCDL component and give it a unique name |
| var wcomp = append(mklist(element, "'component", mklist(attribute, "'name", graph.ucid(scdl.name(e.comp), compos, compos, true))), |
| filter(function(c) { return !(isAttribute(c) && attributeName(c) == "'name")}, elementChildren(e.comp))); |
| var x = '<composite>' + writeXML(mklist(wcomp), false) + '</composite>'; |
| var rcompos = scdl.composite(readXML(mklist(x))); |
| var comp = car(scdl.components(mklist(rcompos))); |
| |
| // Update component position |
| setElement(comp, graph.movecomp(comp, graph.abspos(e).rmove(graph.palcx, 0))); |
| |
| return comp; |
| }; |
| |
| /** |
| * Move a SCDL component to the given position. |
| */ |
| graph.movecomp = function(comp, pos) { |
| if (isNull(pos)) |
| return append(mklist(element, "'component"), |
| filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'x" || attributeName(e) == "'y")); }, elementChildren(comp))); |
| return append(mklist(element, "'component", mklist(attribute, "'x", '' + (pos.x - graph.palcx)), mklist(attribute, "'y", '' + pos.y)), |
| filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'x" || attributeName(e) == "'y")); }, elementChildren(comp))); |
| }; |
| |
| /** |
| * Align a pos along a 10pixel grid. |
| */ |
| graph.gridsnap = function(x) { |
| return Math.round(x / 10) * 10; |
| } |
| |
| /** |
| * Clone a component node and all the components it references. |
| */ |
| graph.clonecomp = function(e, compos) { |
| |
| // Write the component and the components it references to XML |
| function collectcomp(e) { |
| function collectrefs(refpos) { |
| if (isNull(refpos)) |
| return mklist(); |
| var r = car(refpos); |
| var n = caddr(r); |
| if (isNull(n)) |
| return collectrefs(cdr(refpos)); |
| return append(collectcomp(n), collectrefs(cdr(refpos))); |
| } |
| |
| return cons(e.comp, collectrefs(e.refpos)); |
| } |
| |
| var allcomps = collectcomp(e); |
| var ls = map(function(e) { return writeXML(mklist(e), false); }, allcomps); |
| var x = '<composite>' + writeStrings(ls) + '</composite>'; |
| |
| // Read them back from XML to clone them |
| var rcompos = scdl.composite(readXML(mklist(x))); |
| var comps = scdl.components(mklist(rcompos)); |
| |
| // Give them new unique names |
| map(function(e) { |
| |
| // Rename each component |
| var oname = scdl.name(e); |
| var name = graph.ucid(oname, compos, rcompos, true); |
| setElement(e, append(mklist(element, "'component", mklist(attribute, "'name", name)), |
| filter(function(c) { return !(isAttribute(c) && attributeName(c) == "'name")}, elementChildren(e)))); |
| |
| // Refactor references to the component |
| map(function(c) { return graph.refactorrefs(scdl.references(c), oname, name); }, comps); |
| }, comps); |
| |
| // Update the top component position |
| var comp = car(comps); |
| setElement(comp, graph.movecomp(comp, graph.abspos(e).rmove(10, 10))); |
| |
| return comps; |
| }; |
| |
| /** |
| * Sort elements of a composite. |
| */ |
| graph.sortcompos = function(compos) { |
| return append(mklist(element, "'composite"), elementChildren(compos).sort(function(a, b) { |
| |
| // Sort attributes, place them at the top |
| var aa = isAttribute(a); |
| var ba = isAttribute(b); |
| if (aa && !ba) return -1; |
| if (!aa && ba) return 1; |
| if (aa && ba) { |
| var aan = attributeName(a); |
| var ban = attributeName(b); |
| if (aan < ban) return -1; |
| if (aan > ban) return 1; |
| return 0; |
| } |
| |
| // Sort elements, place services before components |
| var aen = elementName(a); |
| var ben = elementName(b); |
| if (aen == "'service" && ben == "'component") return -1; |
| if (aen == "'component" && ben == "'service") return 1; |
| var an = scdl.name(a); |
| var bn = scdl.name(b); |
| if (an < bn) return -1; |
| if (an > bn) return 1; |
| return 0; |
| })); |
| } |
| |
| /** |
| * Add a list of components to a SCDL composite. The first |
| * component in the list is a promoted component. |
| */ |
| graph.addcomps = function(comps, compos) { |
| var comp = car(comps); |
| var name = scdl.name(comp); |
| var prom = mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name)); |
| return append(mklist(element, "'composite"), append(elementChildren(compos), cons(prom, comps))); |
| }; |
| |
| /** |
| * Remove a component from a SCDL composite. |
| */ |
| graph.removecomp = function(comp, compos) { |
| var name = scdl.name(comp); |
| return append(mklist(element, "'composite"), |
| filter(function(c) { return !(isElement(c) && scdl.name(c) == name); }, elementChildren(compos))); |
| }; |
| |
| /** |
| * Garbage collect components not referenced or promoted. |
| */ |
| graph.gcollect = function(compos) { |
| |
| // List the promoted components |
| var proms = map(function(s) { return mklist(scdl.promote(s), true); }, scdl.promotions(mklist(compos))); |
| |
| // List the referenced components |
| var refs = reduce(function(a, comp) { |
| return append(a, |
| map(function(ref) { return mklist(scdl.target(ref), true); }, filter(function(ref) { return scdl.target(ref) != null; }, scdl.references(comp)))); |
| }, mklist(), scdl.components(mklist(compos))); |
| |
| // Filter out the unused components |
| var used = append(proms, refs); |
| return append(mklist(element, "'composite"), |
| filter(function(c) { return !(isElement(c) && elementName(c) == "'component" && isNull(assoc(scdl.name(c), used))); }, elementChildren(compos))); |
| } |
| |
| /** |
| * Clone and cleanup clonable references. |
| */ |
| graph.clonerefs = function(compos) { |
| return append(mklist(element, "'composite"), |
| map(function(c) { |
| if (elementName(c) != "'component") |
| return c; |
| |
| // If the references are clonable |
| var refs = scdl.references(c); |
| if (isNull(refs)) |
| return c; |
| if (scdl.clonable(car(refs)) != 'true') |
| return c; |
| |
| // Filter out the unwired references and add a fresh unwired |
| // reference at the end of the list |
| var cc = append( |
| filter(function(e) { return !(elementName(e) == "'reference" && scdl.target(e) == null); }, elementChildren(c)), |
| mklist(mklist(element, "'reference", mklist(attribute, "'name", scdl.name(car(refs))), mklist(attribute, "'clonable", "true")))); |
| return append(mklist(element, "'component"), cc); |
| |
| }, elementChildren(compos))); |
| } |
| |
| /** |
| * Refactor references to a component. |
| */ |
| graph.refactorrefs = function(refs, oname, nname) { |
| if (isNull(refs)) |
| return true; |
| var ref = car(refs); |
| if (scdl.target(ref) != oname) |
| return graph.refactorrefs(cdr(refs), oname, nname); |
| |
| // Change the reference's target attribute |
| setElement(ref, append(mklist(element, "'reference"), |
| append(filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'target"); }, elementChildren(ref)), |
| mklist(mklist(attribute, "'target", nname))))); |
| |
| return graph.refactorrefs(cdr(refs), oname, nname); |
| }; |
| |
| /** |
| * Rename a component. |
| */ |
| graph.renamecomp = function(comp, compos, name) { |
| |
| // Refactor all the references to the renamed component |
| var oname = scdl.name(comp); |
| map(function(c) { return graph.refactorrefs(scdl.references(c), oname, name); }, namedElementChildren("'component", compos)); |
| |
| // Rename the SCDL promoted service and component |
| var proms = filter(function(s) { return scdl.name(s) == oname }, scdl.services(compos)); |
| if (!isNull(proms)) |
| setElement(car(proms), mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name))); |
| setElement(comp, append(mklist(element, "'component"), |
| cons(mklist(attribute, "'name", name), |
| filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'name"); }, elementChildren(comp))))); |
| |
| return append(mklist(element, "'composite"), elementChildren(compos)); |
| }; |
| |
| /** |
| * Cut the wire to a component node and make that node a |
| * top level component node. |
| */ |
| graph.cutwire = function(node, compos, g) { |
| |
| /** |
| * Find the reference wired to a node and cut its wire. |
| */ |
| function cutref(refs, node) { |
| if (isNull(refs)) |
| return true; |
| var ref = car(refs); |
| if (caddr(ref) == node) { |
| setlist(ref, mklist(car(ref), cadr(ref), null)); |
| setElement(car(ref), |
| append(mklist(element, "'reference"), |
| filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'target"); }, elementChildren(car(ref))))); |
| } |
| return cutref(cdr(refs), node); |
| } |
| |
| // Cut any reference wire, if found |
| cutref(node.parentNode.refpos, node); |
| |
| // Make the component node a top level node. |
| node.compos = g.compos; |
| |
| // Update the SCDL composite, add a promote element for |
| // that component |
| var comp = node.comp; |
| var name = scdl.name(comp); |
| var prom = mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name)); |
| return append(mklist(element, "'composite"), |
| append(mklist(prom), filter(function(c) { return !(isElement(c) && elementName(c) == "'service" && scdl.name(c) == name); }, elementChildren(compos)))); |
| } |
| |
| /** |
| * Wire a component to the closest neighbor reference. |
| */ |
| graph.wire = function(n, compos, g) { |
| |
| // Compute position of the component's service node |
| var spos = cadr(car(n.svcpos)); |
| var aspos = graph.abspos(n).rmove(spos.x, spos.y); |
| |
| /** |
| * Find closest unwired reference node among all the references |
| * of all the components. |
| */ |
| function closecomprefs(nodes, spos, cref) { |
| |
| /** |
| * Find the closest unwired reference node among all the |
| * references of a node. |
| */ |
| function closerefs(npos, refs, spos, cref) { |
| if (isNull(refs)) |
| return cref; |
| var fdist = cadddr(cref); |
| var ref = car(refs); |
| |
| // Skip wired reference |
| if (!isNull(filter(function(n) { return isAttribute(n) && attributeName(n) == "'target"; }, car(ref)))) |
| return closerefs(npos, cdr(refs), spos, cref); |
| |
| // Compute distance between service node and reference node |
| var rpos = cadr(ref).clone().rmove(npos.x, npos.y); |
| var dx = Math.pow(rpos.x - spos.x, 2); |
| var dy = Math.pow(rpos.y - spos.y, 2); |
| |
| // Check for proximity threshold |
| var rdist = (dx < (graph.proxcx * graph.proxcx) && dy < (graph.proxcy * graph.proxcy))? Math.sqrt(dx + dy) : 25000000; |
| |
| // Go through all the references in the component |
| return closerefs(npos, cdr(refs), spos, fdist < rdist? cref : mklist(car(ref), cadr(ref), caddr(ref), rdist)); |
| } |
| |
| if (isNull(nodes)) |
| return cref; |
| |
| // Skip non-component nodes |
| var node = car(nodes); |
| if (isNull(node.comp)) |
| return closecomprefs(cdr(nodes), spos, cref); |
| |
| // Compute the component absolute position |
| var npos = graph.abspos(node); |
| |
| // Go through all the components and their references |
| return closecomprefs(append(nodeList(node.childNodes), cdr(nodes)), spos, closerefs(npos, node.refpos, spos, cref)); |
| } |
| |
| // Find closest reference node |
| var cref = closecomprefs(nodeList(g.childNodes), aspos, mklist(null, graph.mkpath(), null, 25000000)); |
| if (car(cref) == null) |
| return compos; |
| if (cadddr(cref) == 25000000) |
| return compos; |
| |
| // Wire component to that reference, un-promote it, and |
| // update the SCDL reference and composite |
| setElement(n.comp, graph.movecomp(graph.dragging.comp, null)); |
| n.compos = null; |
| setElement(car(cref), append(mklist(element, "'reference", mklist(attribute, "'target", scdl.name(n.comp))), elementChildren(car(cref)))); |
| var name = scdl.name(n.comp); |
| return append(mklist(element, "'composite"), |
| filter(function(c) { return !(isElement(c) && elementName(c) == "'service" && scdl.name(c) == name); }, elementChildren(compos))); |
| } |
| |
| /** |
| * Display a list of graphical nodes. |
| */ |
| graph.display = function(nodes, g) { |
| |
| // Append the nodes to the graphical canvas |
| appendNodes(nodes, g); |
| return nodes; |
| }; |
| |
| /** |
| * Hide a graph. |
| */ |
| graph.hide = function(g) { |
| |
| // Remove nodes from the graph |
| map(function(n) { if (!isNull(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes)); |
| return g; |
| }; |
| |
| /** |
| * Refresh a graph. |
| */ |
| graph.refresh = function(g) { |
| //debug('refresh'); |
| |
| // Remove existing nodes from the graph |
| map(function(n) { if (!isNull(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes)); |
| |
| // Redisplay the composite associated with the graph |
| var nodes = graph.composite(g.compos, graph.mkpath().pos(graph.palcx,0), false); |
| appendNodes(nodes, g); |
| return nodes; |
| }; |
| |
| /** |
| * Display and enable editing of a composite and the graphical |
| * nodes that represent it. |
| */ |
| graph.edit = function(appname, compos, nodes, g) { |
| |
| // Store the composite elements, and sort them to allow for change detection later |
| g.compos = compos; |
| var scompos = scdl.composite(g.compos); |
| setElement(scompos, graph.sortcompos(scompos)); |
| |
| // Remove existing nodes from the graph |
| map(function(n) { if (!isNull(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes)); |
| |
| // Display the composite nodes |
| appendNodes(nodes, g); |
| return nodes; |
| }; |
| |
| /** |
| * Track the composition graph, whether it's visible or not and the selected component. |
| */ |
| var gvisible = true; |
| var gcomp = null; |
| var cdiv = $('contentdiv'); |
| var pdiv = $('playdiv'); |
| var graphdiv = $('graphdiv'); |
| |
| /** |
| * Track the palettes. |
| */ |
| var gpalettes = new Array(); |
| var spalette = 'control'; |
| var bpalette = null; |
| |
| /** |
| * Get and display an application composite. |
| */ |
| function getapp(name, g) { |
| if (isNull(name)) |
| return false; |
| workingstatus(true); |
| showstatus('Loading'); |
| |
| return composites.get(name, function(doc) { |
| |
| // Stop now if we didn't get a composite |
| if (doc == null) { |
| errorstatus('Couldn\'t get the app info'); |
| workingstatus(false); |
| return false; |
| } |
| |
| // Get the composite from the ATOM entry |
| var composentry = car(atom.readATOMEntry(mklist(doc))); |
| var content = namedElementChild("'content", composentry); |
| composite = isNull(content)? mklist() : elementChildren(content); |
| if (isNull(composite)) { |
| |
| // Create a default empty composite if necessary |
| var x = '<composite xmlns="http://docs.oasis-open.org/ns/opencsa/sca/200912" ' + |
| 'targetNamespace="http://app" name="app"></composite>'; |
| composite = readXML(mklist(x)); |
| } |
| |
| // Display the composite |
| graph.edit(name, composite, graph.composite(composite, graph.mkpath().move(graph.palcx,0), false, g), g); |
| |
| // Track the saved composite XML |
| savedcomposxml = car(writeXML(composite, false)); |
| |
| // Enable author to edit the composite |
| author = elementValue(namedElementChild("'author", composentry)); |
| editable = author == username; |
| cadd.disabled = !editable; |
| if (editable) |
| onlinestatus(); |
| else |
| showstatus('Read only'); |
| |
| workingstatus(false); |
| return true; |
| }); |
| } |
| |
| /** |
| * Display a palette. Get it from the server if needed. |
| */ |
| function displaypalette(name, g, palette, gpalettes) { |
| if (isNull(name)) |
| return; |
| if (isNull(gpalettes[name])) { |
| |
| // Get the palette from the server |
| palettes.get(name, function(doc) { |
| var entry = car(atom.readATOMEntry(mklist(doc))); |
| var content = namedElementChild("'content", entry); |
| var compos = isNull(content)? mklist() : elementChildren(content); |
| gpalettes[name] = graph.composite(compos, graph.mkpath().move(2580,0), true, g); |
| graph.display(gpalettes[name], g); |
| }); |
| return true; |
| } |
| graph.display(gpalettes[name], g); |
| return true; |
| } |
| |
| /** |
| * Install a palette, including a button to select the palette, and |
| * the palette content. |
| */ |
| function installpalette(name, pos, g, bg, palette, gpalettes) { |
| var b = graph.mkbutton(name, pos); |
| graph.display(mklist(b), g); |
| b.onclick = function(e) { |
| |
| // Swap the selected palette |
| graph.paletteselect(bpalette, false); |
| displaypalette(spalette, bg, palette, gpalettes); |
| bpalette = b; |
| graph.paletteselect(b, true); |
| spalette = name; |
| return displaypalette(spalette, g, palette, gpalettes); |
| }; |
| |
| if (name != spalette) { |
| |
| // Will get the palette from the server later if needed |
| gpalettes[name] = null; |
| return true; |
| } |
| |
| // Display the selected palette |
| graph.paletteselect(b, true); |
| displaypalette(name, g, palette, gpalettes); |
| |
| return b; |
| } |
| |
| /** |
| * Save the current composite. |
| */ |
| function save(savexml) { |
| workingstatus(true); |
| showstatus('Saving'); |
| |
| savedcomposxml = savexml; |
| var entry = '<?xml version="1.0" encoding="UTF-8"?>\n' + '<entry xmlns="http://www.w3.org/2005/Atom">' + |
| '<title type="text">' + appname + '</title><id>' + appname + '</id><author><email>' + author + '</email></author>' + |
| '<content type="application/xml">' + savedcomposxml + '</content></entry>'; |
| composites.put(appname, entry, function(e) { |
| if (e) { |
| showstatus('Local copy'); |
| workingstatus(false); |
| return false; |
| } |
| showstatus('Saved'); |
| workingstatus(false); |
| return false; |
| }); |
| return true; |
| } |
| |
| /** |
| * Handle a composite change event. |
| */ |
| function oncomposchange(prop) { |
| if (!editable) |
| return false; |
| |
| var newxml = car(writeXML(composite, false)); |
| if (savedcomposxml == newxml) |
| return false; |
| showstatus('Modified'); |
| |
| // Save property changes right away |
| if (prop) |
| return save(newxml); |
| |
| // Autosave other changes after 1 second |
| showstatus('Modified'); |
| ui.delay(function autosave() { |
| if (savedcomposxml == newxml) { |
| showstatus('Saved'); |
| return false; |
| } |
| return save(newxml); |
| }, 1000); |
| return true; |
| } |
| |
| /** |
| * Return the link to a component. |
| */ |
| function complink(appname, cname) { |
| if (cname == '' || isNull(cname)) |
| return ''; |
| var protocol = location.protocol; |
| var host = location.hostname; |
| var port = ':' + location.port; |
| if (port == ':80' || port == ':443' || port == ':') |
| port = ''; |
| var link = protocol + '//' + host + port + '/' + appname + '/c/' + cname; |
| return link; |
| } |
| |
| /** |
| * Handle a component select event. |
| */ |
| function oncompselect(gsel) { |
| if (gsel == gcomp) |
| return true; |
| gcomp = gsel; |
| |
| cdelete.disabled = isNull(gsel) || !editable; |
| ccopy.disabled = isNull(gsel) || !editable; |
| cplay.disabled = isNull(gsel); |
| return true; |
| } |
| |
| /** |
| * Show the result data of a component. |
| */ |
| function showdata(gcomp) { |
| if (!gvisible) |
| return true; |
| if (isNull(gcomp)) |
| return true; |
| cvalue.value = complink(appname, gcomp.id); |
| cplay.value = '<'; |
| gvisible = false; |
| pdiv.innerHTML = ''; |
| pdiv.style.display = 'block'; |
| |
| // Get the component result data |
| var comp = sca.component(gcomp.id, appname); |
| comp.get('', function(doc) { |
| function displaydata(t, w) { |
| pdiv.style.width = w; |
| pdiv.innerHTML = t; |
| return true; |
| } |
| |
| // Stop now if we didn't get the doc |
| if (doc == null) |
| return displaydata('No content', '2500px'); |
| |
| // Format data table |
| if (json.isJSON(mklist(doc))) |
| return displaydata(ui.datatable(json.readJSON(mklist(doc))), '2500px'); |
| |
| if (atom.isATOMEntry(mklist(doc))) |
| return displaydata(ui.datatable(atom.readATOMEntry(mklist(doc))), '2500px'); |
| |
| if (atom.isATOMFeed(mklist(doc))) |
| return display(ui.datatable(atom.readATOMFeed(mklist(doc))), '2500px'); |
| |
| // Insert the doc as is in an iframe |
| var t = '<table class="datatable" style="width: 100%;">' + |
| '<tr><td class="datatdltop">' + 'value' + '</td>' + '<td class="datatdr">' + |
| '<iframe style="width: 100%; height: 5000px;" scrolling="no" frameborder="0" src="' + clink + '"/>' + |
| '</td></tr></table>' |
| return displaydata(t, '100%'); |
| }); |
| |
| ui.delay(function hidegraphdiv() { |
| graphdiv.style.display = 'none' |
| }); |
| return true; |
| } |
| |
| /** |
| * Show the composition graph. |
| */ |
| function showgraph(gcomp) { |
| if (gvisible) |
| return true; |
| cplay.value = '>'; |
| graphdiv.style.display = 'block' |
| gvisible = true; |
| graph.compselect(gcomp, true, atitle, cvalue, ccopy, cdelete); |
| ui.delay(function hideplaydiv() { |
| pdiv.style.display = 'none'; |
| pdiv.innerHTML = ''; |
| }); |
| return true; |
| } |
| |
| /** |
| * Handle play component button event. |
| */ |
| cplay.onclick = function() { |
| if (gcomp == null) |
| return false; |
| if (!gvisible) |
| return showgraph(gcomp); |
| return showdata(gcomp); |
| } |
| |
| /** |
| * Create editor graph area. |
| */ |
| graph.mkedit(graphdiv, graph.mkpath().move(-2500,0), atitle, cvalue, cadd, ccopy, cdelete, oncomposchange, oncompselect); |
| |
| /** |
| * Install the palettes. |
| */ |
| var bg = graph.mkgroup(graph.mkpath()); |
| var pos = graph.mkpath().move(0, 0); |
| bpalette = installpalette('control', pos.rmove(5,2), graphdiv, bg, spalette, gpalettes); |
| installpalette('values', pos.rmove(0,28), graphdiv, bg, spalette, gpalettes); |
| installpalette('lists', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes); |
| installpalette('transform', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes); |
| installpalette('text', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes); |
| installpalette('http', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes); |
| installpalette('animation', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes); |
| installpalette('talk', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes); |
| installpalette('social', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes); |
| installpalette('search', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes); |
| installpalette('database', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes); |
| installpalette('logic', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes); |
| installpalette('math', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes); |
| installpalette('python', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes); |
| |
| /** |
| * Get and display the current app. |
| */ |
| getapp(appname, graphdiv); |
| |
| })(); |
| </script> |
| |
| </div> |