Index: lams_central/web/author2.jsp =================================================================== diff -u -rebe1058e6422f0e8ce4d72f37a130316da559d62 -ra3d8ca871cecdad8f45756fdcae6d697bd0e8499 --- lams_central/web/author2.jsp (.../author2.jsp) (revision ebe1058e6422f0e8ce4d72f37a130316da559d62) +++ lams_central/web/author2.jsp (.../author2.jsp) (revision a3d8ca871cecdad8f45756fdcae6d697bd0e8499) @@ -37,6 +37,9 @@
+
+ New +
Open
@@ -55,13 +58,12 @@
Group
+
+ Arrange +
- - - - - +
@@ -74,7 +76,9 @@
+
+
Index: lams_central/web/css/authoring.css =================================================================== diff -u -rebe1058e6422f0e8ce4d72f37a130316da559d62 -ra3d8ca871cecdad8f45756fdcae6d697bd0e8499 --- lams_central/web/css/authoring.css (.../authoring.css) (revision ebe1058e6422f0e8ce4d72f37a130316da559d62) +++ lams_central/web/css/authoring.css (.../authoring.css) (revision a3d8ca871cecdad8f45756fdcae6d697bd0e8499) @@ -1,11 +1,24 @@ -div.dialogContainer, .ldChoiceDependentCanvasElement { - display: none; +.ygtv-highlight1, .ygtv-highlight1 .ygtvlabel { + background-color: #dfeffc; } +a.ygtvspacer { + border-bottom: none; +} + .ui-dialog { font-size: 12px; } +div.dialogContainer, .ldChoiceDependentCanvasElement, +.dialog-no-title .ui-dialog-titlebar { + display: none; +} + +#infoDialog { + font-weight: bold; +} + div.dialogTitle { padding: 5px 0px 5px 0px; font-weight: bold; @@ -19,26 +32,18 @@ border-top: thin dotted #2E6E9E; } -a.ygtvspacer { - border-bottom: none; -} - -td#learningDesignTreeCell { +div#openLearningDesignDialog td#learningDesignTreeCell { padding: 2px 2px 0px 5px; vertical-align: top; width: 200px; border-right: thin dotted #2E6E9E; } -div#learningDesignTree { +div#openLearningDesignDialog div#learningDesignTree { overflow: auto; } -.ygtv-highlight1, .ygtv-highlight1 .ygtvlabel { - background-color: #dfeffc; -} - -#recentlyUsedCell { +div#openLearningDesignDialog #recentlyUsedCell { height: 200px; vertical-align: top; border-top: thin dotted #2E6E9E; @@ -49,18 +54,18 @@ text-align: center; } -td#canvasCell { +div#openLearningDesignDialog td#canvasCell { text-align: center; padding: 10px 0px 0px 10px; vertical-align: middle; } -div#canvasDiv { +div#openLearningDesignDialog div#canvasDiv { overflow: auto; height: 665px; } -img#ldScreenshotLoading { +div#openLearningDesignDialog img#ldScreenshotLoading { padding-top: 200px; } @@ -96,23 +101,20 @@ width: 100%; } -td#layoutCell { - width: 152px; -} - #groupingButton span { background: url('../images/icons/group.png') no-repeat 3px 3px; padding-left: 25px; } td#templateContainerCell { + width: 152px; border: black thin solid; vertical-align: top; background-color: rgb(219, 230, 252); } div#templateContainer { - height: 800px; + height: 745px; overflow: auto; } @@ -133,6 +135,13 @@ font-size: 10pt; } +#canvas { + overflow: auto; + width: 100%; + height: 745px; + vertical-align: top; +} + div.propertiesDialog { font-size: 12px; } Index: lams_central/web/includes/javascript/authoring/authoringActivity.js =================================================================== diff -u -rebe1058e6422f0e8ce4d72f37a130316da559d62 -ra3d8ca871cecdad8f45756fdcae6d697bd0e8499 --- lams_central/web/includes/javascript/authoring/authoringActivity.js (.../authoringActivity.js) (revision ebe1058e6422f0e8ce4d72f37a130316da559d62) +++ lams_central/web/includes/javascript/authoring/authoringActivity.js (.../authoringActivity.js) (revision a3d8ca871cecdad8f45756fdcae6d697bd0e8499) @@ -10,25 +10,32 @@ ToolActivity: function(id, toolID, x, y, label) { this.id = id; this.type = 'tool'; + this.transitions = { + 'from' : [], + 'to' : [] + }; this.toolID = toolID; + this.draw = function(x, y) { + if (this.items) { + this.items.remove(); + } + + // create activity SVG elements + paper.setStart(); + var shape = paper.path(Raphael.format('M {0} {1}' + layout.defs.activity, x, y)) + .attr({ + 'fill' : layout.colors.activity + }); + paper.image(layout.toolIcons[toolID], x + 47, y + 2, 30, 30); + paper.text(x + 62, y + 40, label); + + this.items = paper.setFinish(); + this.items.shape = shape; + + ActivityLib.initActivity(this); + }; - // create activity SVG elements - paper.setStart(); - var shape = paper.rect(x, y, layout.conf.activityWidth, layout.conf.activityHeight) - .attr({ - 'fill' : layout.colors.activity - }); - - paper.image(layout.toolIcons[toolID], shape.attr('x') - + shape.attr('width') / 2 - 15, shape.attr('y') + 2, 30, 30); - - paper.text(shape.attr('x') + shape.attr('width') / 2, shape - .attr('y') + 40, label); - - this.items = paper.setFinish(); - this.items.shape = shape; - - ActivityLib.initActivity(this); + this.draw(x, y); }, /** @@ -37,29 +44,35 @@ GroupingActivity : function(id, x, y) { this.id = id; this.type = 'group'; + this.transitions = { + 'from' : [], + 'to' : [] + }; + this.draw = function(x, y) { + if (this.items) { + this.items.remove(); + } + + x-=47; + y-=2; + + // create activity SVG elements + paper.setStart(); + var shape = paper.path(Raphael.format('M {0} {1}' + layout.defs.activity, x, y)) + .attr({ + 'fill' : layout.colors.activity + }); + + paper.image('../images/grouping.gif', x + 47, y + 2, 30, 30); + paper.text(x + 62, y + 40, 'Grouping'); + + this.items = paper.setFinish(); + this.items.shape = shape; + + ActivityLib.initActivity(this); + }; - // create activity SVG elements - paper.setStart(); - var shape = paper.rect( - x - layout.conf.activityWidth/2, - y - layout.conf.activityHeight/2, - layout.conf.activityWidth, layout.conf.activityHeight) - .attr({ - 'fill' : layout.colors.activity - }); - - paper.image('../images/grouping.gif', - shape.attr('x') + shape.attr('width') / 2 - 15, - shape.attr('y') + 2, - 30, 30); - - paper.text(shape.attr('x') + shape.attr('width') / 2, shape - .attr('y') + 40, 'Grouping'); - - this.items = paper.setFinish(); - this.items.shape = shape; - - ActivityLib.initActivity(this); + this.draw(x, y); }, /** @@ -68,134 +81,140 @@ GateActivity : function(id, x, y) { this.id = id; this.type = 'gate'; - - // create activity SVG elements - paper.setStart(); - var shape = paper.path('M ' + x + ' ' + y + layout.defs.gate) - .attr({ - 'fill' : layout.colors.gate - }); + this.transitions = { + 'from' : [], + 'to' : [] + }; + this.draw = function(x, y) { + if (this.items) { + this.items.remove(); + } + + // create activity SVG elements + paper.setStart(); + var shape = paper.path(Raphael.format('M {0} {1}' + layout.defs.gate, x, y)) + .attr({ + 'fill' : layout.colors.gate + }); + + paper.text(x + 7, y + 14, 'STOP') + .attr({ + 'font-size' : 9, + 'font' : 'sans-serif', + 'stroke' : layout.colors.gateText + }); + + this.items = paper.setFinish(); + this.items.shape = shape; + + ActivityLib.initActivity(this); + }; - paper.text(x + 7, y + 14, 'STOP') - .attr({ - 'font-size' : 9, - 'font' : 'sans-serif', - 'stroke' : layout.colors.gateText - }); - - this.items = paper.setFinish(); - this.items.shape = shape; - - ActivityLib.initActivity(this); + this.draw(x, y); }, + /** + * Either branching or converge point. + */ BranchingEdgeActivity : function(id, x, y, branchingActivity) { this.type = 'branchingEdge'; + this.transitions = { + 'from' : [], + 'to' : [] + }; + if (branchingActivity) { + // branchingActivity already exists, so this is the converge point this.isStart = false; branchingActivity.end = this; } else { + // this is the branching point this.isStart = true; branchingActivity = new ActivityLib.BranchingActivity(id, this); } this.branchingActivity = branchingActivity; + this.draw = function(x, y) { + if (this.items) { + this.items.remove(); + } - // create activity SVG elements - paper.setStart(); - var shape = paper.path('M ' + x + ' ' + y + - (this.isStart ? layout.defs.branchingEdgeStart : layout.defs.branchingEdgeEnd)) - .attr({ - 'fill' : this.isStart ? layout.colors.branchingEdgeStart - : layout.colors.branchingEdgeEnd - }); - - paper.text(x, y + 14, this.isStart ? 'Branching point' - : 'Converge point') - .attr({ - 'font-size' : 9, - 'font' : 'sans-serif' - }); + // create activity SVG elements + paper.setStart(); + var shape = paper.path('M ' + x + ' ' + y + + (this.isStart ? layout.defs.branchingEdgeStart : layout.defs.branchingEdgeEnd)) + .attr({ + 'fill' : this.isStart ? layout.colors.branchingEdgeStart + : layout.colors.branchingEdgeEnd + }); + + paper.text(x, y + 14, this.isStart ? 'Branching point' + : 'Converge point') + .attr({ + 'font-size' : 9, + 'font' : 'sans-serif' + }); + + this.items = paper.setFinish(); + this.items.shape = shape; + + ActivityLib.initActivity(this); + }; - this.items = paper.setFinish(); - this.items.shape = shape; - - ActivityLib.initActivity(this); + this.draw(x, y); }, + /** + * Represents a set of branches. It is not displayed on canvas. + */ BranchingActivity : function(id, branchingEdgeStart) { this.id = id; this.start = branchingEdgeStart; this.branches = []; }, + /** + * Represents a subsequence of activities. It is not displayed on canvas. + */ BranchActivity : function(id, branchingActivity, transitionFrom) { this.id = id; - this.transitionsFrom = transitionFrom; + this.transitionFrom = transitionFrom; this.branchingActivity = branchingActivity; - branchingActivity.branches.push(this); }, /** * Make a new activity fully functional on canvas. */ - initActivity : function(activity) { - activity.items.mousedown(function(event, x, y){ - if (event.ctrlKey) { - // when CTRL is held down, start drawing a transition - HandlerLib.drawTransitionStartHandler(activity, event, x, y); - } else { - // start dragging the activity - var mouseupHandler = function(){ - HandlerLib.dragActivityEndHandler(activity); - }; - - HandlerLib.dragItemsStartHandler(activity.items, this, mouseupHandler, event, x, y); + initActivity : function(activity) { + // set all the handlers + activity.items + .data('activity', activity) + .mousedown(HandlerLib.activityMousedownHandler) + .click(HandlerLib.activityClickHandler) + .dblclick(HandlerLib.activityDblclickHandler) + .attr({ + 'cursor' : 'pointer' + }); + + if (activity.type == 'branchingEdge' && activity.branchingActivity.end) { + // highligh branching edges on hover + activity.branchingActivity.start.items.hover(HandlerLib.branchingEdgeMouseoverHandler, + HandlerLib.branchingEdgeMouseoutHandler); + activity.branchingActivity.end.items.hover(HandlerLib.branchingEdgeMouseoverHandler, + HandlerLib.branchingEdgeMouseoutHandler); } - }) - .click(function(event){ - // inform that user wants to select, not drag the activity - activity.items.clicked = true; - HandlerLib.selectActivityHandler(event, activity); - }) - .dblclick(function(){ - // inform that user wants to open, not drag the activity - activity.items.clicked = true; - HandlerLib.openActivityAuthoringHandler(activity); - }) - .attr({ - 'cursor' : 'pointer' - }); - - if (activity.type == 'branchingEdge' && activity.branchingActivity.end) { - var startItems = activity.branchingActivity.start.items; - var endItems = activity.branchingActivity.end.items; - var mouseover = function(){ - if (!startItems.isDragged && !endItems.isDragged) { - startItems.shape.attr('fill', layout.colors.branchingEdgeMatch); - endItems.shape.attr('fill', layout.colors.branchingEdgeMatch); - } - }; - var mouseout = function(){ - if (!startItems.isDragged && !endItems.isDragged) { - startItems.shape.attr('fill', layout.colors.branchingEdgeStart); - endItems.shape.attr('fill', layout.colors.branchingEdgeEnd); - } - }; - startItems.shape.hover(mouseover, mouseout); - endItems.shape.hover(mouseover, mouseout); - } - }, + }, /** * Deletes the given activity. */ removeActivity : function(activity, forceRemove) { if (!forceRemove && activity.type == 'branchingEdge'){ + // user removes one of the branching edges, so remove the whole activity if (confirm('Are you sure you want to remove the whole branching activity?')){ var otherEdge = activity.isStart ? activity.branchingActivity.end : activity.branchingActivity.start; @@ -206,20 +225,18 @@ } // remove the transitions - if (activity.fromTransition) { - var toActivity = activity.fromTransition.toActivity; + $.each(activity.transitions.from.slice(), function() { // if grouping activity is gone, remove the grouping effect - if (activity.type == 'group' && toActivity.items.groupingEffect) { - toActivity.items.groupingEffect.remove(); - toActivity.items.groupingEffect = null; + if (activity.type == 'group' && this.toActivity.items.groupingEffect) { + this.toActivity.items.groupingEffect.remove(); + this.toActivity.items.groupingEffect = null; } - toActivity.toTransition = null; - activity.fromTransition.remove(); - } - if (activity.toTransition){ - activity.toTransition.fromActivity.fromTransition = null - activity.toTransition.remove(); - } + ActivityLib.removeTransition(this); + }); + // need to use slice() to copy the array as it gets modified in removeTransition() + $.each(activity.transitions.to.slice(), function() { + ActivityLib.removeTransition(this); + }); // remove the activity from activities table activities.splice(activities.indexOf(activity), 1); @@ -231,58 +248,73 @@ /** * Draws a transition between two activities. */ - drawTransition : function(fromActivity, toActivity) { - // remove the existing activities - if (fromActivity.fromTransition) { - fromActivity.fromTransition.remove(); + drawTransition : function(fromActivity, toActivity, redraw) { + // only converge points are allowed to have few inbound transitions + if (!redraw + && toActivity.transitions.to.length > 0 + && !(toActivity.type == 'branchingEdge' && !toActivity.isStart)) { + alert('Transition to this activity already exists'); + return; } - if (toActivity.toTransition) { - toActivity.toTransition.remove(); + + // user chose to create outbound transition from an activity that already has one + if (!redraw + && fromActivity.transitions.from.length > 0 + && !(fromActivity.type == 'branchingEdge' && fromActivity.isStart) + && !(toActivity.type == 'branchingEdge' && toActivity.isStart)) { + if (confirm('Transition from this activity already exists.\n' + + 'Do you want to create branching here?')) { + ActivityLib.addBranching(fromActivity, toActivity); + } + return; } + // remove the existing transition + $.each(fromActivity.transitions.from, function(index) { + if (this.toActivity == toActivity) { + ActivityLib.removeTransition(this); + return false; + } + }); + // calculate middle points of each activity - var fromActivityBox = fromActivity.items.shape.getBBox(); - var toActivityBox = toActivity.items.shape.getBBox(); - var startX = fromActivityBox.x + fromActivityBox.width / 2; - var startY = fromActivityBox.y + fromActivityBox.height / 2; - var endX = toActivityBox.x + toActivityBox.width / 2; - var endY = toActivityBox.y + toActivityBox.height / 2; + var points = ActivityLib.findTransitionPoints(fromActivity, toActivity); // do the actual drawing paper.setStart(); - paper.path('M ' + startX + ' ' + startY + ' L ' + endX + ' ' + endY) - .attr({ - 'stroke' : layout.colors.transition, + paper.path(Raphael.format('M {0} {1} L {2} {3}', points.startX, points.startY, points.endX, points.endY)) + .attr({ + 'stroke' : layout.colors.transition, 'stroke-width' : 2 - }); + }); // draw the arrow and turn it in the same direction as the line - var angle = 90 + Math.atan2(endY - startY, endX - startX) * 180 / Math.PI; - var arrowX = (startX + (endX - startX)/2); - var arrowY = (startY + (endY - startY)/2); - var arrowPath = Raphael.transformPath('M ' + arrowX + ' ' + arrowY + layout.defs.transArrow, - 'R ' + angle + ' ' + arrowX + ' ' + arrowY); + var angle = 90 + Math.atan2(points.endY - points.startY, points.endX - points.startX) * 180 / Math.PI; + var arrowPath = Raphael.transformPath(Raphael.format('M {0} {1}' + layout.defs.transArrow, points.middleX, points.middleY), + Raphael.format('R {0} {1} {2}', angle, points.middleX, points.middleY)); paper.path(arrowPath) .attr({ 'stroke' : layout.colors.transition, 'fill' : layout.colors.transition }); - transition = paper.setFinish(); + var transition = paper.setFinish(); + transition.data('transition', transition); // set up references in activities and the transition + fromActivity.transitions.from.push(transition); + toActivity.transitions.to.push(transition); transition.fromActivity = fromActivity; transition.toActivity = toActivity; - fromActivity.fromTransition = transition; - toActivity.toTransition = transition; transition.toBack(); - transition.mousedown(function(event, x, y){ - // allow transition dragging - var mouseupHandler = function(){ - HandlerLib.dragTransitionEndHandler(transition); - }; - HandlerLib.dragItemsStartHandler(transition, this, mouseupHandler, event, x, y) - }); + transition.mousedown(HandlerLib.transitionMousedownHandler); + if (fromActivity.type == 'branchingEdge' && fromActivity.isStart) { + // create a new branch + var branch = new ActivityLib.BranchActivity(null, fromActivity.branchingActivity, transition); + fromActivity.branchingActivity.branches.push(branch); + transition.data('branch', branch); + } + // add grouping effect if previous activity is of grouping type if (fromActivity.type == 'group' && toActivity.type != 'gate') { ActivityLib.addGroupingEffect(toActivity); @@ -295,16 +327,82 @@ */ removeTransition : function(transition) { if (transition.toActivity.items.groupingEffect) { + // if toActivity had a grouping effect, remove it transition.toActivity.items.groupingEffect.remove(); transition.toActivity.items.groupingEffect = null; } - transition.fromActivity.fromTransition = null; - transition.toActivity.toTransition = null; + + // find the transition and remove it + var transitions = transition.fromActivity.transitions.from; + transitions.splice(transitions.indexOf(transition), 1); + transitions = transition.toActivity.transitions.to; + transitions.splice(transitions.indexOf(transition), 1); + + var branch = transition[0].data('branch'); + if (branch) { + var branches = branch.branchingActivity.branches; + branches.splice(branches.indexOf(branch), 1); + } + transition.remove(); }, /** + * Adds branching activity when user draws an extra outbout transition from. + */ + addBranching : function(fromActivity, toActivity1) { + // find the other toActivity + var existingTransition = fromActivity.transitions.from[0], + toActivity2 = existingTransition.toActivity, + branchingEdgeStart = null, + branchingEdgeEnd = null, + convergeActivity1 = toActivity1, + convergeActivity2 = toActivity2; + // find converge activity of the new branch + while (convergeActivity1.transitions.from.length > 0) { + convergeActivity1 = convergeActivity1.transitions.from[0].toActivity; + }; + + if (toActivity2.type == 'branchingEdge' && toActivity2.isStart) { + // there is already a branching activity, reuse existing items + branchingEdgeStart = toActivity2; + branchingEdgeEnd = toActivity2.branchingActivity.end; + } else { + // add new branching + ActivityLib.removeTransition(existingTransition); + + // calculate position of branching point + var branchPoints1 = ActivityLib.findTransitionPoints(fromActivity, toActivity1), + branchPoints2 = ActivityLib.findTransitionPoints(fromActivity, toActivity2), + branchEdgeStartX = branchPoints1.middleX + (branchPoints2.middleX - branchPoints1.middleX)/2, + branchEdgeStartY = branchPoints1.middleY + (branchPoints2.middleY - branchPoints1.middleY)/2, + branchingEdgeStart = new ActivityLib.BranchingEdgeActivity(null, branchEdgeStartX, + branchEdgeStartY, null); + activities.push(branchingEdgeStart); + + // find last activities in subsequences and make an converge point between them + while (convergeActivity2.transitions.from.length > 0) { + convergeActivity2 = convergeActivity2.transitions.from[0].toActivity; + }; + + var convergePoints = ActivityLib.findTransitionPoints(convergeActivity1, convergeActivity2), + branchingEdgeEnd = new ActivityLib.BranchingEdgeActivity(null, convergePoints.middleX, + convergePoints.middleY, branchingEdgeStart.branchingActivity); + activities.push(branchingEdgeEnd); + + // draw all required transitions + ActivityLib.drawTransition(fromActivity, branchingEdgeStart); + ActivityLib.drawTransition(branchingEdgeStart, toActivity2); + ActivityLib.drawTransition(convergeActivity2, branchingEdgeEnd); + } + + ActivityLib.drawTransition(branchingEdgeStart, toActivity1); + ActivityLib.drawTransition(convergeActivity1, branchingEdgeEnd); + }, + + + /** * Draws an extra border around the selected activity. */ addSelectEffect : function (activity) { @@ -331,6 +429,22 @@ }, + removeSelectEffect : function() { + var selectedActivity = layout.items.selectedActivity; + // does selection exist at all? + if (selectedActivity) { + if (selectedActivity.items.selectEffect) { + selectedActivity.items.selectEffect.remove(); + selectedActivity.items.selectEffect = null; + } + + // no selected activity = no properties dialog + layout.items.propertiesDialog.dialog('close'); + layout.items.selectedActivity = null; + } + }, + + /** * Adds visual grouping effect on an activity. */ @@ -349,5 +463,151 @@ .toBack(); activity.items.push(activity.items.groupingEffect); } + }, + + + /** + * Drop the dragged activity on the canvas. + */ + dropActivity : function(activity) { + // finally transform the dragged elements + var transformation = activity.items.shape.attr('transform'); + activity.items.transform(''); + if (transformation.length > 0) { + activity.items.forEach(function(elem) { + // some elements (rectangles) have "x", some are paths + if (elem.attr('x')) { + elem.attr({ + 'x' : elem.attr('x') + transformation[0][1], + 'y' : elem.attr('y') + transformation[0][2] + }); + } else { + var path = elem.attr('path'); + elem.attr('path', Raphael.transformPath(path, transformation)); + } + }); + } + + if (activity.items.groupingEffect) { + activity.items.groupingEffect.toBack(); + } + + // redraw transitions + $.each(activity.transitions.from.slice(), function(){ + ActivityLib.drawTransition(activity, this.toActivity, true); + }); + $.each(activity.transitions.to.slice(), function(){ + ActivityLib.drawTransition(this.fromActivity, activity, true); + }); + }, + + + /** + * Open separate window with activity authoring on double click. + */ + openActivityAuthoring : function(activity){ + // fetch authoring URL for a Tool Activity + if (!activity.authorURL && activity.toolID) { + $.ajax({ + async : false, + cache : false, + url : LAMS_URL + "authoring/author.do", + dataType : 'json', + data : { + 'method' : 'createToolContent', + 'toolID' : activity.toolID, + 'contentFolderID' : contentFolderID + }, + success : function(response) { + activity.authorURL = response.authorURL; + activity.id = response.toolContentID; + if (!contentFolderID) { + // if LD did not have contentFolderID, it was just generated + // so remember it + contentFolderID = response.contentFolderID; + } + } + }); + } + + if (activity.authorURL) { + window.open(activity.authorURL, 'activityAuthoring' + activity.id, + "HEIGHT=800,WIDTH=1024,resizable=yes,scrollbars=yes,status=false," + + "menubar=no,toolbar=no"); + } + }, + + + /** + * Drop the dragged transition. + */ + dropTransition : function(transition) { + // if the transition was over rubbish bin, remove it + transition.transform(''); + transition.toBack(); + }, + + + /** + * Calculates start, middle and end points of a line between two activities. + */ + findTransitionPoints : function(fromActivity, toActivity) { + var fromActivityBox = fromActivity.items.shape.getBBox(), + toActivityBox = toActivity.items.shape.getBBox(), + + // find points in the middle of each activity + points = { + 'startX' : fromActivityBox.x + fromActivityBox.width / 2, + 'startY' : fromActivityBox.y + fromActivityBox.height / 2, + 'endX' : toActivityBox.x + toActivityBox.width / 2, + 'endY' : toActivityBox.y + toActivityBox.height / 2 + }, + + // find intersection points of the temporary transition + tempTransition = Raphael.parsePathString(Raphael.format( + 'M {0} {1} L {2} {3}', points.startX, points.startY, points.endX, points.endY)), + fromIntersect = Raphael.pathIntersection(tempTransition, fromActivity.items.shape.attr('path')), + toIntersect = Raphael.pathIntersection(tempTransition, toActivity.items.shape.attr('path')); + + // find points on borders of activities, if they exist + if (fromIntersect.length > 0) { + points.startX = fromIntersect[0].x; + points.startY = fromIntersect[0].y; + } + if (toIntersect.length > 0) { + points.endX = toIntersect[0].x; + points.endY = toIntersect[0].y; + } + // middle point of the transition + points.middleX = points.startX + (points.endX - points.startX)/2; + points.middleY = points.startY + (points.endY - points.startY)/2; + + return points; + }, + + + /** + * Crawles through branches setting their lengths and finding the longest one. + */ + updateBranchesLength : function(branchingActivity) { + var longestBranchLength = 0; + $.each(branchingActivity.branches, function(){ + // include the first activity + var branchLength = 1, + activity = this.transitionFrom.toActivity; + while (activity.transitions.from.length > 0) { + activity = activity.transitions.from[0].toActivity; + // check if reached the end of branch + if (activity.type != 'branchingEdge') { + branchLength++; + } + }; + this.branchLength = branchLength; + if (branchLength > longestBranchLength) { + longestBranchLength = branchLength; + } + }); + + branchingActivity.longestBranchLength = longestBranchLength; } }; \ No newline at end of file Index: lams_central/web/includes/javascript/authoring/authoringGeneral.js =================================================================== diff -u -rebe1058e6422f0e8ce4d72f37a130316da559d62 -ra3d8ca871cecdad8f45756fdcae6d697bd0e8499 --- lams_central/web/includes/javascript/authoring/authoringGeneral.js (.../authoringGeneral.js) (revision ebe1058e6422f0e8ce4d72f37a130316da559d62) +++ lams_central/web/includes/javascript/authoring/authoringGeneral.js (.../authoringGeneral.js) (revision a3d8ca871cecdad8f45756fdcae6d697bd0e8499) @@ -3,23 +3,26 @@ */ // few publicly visible variables -var paper = null; -var canvas = null; -var activities = []; +var paper = null, + canvas = null, + activities = null; // configuration and storage of various elements var layout = { 'toolIcons': {}, 'conf' : { - 'activityWidth' : 125, - 'activityHeight' : 50, 'propertiesDialogDimOpacity' : 0.3, 'propertiesDialogDimThreshold' : 100, 'propertiesDialogDimThrottle' : 100, - 'dragStartThreshold' : 300 + 'dragStartThreshold' : 300, + 'arrangeHorizontalSpace' : 200, + 'arrangeVerticalSpace' : 100, + 'arrangeHorizontalPadding' : 40, + 'arrangeVerticalPadding' : 50 }, 'defs' : { - 'bin' : 'M 830 680 h -50 l 10 50 h 30 z', + 'activity' : ' h 125 v 50 h -125 z', + 'bin' : 'M 0 0 h -50 l 10 50 h 30 z', 'transArrow' : ' l 10 15 a 25 25 0 0 0 -20 0 z', 'gate' : ' l-8 8 v14 l8 8 h14 l8 -8 v-14 l-8 -8 z', 'branchingEdgeStart' : ' m -8 0 a 8 8 0 1 0 16 0 a 8 8 0 1 0 -16 0', @@ -48,8 +51,8 @@ * Initialises the whole Authoring window. */ $(document).ready(function() { - paper = Raphael('canvas'); canvas = $('#canvas'); + MenuLib.newLearningDesign(true); initLayout(); initTemplates(); @@ -121,9 +124,6 @@ * Initialises various Authoring widgets. */ function initLayout() { - // draw rubbish bin on canvas - layout.items.bin = paper.path(layout.defs.bin); - // add jQuery UI button functionality $('.ui-button').button(); $(".split-ui-button").each(function(){ @@ -246,8 +246,6 @@ of: '#canvas' } }); - - HandlerLib.resetCanvasMode(); } @@ -270,15 +268,10 @@ return; } - // reset the canvas - paper.clear(); - // draw the rubbish bin again - layout.items.bin = paper.path(layout.defs.bin); + MenuLib.newLearningDesign(true); // create visual representation of the loaded activities - activities = []; $.each(ld.activities, function() { var activity = this; - activities.push(new ActivityLib.ToolActivity(activity.activityID, activity.toolID, activity.xCoord, activity.yCoord, activity.activityTitle)); @@ -310,4 +303,30 @@ HandlerLib.resetCanvasMode(); } }); +} + + +function resizePaper(width, height) { + if (!paper) { + return; + } + + if (!width) { + width = canvas.width() - 5; + } + if (!height || (height == canvas.height() - 5)) { + height = canvas.height() - 5 - (width > canvas.width() - 5 ? 20 : 0); + } + paper.setSize(width, height); + + if (layout.items.bin) { + layout.items.bin.remove(); + } + + // draw rubbish bin on canvas + var binPath = Raphael.parsePathString(layout.defs.bin); + binPath = Raphael.transformPath(binPath, Raphael.format('t {0} {1}', width, height - 50)); + layout.items.bin = paper.path(binPath); + + HandlerLib.resetCanvasMode(); } \ No newline at end of file Index: lams_central/web/includes/javascript/authoring/authoringHandler.js =================================================================== diff -u -rebe1058e6422f0e8ce4d72f37a130316da559d62 -ra3d8ca871cecdad8f45756fdcae6d697bd0e8499 --- lams_central/web/includes/javascript/authoring/authoringHandler.js (.../authoringHandler.js) (revision ebe1058e6422f0e8ce4d72f37a130316da559d62) +++ lams_central/web/includes/javascript/authoring/authoringHandler.js (.../authoringHandler.js) (revision a3d8ca871cecdad8f45756fdcae6d697bd0e8499) @@ -8,10 +8,13 @@ * Default mode for canvas. Run after special mode is no longer needed. */ resetCanvasMode : function(){ + // remove selection if exists + ActivityLib.removeSelectEffect(); + canvas.css('cursor', 'default') .off('click') // if clicked anywhere, activity selection is gone - .click(HandlerLib.unselectActivityHandler) + .click(HandlerLib.canvasClickHandler) .off('mouseup') .off('mousemove') // when mouse gets closer to properties dialog, make it fully visible @@ -50,81 +53,22 @@ container.css('opacity', opacity); } }, - - + /** - * Show selection border around the clicked activity. - */ - selectActivityHandler : function(event, activity) { - if (activity != layout.items.selectedActivity) { - HandlerLib.unselectActivityHandler(event); - ActivityLib.addSelectEffect(activity); - } - // so canvas handler unselectActivityHandler() is not run - event.preventDefault(); - }, - - - /** * Remove activity selection when user clicks on canvas. */ - unselectActivityHandler : function(event) { + canvasClickHandler : function(event) { // check if user clicked on empty space on canvas // or on some element on top of it var defaultPrevented = event.originalEvent ? event.originalEvent.defaultPrevented : event.defaultPrevented; if (!defaultPrevented) { - var selectedActivity = layout.items.selectedActivity; - // does selection exist at all? - if (selectedActivity) { - selectedActivity.items.selectEffect.remove(); - selectedActivity.items.selectEffect = null; - - // no selected activity = no properties dialog - layout.items.propertiesDialog.dialog('close'); - layout.items.selectedActivity = null; - } + ActivityLib.removeSelectEffect(); } }, /** - * Open separate window with activity authoring on double click. - */ - openActivityAuthoringHandler : function(activity){ - // fetch authoring URL for a Tool Activity - if (!activity.authorURL && activity.toolID) { - $.ajax({ - async : false, - cache : false, - url : LAMS_URL + "authoring/author.do", - dataType : 'json', - data : { - 'method' : 'createToolContent', - 'toolID' : activity.toolID, - 'contentFolderID' : contentFolderID - }, - success : function(response) { - activity.authorURL = response.authorURL; - activity.id = response.toolContentID; - if (!contentFolderID) { - // if LD did not have contentFolderID, it was just generated - // so remember it - contentFolderID = response.contentFolderID; - } - } - }); - } - - if (activity.authorURL) { - window.open(activity.authorURL, 'activityAuthoring' + activity.id, - "HEIGHT=800,WIDTH=1024,resizable=yes,scrollbars=yes,status=false," + - "menubar=no,toolbar=no"); - } - }, - - - /** * Start dragging an activity or transition. */ dragItemsStartHandler : function(items, draggedElement, mouseupHandler, event, startX, startY) { @@ -153,15 +97,15 @@ HandlerLib.dragItemsMoveHandler(items, event, startX, startY); }); - var mouseup = function(){ + var mouseup = function(mouseupEvent){ // finish dragging - restore various elements' default state items.isDragged = false; items.unmouseup(); HandlerLib.resetCanvasMode(); layout.items.bin.attr('fill', 'transparent'); // do whetver needs to be done with the dragged elements - mouseupHandler(); + mouseupHandler(mouseupEvent); }; // if user moves mouse very quickly, mouseup event can be triggered @@ -185,71 +129,14 @@ } // highlight rubbish bin if dragged elements are over it - if (Raphael.isBBoxIntersect(layout.items.bin.getBBox(), items.getBBox())) { + if (HandlerLib.isElemenentBinned(event)) { layout.items.bin.attr('fill', layout.colors.binActive); } else { layout.items.bin.attr('fill', 'transparent'); } }, - /** - * Drop the dragged activity on the canvas. - */ - dragActivityEndHandler : function(activity) { - // if the activity was over rubbish bin, remove it - if (Raphael.isBBoxIntersect(layout.items.bin.getBBox(), activity.items.shape.getBBox())) { - ActivityLib.removeActivity(activity); - return; - } - - // finally transform the dragged elements - var transformation = activity.items.shape.attr('transform'); - activity.items.transform(''); - if (transformation.length > 0) { - activity.items.forEach(function(elem) { - // some elements (rectangles) have "x", some are paths - if (elem.attr('x')) { - elem.attr({ - 'x' : elem.attr('x') + transformation[0][1], - 'y' : elem.attr('y') + transformation[0][2] - }); - } else { - var path = elem.attr('path'); - elem.attr('path', Raphael.transformPath(path, transformation)); - } - }); - } - - if (activity.items.groupingEffect) { - activity.items.groupingEffect.toBack(); - } - - // redraw transitions - if (activity.fromTransition) { - ActivityLib.drawTransition(activity, activity.fromTransition.toActivity); - } - if (activity.toTransition) { - ActivityLib.drawTransition(activity.toTransition.fromActivity, activity); - } - }, - - - /** - * Drop the dragged transition. - */ - dragTransitionEndHandler : function(transition) { - // if the transition was over rubbish bin, remove it - if (Raphael.isBBoxIntersect(layout.items.bin.getBBox(), transition.getBBox())) { - ActivityLib.removeTransition(transition); - } else { - // cancel drag - transition.transform(''); - transition.toBack(); - } - }, - - /** * Start drawing a transition. */ drawTransitionStartHandler : function(activity, event, x, y) { @@ -263,7 +150,8 @@ canvas.mousemove(function(event){ HandlerLib.drawTransitionMoveHandler(activity, event, startX, startY); - }).mouseup(function(event){ + }) + .mouseup(function(event){ HandlerLib.drawTransitionEndHandler(activity, event); }); }, @@ -302,27 +190,133 @@ * Finalise transition drawing. */ drawTransitionEndHandler : function(activity, event) { + // prevent triggering event on several activity items; we just need on transition + event.stopImmediatePropagation(); HandlerLib.resetCanvasMode(); - //remove the temporary transition + //remove the temporary transition (dashed line) if (activity.tempTransition) { activity.tempTransition.remove(); activity.tempTransition = null; } - var endX = event.pageX - canvas.offset().left; - var endY = event.pageY - canvas.offset().top; var endActivity = null; - // find the target activity - $.each(activities, function(){ - if (this.items.shape.isPointInside(endX, endY)) { - endActivity = this; - return false; - } - }); + var targetElement = paper.getElementByPoint(event.pageX, event.pageY); + if (targetElement) { + endActivity = targetElement.data('activity'); + } - if (endActivity) { + if (endActivity && activity != endActivity) { ActivityLib.drawTransition(activity, endActivity); } + }, + + + /** + * Lighthens up branching edges in the same colour for identifictation. + */ + branchingEdgeMouseoverHandler : function() { + var branchingActivity = this.data('activity').branchingActivity; + var startItems = branchingActivity.start.items; + var endItems = branchingActivity.end.items; + if (!startItems.isDragged && !endItems.isDragged) { + startItems.shape.attr('fill', layout.colors.branchingEdgeMatch); + endItems.shape.attr('fill', layout.colors.branchingEdgeMatch); + } + }, + + + /** + * Return branching edges to their normal colours. + */ + branchingEdgeMouseoutHandler : function() { + var branchingActivity = this.data('activity').branchingActivity; + var startItems = branchingActivity.start.items; + var endItems = branchingActivity.end.items; + if (!startItems.isDragged && !endItems.isDragged) { + startItems.shape.attr('fill', layout.colors.branchingEdgeStart); + endItems.shape.attr('fill', layout.colors.branchingEdgeEnd); + } + }, + + + /** + * Starts drawing a transition or dragging an activity. + */ + activityMousedownHandler : function(event, x, y){ + var activity = this.data('activity'); + if (event.ctrlKey) { + // when CTRL is held down, start drawing a transition + HandlerLib.drawTransitionStartHandler(activity, event, x, y); + } else { + var mouseupHandler = function(event){ + if (HandlerLib.isElemenentBinned(event)) { + // if the activity was over rubbish bin, remove it + ActivityLib.removeActivity(activity); + } else { + ActivityLib.dropActivity(activity); + } + } + // start dragging the activity + HandlerLib.dragItemsStartHandler(activity.items, this, mouseupHandler, event, x, y); + } + }, + + + /** + * Selects an activity. + */ + activityClickHandler : function(event) { + var activity = this.data('activity'); + // inform that user wants to select, not drag the activity + activity.items.clicked = true; + if (activity != layout.items.selectedActivity) { + HandlerLib.canvasClickHandler(event); + ActivityLib.addSelectEffect(activity); + } + // so canvas handler unselectActivityHandler() is not run + event.preventDefault(); + }, + + + /** + * Opens activity authoring. + */ + activityDblclickHandler : function(event) { + var activity = this.data('activity'); + // inform that user wants to open, not drag the activity + activity.items.clicked = true; + ActivityLib.openActivityAuthoring(activity); + }, + + + /** + * Starts dragging a transition. + */ + transitionMousedownHandler : function(event, x, y){ + var transition = this.data('transition'); + // allow transition dragging + var mouseupHandler = function(event){ + if (HandlerLib.isElemenentBinned(event)) { + // if the transition was over rubbish bin, remove it + ActivityLib.removeTransition(transition); + } else { + ActivityLib.dropTransition(transition); + } + } + + HandlerLib.dragItemsStartHandler(transition, this, mouseupHandler, event, x, y); + }, + + + /** + * Checks whether activity or transition is over rubbish bin. + */ + isElemenentBinned : function(event) { + var canvasX = event.pageX - canvas.offset().left; + var canvasY = event.pageY - canvas.offset().top; + + // highlight rubbish bin if dragged elements are over it + return Raphael.isPointInsideBBox(layout.items.bin.getBBox(), canvasX, canvasY); } }; \ No newline at end of file Index: lams_central/web/includes/javascript/authoring/authoringMenu.js =================================================================== diff -u -rebe1058e6422f0e8ce4d72f37a130316da559d62 -ra3d8ca871cecdad8f45756fdcae6d697bd0e8499 --- lams_central/web/includes/javascript/authoring/authoringMenu.js (.../authoringMenu.js) (revision ebe1058e6422f0e8ce4d72f37a130316da559d62) +++ lams_central/web/includes/javascript/authoring/authoringMenu.js (.../authoringMenu.js) (revision a3d8ca871cecdad8f45756fdcae6d697bd0e8499) @@ -15,18 +15,17 @@ var x = event.pageX - canvas.offset().left; var y = event.pageY - canvas.offset().top; - var isStart = !branchingActivity; var branchingEdge = new ActivityLib.BranchingEdgeActivity(null, x, y, branchingActivity); activities.push(branchingEdge); - if (isStart) { - branchingActivity = branchingEdge.branchingActivity; - dialog.text('Place the converge point'); - } else { + if (branchingActivity) { + HandlerLib.resetCanvasMode(); + dialog.text(''); dialog.dialog('close'); - - HandlerLib.resetCanvasMode(); + } else { + branchingActivity = branchingEdge.branchingActivity; + dialog.text('Place the converge point'); } }); }, @@ -36,13 +35,14 @@ */ addGrouping : function() { canvas.css('cursor', 'url("../images/grouping.gif"), move').click(function(event){ + HandlerLib.resetCanvasMode(); + // pageX and pageY tell event coordinates relative to the whole page // we need relative to canvas var x = event.pageX - canvas.offset().left; var y = event.pageY - canvas.offset().top; activities.push(new ActivityLib.GroupingActivity(null, x, y)); - HandlerLib.resetCanvasMode(); }); }, @@ -52,13 +52,14 @@ */ addGate : function() { canvas.css('cursor', 'url("../images/stop.gif"), move').click(function(event){ + HandlerLib.resetCanvasMode(); + // pageX and pageY tell event coordinates relative to the whole page // we need relative to canvas var x = event.pageX - canvas.offset().left; var y = event.pageY - canvas.offset().top; activities.push(new ActivityLib.GateActivity(null, x, y)); - HandlerLib.resetCanvasMode(); }); }, @@ -161,5 +162,208 @@ }); return result; + }, + + + /** + * Sorts activities on canvas. + */ + arrangeActivities : function(){ + // just to refresh the state of canvas + HandlerLib.resetCanvasMode(); + + if (activities.length == 0) { + // no activities, nothing to do + return; + } + + // activities are arranged in a grid + var row = 0, + column = 0, + // check how many columns current paper can hold + maxColumns = Math.floor((paper.width - layout.conf.arrangeHorizontalPadding) + / layout.conf.arrangeHorizontalSpace), + // the initial max length of subsequences is limited by paper space + subsequenceMaxLength = maxColumns, + // check how many rows current paper can hold + maxRows = Math.floor((paper.height - layout.conf.arrangeVerticalPadding) + / layout.conf.arrangeVerticalSpace), + // make a shallow copy of activities array + activitiesCopy = activities.slice(), + // just to speed up processing when there are only activities with no transitions left + onlyDetachedLeft = false; + + // branches will not be broken into few rows; if they are long, paper will be resized + // find the longes branch to find the new paper size + $.each(activities, function(){ + if (this.type == 'branchingEdge' && this.isStart) { + // refresh branching metadata + ActivityLib.updateBranchesLength(this.branchingActivity); + // add start and end edges to the result + var longestBranchLength = this.branchingActivity.longestBranchLength + 2; + if (longestBranchLength > subsequenceMaxLength) { + subsequenceMaxLength = longestBranchLength; + } + } + }); + // resize paper horizontally, if needed + if (subsequenceMaxLength > maxColumns) { + maxColumns = subsequenceMaxLength; + resizePaper(layout.conf.arrangeHorizontalPadding + + maxColumns * layout.conf.arrangeHorizontalSpace, + paper.height); + } + + // main loop; iterate over whatever is left in the array + while (activitiesCopy.length > 0) { + // resize paper vertically, if needed + if (row > maxRows) { + maxRows++; + resizePaper(paper.width, layout.conf.arrangeVerticalPadding + + maxColumns * layout.conf.arrangeVerticalSpace); + } + + // look for activities with transitions first; detached ones go to the very end + var activity = null; + if (!onlyDetachedLeft) { + $.each(activitiesCopy, function(){ + if (this.transitions.to.length > 0) { + activity = this; + while (activity.transitions.to.length > 0) { + // check if previous activity was not drawn already + // it can happen for branching edges + var activityLookup = activity.transitions.to[0].fromActivity; + if (activitiesCopy.indexOf(activityLookup) == -1) { + break; + } + activity = activityLookup; + }; + return false; + } + }); + } + + if (!activity) { + // no activities with transitions left, take first detached one + onlyDetachedLeft = true; + activity = activitiesCopy[0]; + } + + // markers for complex activity processing + var branchIndex = null, + complexActivityEnd = null; + + // crawl through a sequence of activities + while (activity) { + if (activity.type == 'branchingEdge') { + // draw branching edges straight away and remove them from normall processing + branchingActivity = activity.branchingActivity; + var start = branchingActivity.start, + end = branchingActivity.end, + complexActivityEnd = end, + // edge points go to middle of rows with branches + branchingRow = row + 1 + Math.floor(branchingActivity.branches.length / 2); + + activitiesCopy.splice(activitiesCopy.indexOf(start), 1); + activitiesCopy.splice(activitiesCopy.indexOf(end), 1); + + // start point goes to very left, end goes wherever the longes branch ends + start.draw(layout.conf.arrangeHorizontalPadding + 62, + layout.conf.arrangeVerticalPadding + branchingRow * layout.conf.arrangeVerticalSpace + 25); + end.draw(layout.conf.arrangeHorizontalPadding + (activity.branchingActivity.longestBranchLength + 1) + * layout.conf.arrangeHorizontalSpace + 62, + layout.conf.arrangeVerticalPadding + branchingRow * layout.conf.arrangeVerticalSpace + 25); + + row++; + if (branchingActivity.branches.length > 0) { + // set up branch drawing + branchIndex = 0; + activity = branchingActivity.branches[branchIndex].transitionFrom.toActivity; + // next activity for normal processing will be first one from the first branch + continue; + } else { + // no branches, nothing to do, carry on with normall activity processing + activity = complexActivityEnd; + complexActivityEnd = null; + // this will be incremented in the same iteration by normal activity processing + column = 1; + } + } else { + // it is a simple activity, so redraw it + var x = layout.conf.arrangeHorizontalPadding + column * layout.conf.arrangeHorizontalSpace, + y = layout.conf.arrangeVerticalPadding + row * layout.conf.arrangeVerticalSpace; + + activity.draw(x, y); + // remove the activity so we do not process it twice + activitiesCopy.splice(activitiesCopy.indexOf(activity), 1); + } + + // find the next row and column + column = (column + 1) % maxColumns; + if (column == 0) { + row++; + } + + // does the activity has further activities? + if (activity.transitions.from.length > 0) { + activity = activity.transitions.from[0].toActivity; + } else { + activity = null; + } + + if (complexActivityEnd && (!activity || activity == complexActivityEnd)) { + // end of branch + branchIndex++; + row++; + if (complexActivityEnd.branchingActivity.branches.length > branchIndex) { + // there is another branch to process + activity = complexActivityEnd.branchingActivity.branches[branchIndex].transitionFrom.toActivity; + // go back to left side of canvas and draw next branch + column = 1; + continue; + } else { + // no more branches, return to normal activity processing + activity = complexActivityEnd; + branchIndex = null; + complexActivityEnd = null; + column = 0; + if (activity.transitions.from.length == 0) { + break; + } + } + } + + if (!activity || activitiesCopy.indexOf(activity) == -1) { + // next activity was already processed, so stop crawling + break; + } + }; + }; + + // redraw transitions one by one + $.each(activities, function(){ + $.each(this.transitions.from.slice(), function(){ + ActivityLib.drawTransition(this.fromActivity, this.toActivity, true); + }); + }); + }, + + + /** + * Removes existing activities and prepares canvas for a new sequence. + */ + newLearningDesign : function(force){ + if (!force && activities.length > 0 && !confirm('Are you sure you want to remove all existing activities?')){ + return; + } + + activities = []; + if (paper) { + paper.clear(); + } else { + paper = Raphael('canvas'); + } + + resizePaper(); } }; \ No newline at end of file