diff --git a/src/GraphMind.mxml b/src/GraphMind.mxml index 17c5dca..9dd227c 100644 --- a/src/GraphMind.mxml +++ b/src/GraphMind.mxml @@ -91,11 +91,11 @@ - + diff --git a/src/assets/images/arrow_refresh.png b/src/assets/images/arrow_refresh.png new file mode 100755 index 0000000..0de2656 Binary files /dev/null and b/src/assets/images/arrow_refresh.png differ diff --git a/src/assets/images/cross_orange.png b/src/assets/images/cross_orange.png new file mode 100644 index 0000000..3333136 Binary files /dev/null and b/src/assets/images/cross_orange.png differ diff --git a/src/com/graphmind/ApplicationController.as b/src/com/graphmind/ApplicationController.as index 60c007e..eb07759 100644 --- a/src/com/graphmind/ApplicationController.as +++ b/src/com/graphmind/ApplicationController.as @@ -49,11 +49,6 @@ package com.graphmind { */ protected var _isEditable:Boolean = false; - /** - * Feature array. - */ - public var features:Array; - /** * Disk image source for the save button. */ @@ -114,23 +109,16 @@ package com.graphmind { GraphMind.i.map.addChild(this.treeMapViewController.view); // Establish connection to the Drupal site. - ConnectionController.mainConnection = ConnectionController.createConnection(getBaseDrupalURL()); - ConnectionController.mainConnection.isSessionAuthentication = true; + ConnectionController.mainConnection = ConnectionController.createConnection(getBaseDrupalURL(), getServiceEndpoint()); - ConnectionController.mainConnection.addEventListener(ConnectionEvent.CONNECTION_IS_READY, onSuccess_siteIsConnected); ConnectionController.mainConnection.addEventListener(ConnectionIOErrorEvent.IO_ERROR_EVENT, ConnectionController.defaultIOErrorHandler); ConnectionController.mainConnection.addEventListener(ConnectionNetStatusEvent.NET_STATUS_EVENT, ConnectionController.defaultNetStatusHandler); - ConnectionController.mainConnection.connect(); EventCenter.subscribe(EventCenterEvent.REQUEST_FOR_FREEMIND_XML, onAppFormRequestForFreemindXml); - MainMenuController.createIconMenuItem(diskImage, 'Save', onClick_saveMenuItem); - MainMenuController.createIconMenuItem(fullScreenImage, 'Full screen', onClick_fullScreenIcon); - applicationSettingsComponent = new ApplicationSettingsComponent(); applicationSettingsPanel = new ConfigPanelController('Map settings'); applicationSettingsPanel.addItem(applicationSettingsComponent); - MainMenuController.createIconMenuItem(gearImage, 'Settings', onClick_ApplicationSettingsMenuItem); applicationSettingsComponent.desktopScaleHSlider.addEventListener(SliderEvent.CHANGE, onChange_mapScaleSlider); applicationSettingsComponent.nodeSizeSelect.addEventListener(ListEvent.CHANGE, onDataChange_nodeSizeSelect); var e:EventCenterEvent = EventCenter.notify(EventCenterEvent.ALTER_SETTINGS_PANEL, []); @@ -138,15 +126,10 @@ package com.graphmind { applicationSettingsPanel.addItem(item); } - if (FeatureController.isFeatureEnabled(FeatureController.CONNECTIONS)) { - connectionSettingsComponent = new ConnectionSettingsComponent(); - connectionSettingsPanel = new ConfigPanelController('Remote connections'); - connectionSettingsPanel.addItem(connectionSettingsComponent); - MainMenuController.createIconMenuItem(connectionImage, 'Connections', onClick_ConnectionsMenuItem); - connectionSettingsComponent.saveButton.addEventListener(MouseEvent.CLICK, onClick_AddNewSiteConnectionButton); - } - NodeViewController.init(); + + // No necessary to call system.connect. We just start with a normal call. + onSuccess_siteIsConnected(null); } @@ -158,6 +141,14 @@ package com.graphmind { } + /** + * Returns the URL of the service endpoint. + */ + public static function getServiceEndpoint():String { + return Application.application.parameters.endPoint; + } + + /** * Get hosting node's NID */ @@ -179,20 +170,9 @@ package com.graphmind { */ protected function onSuccess_siteIsConnected(event:ConnectionEvent):void { Log.info("Connection to Drupal is established."); - // Get all the available features - ConnectionController.mainConnection.call('graphmind.getFeatures', onSuccess_featuresAreLoaded, null, getHostNodeID()); - ConnectionController.mainConnection.call('graphmind.getViews', onSuccess_viewsListsAreLoaded, null); - ConnectionController.mainConnection.call('node.get', onSuccess_rootNodeIsLoaded, null, getHostNodeID()); - } - - - /** - * Features are loaded. - * Features are disabled by default. - */ - protected function onSuccess_featuresAreLoaded(result:Object):void { - Log.info("Features are loaded: " + result.toString()); - this.features = result as Array; + // Views service is not ported to D7 yet. + //ConnectionController.mainConnection.call('graphmind.getViews', onSuccess_viewsListsAreLoaded, ConnectionController.defaultRequestErrorHandler); + ConnectionController.mainConnection.call('graphmind.isNodeEditable', onSuccess_isNodeEditable, ConnectionController.defaultRequestErrorHandler, getHostNodeID()); } @@ -206,6 +186,32 @@ package com.graphmind { new DrupalViews(data, ConnectionController.mainConnection); } } + + + /** + * Event callback - when edit check is back. + */ + protected function onSuccess_isNodeEditable(result:Object):void { + setEditMode(result); + + if (isEditable()) { + MainMenuController.createIconMenuItem(diskImage, 'Save', onClick_saveMenuItem); + } + + MainMenuController.createIconMenuItem(fullScreenImage, 'Full screen', onClick_fullScreenIcon); + + MainMenuController.createIconMenuItem(gearImage, 'Settings', onClick_ApplicationSettingsMenuItem); + + if (FeatureController.isFeatureEnabled(FeatureController.CONNECTIONS)) { + connectionSettingsComponent = new ConnectionSettingsComponent(); + connectionSettingsPanel = new ConfigPanelController('Remote connections'); + connectionSettingsPanel.addItem(connectionSettingsComponent); + MainMenuController.createIconMenuItem(connectionImage, 'Connections', onClick_ConnectionsMenuItem); + connectionSettingsComponent.saveButton.addEventListener(MouseEvent.CLICK, onClick_AddNewSiteConnectionButton); + } + + ConnectionController.mainConnection.call('node.retrieve', onSuccess_rootNodeIsLoaded, ConnectionController.defaultRequestErrorHandler, getHostNodeID()); + } /** @@ -213,7 +219,7 @@ package com.graphmind { */ protected function onSuccess_rootNodeIsLoaded(result:Object):void { Log.info("Root node is loaded: " + result.nid); - setEditMode(result.graphmindEditable == '1'); + TreeMapViewController.rootNode = ImportManager.importNodesFromDrupalResponse(result); // Call map to draw its contents. @@ -304,10 +310,11 @@ package com.graphmind { * Event handler for */ public function onClick_AddNewSiteConnectionButton(e:MouseEvent):void { - var url:String = connectionSettingsComponent.connectFormURL.text; + var basePath:String = connectionSettingsComponent.connectFormBasePath.text; + var endPoint:String = connectionSettingsComponent.connectFormEndPoint.text; var userName:String = connectionSettingsComponent.connectFormUsername.text; var userPassword:String = connectionSettingsComponent.connectFormPassword.text; - var conn:Connection = ConnectionController.createConnection(url); + var conn:Connection = ConnectionController.createConnection(basePath, endPoint); conn.userName = userName; conn.userPassword = userPassword; diff --git a/src/com/graphmind/ConnectionController.as b/src/com/graphmind/ConnectionController.as index 452b939..b35a538 100644 --- a/src/com/graphmind/ConnectionController.as +++ b/src/com/graphmind/ConnectionController.as @@ -42,7 +42,7 @@ package com.graphmind { private static function addConnection(conn:Connection):void { // Check if the connection is already added. for (var idx:* in _connections) { - if ((_connections[idx] as Connection).target == conn.target) { + if ((_connections[idx] as Connection).basePath == conn.basePath && (_connections[idx] as Connection).endPoint == conn.endPoint) { return; } } @@ -81,7 +81,7 @@ package com.graphmind { public static function defaultNetStatusHandler(event:ConnectionNetStatusEvent):void { OSD.show( "Network status error." + - "\n Connection: " + event.connection.target + + "\n Connection: " + event.connection.basePath + ' [' + event.connection.endPoint + ']' + "\n Code: " + event.netStatusEvent.info.code + "\n Description: " + event.netStatusEvent.info.description + "\n Details: " + event.netStatusEvent.info.details + @@ -93,14 +93,14 @@ package com.graphmind { /** * Connection factory. */ - public static function createConnection(target:String):Connection { + public static function createConnection(basePath:String, endPoint:String):Connection { for (var idx:* in _connections) { - if ((_connections[idx] as Connection).target == target) { + if ((_connections[idx] as Connection).basePath == basePath && (_connections[idx] as Connection).endPoint == endPoint) { return _connections[idx]; } } - var conn:Connection = new Connection(target); + var conn:Connection = new Connection(basePath, endPoint); addConnection(conn); return conn; } diff --git a/src/com/graphmind/ExportController.as b/src/com/graphmind/ExportController.as index b831bf9..8545757 100644 --- a/src/com/graphmind/ExportController.as +++ b/src/com/graphmind/ExportController.as @@ -2,7 +2,6 @@ package com.graphmind { import com.graphmind.event.EventCenter; import com.graphmind.event.EventCenterEvent; - import com.kitten.network.Connection; public class ExportController { @@ -22,15 +21,30 @@ package com.graphmind { * @return string */ public static function getFreeMindXML(node:NodeViewController):String { - return '' + "\n" + node.exportToFreeMindFormat() + '' + "\n"; + return '' + "\n" + + '' + + '' + "\n" + + node.exportToFreeMindFormat() + + '' + "\n"; } /** * Save work into host node */ - public static function saveFreeMindXMLToDrupal(conn:Connection, xml:String, nid:uint):void { - conn.call('graphmind.saveGraphMind', onSaveFreemindXMLToDrupalSucceed, ConnectionController.defaultRequestErrorHandler, nid, xml, lastSaved * 0.001); + public static function saveFreeMindXMLToDrupal(xml:String, nid:uint):void { + ConnectionController.mainConnection.call('graphmind.saveGraphMind', onSaveFreemindXMLToDrupalSucceed, ConnectionController.defaultRequestErrorHandler, nid, xml, lastSaved * 0.001); + } + + + /** + * Save map silently. + */ + public static function saveFreeMindXMLToDrupalSilent(xml:String, nid:uint):void { + ConnectionController.mainConnection.call('graphmind.saveGraphMind', function(e:Object):void{ + lastSaved = new Date().time; + EventCenter.notify(EventCenterEvent.MAP_SAVED_SILENTLY, e); + }, ConnectionController.defaultRequestErrorHandler, nid, xml, lastSaved * 0.001); } diff --git a/src/com/graphmind/FeatureController.as b/src/com/graphmind/FeatureController.as index 5b37137..2193100 100644 --- a/src/com/graphmind/FeatureController.as +++ b/src/com/graphmind/FeatureController.as @@ -1,6 +1,7 @@ package com.graphmind { - import mx.graphics.Stroke; + import com.graphmind.event.EventCenter; + import com.graphmind.event.EventCenterEvent; public class FeatureController { @@ -14,6 +15,8 @@ package com.graphmind { public static var ATTRIBUTES:String = 'attributes'; public static var CONNECTIONS:String = 'connections'; public static var TOOLTIPS:String = 'tooltips'; + public static var NODE_INFO:String = 'nodeInfo'; + public static var REMOVE_NODE:String = 'removeNode'; /** * Inner feature storage. @@ -26,6 +29,7 @@ package com.graphmind { */ public static function set features(aFeatures:Array):void { _features = aFeatures; + EventCenter.notify(EventCenterEvent.FEATURES_CHANGED); } @@ -35,6 +39,7 @@ package com.graphmind { public static function addFeature(feature:String):void { if (_features.indexOf(feature) == -1) { _features.push(feature); + EventCenter.notify(EventCenterEvent.FEATURES_CHANGED); } } @@ -45,6 +50,7 @@ package com.graphmind { public static function removeFeature(feature:String):void { if (_features.indexOf(feature) !== -1) { delete _features[_features.indexOf(feature)]; + EventCenter.notify(EventCenterEvent.FEATURES_CHANGED); } } diff --git a/src/com/graphmind/ImportManager.as b/src/com/graphmind/ImportManager.as index 752273a..90117c0 100644 --- a/src/com/graphmind/ImportManager.as +++ b/src/com/graphmind/ImportManager.as @@ -12,7 +12,7 @@ package com.graphmind { public static function importNodesFromDrupalResponse(response:Object):NodeViewController { var rootNode:NodeViewController; var is_valid_mm_xml:Boolean = false; - var body:String = response.body.toString(); + var body:String = response['body'].hasOwnProperty(response.language) ? response.body[response.language][0]['value'].toString() : ''; if (body.length > 0) { var xmlData:XML = new XML(body); var nodes:XML = xmlData.child('node')[0]; @@ -73,7 +73,7 @@ package com.graphmind { // Site connection var conn:Connection = null; if (nodeXML.site) { - conn = ConnectionController.createConnection(unescape(nodeXML.site.@URL)); + conn = ConnectionController.createConnection(unescape(nodeXML.site.@BASEPATH), unescape(nodeXML.site.@ENDPOINT)); } var node:NodeViewController = new NodeViewController(new NodeDataObject( diff --git a/src/com/graphmind/NodeContextMenuController.as b/src/com/graphmind/NodeContextMenuController.as new file mode 100644 index 0000000..96854b3 --- /dev/null +++ b/src/com/graphmind/NodeContextMenuController.as @@ -0,0 +1,58 @@ +package com.graphmind { + + import com.graphmind.data.NodeContextMenu; + import com.graphmind.data.NodeContextMenuSection; + + import flash.events.ContextMenuEvent; + import flash.ui.ContextMenuItem; + + /** + * Manager of a node's context menus. + * It collects sections and items and outputs the context menu link list. + */ + public class NodeContextMenuController { + + /** + * Add new menu item. + */ + public function addItem(name:String, callback:Function, weight:Number = 0, sectionName:String = 'default'):void { + var item:NodeContextMenu = new NodeContextMenu(name, callback, weight); + var section:NodeContextMenuSection = NodeContextMenuSection.getSection(sectionName); + section.addContextMenu(item); + } + + + /** + * Set weight of a section. + */ + public function setSectionWeight(name:String, weight:Number):void { + var section:NodeContextMenuSection = NodeContextMenuSection.getSection(name); + section.weight = weight; + } + + + /** + * Get the final item list - used by ContextMenu.customMenu. + */ + public function getContextMenus():Array { + var out:Array = []; + var isFirstFlag:Boolean = false; + + NodeContextMenuSection.sortSection(); + + for each (var section:NodeContextMenuSection in NodeContextMenuSection.sections) { + isFirstFlag = true; + for each (var item:NodeContextMenu in section.contextMenus) { + var contextMenu:ContextMenuItem = new ContextMenuItem(item.name, isFirstFlag); + contextMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, item.callback); + isFirstFlag = false; + out.push(contextMenu); + } + } + + return out; + } + + } + +} diff --git a/src/com/graphmind/NodeViewController.as b/src/com/graphmind/NodeViewController.as index 1b4a55b..da37d40 100644 --- a/src/com/graphmind/NodeViewController.as +++ b/src/com/graphmind/NodeViewController.as @@ -35,7 +35,6 @@ package com.graphmind { import flash.net.URLRequest; import flash.net.navigateToURL; import flash.ui.ContextMenu; - import flash.ui.ContextMenuItem; import flash.ui.Keyboard; import flash.utils.clearTimeout; import flash.utils.setTimeout; @@ -161,6 +160,11 @@ package com.graphmind { public static var CAN_HAS_ATTRIBUTES:String = 'canHasAttributes'; public static var canHasAttributes:Boolean = true; + /** + * Feature - node title editing. + */ + public static var canHasTitleEditing:Boolean = true; + /** * Add action icon and the image source. */ @@ -265,7 +269,9 @@ package com.graphmind { } // Event listeners - view.nodeComponentView.title_label.addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick); + if (canHasTitleEditing) { + view.nodeComponentView.title_label.addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick); + } view.nodeComponentView.title_new.addEventListener(KeyboardEvent.KEY_UP, onKeyUp_TitleTextField); view.nodeComponentView.title_new.addEventListener(FocusEvent.FOCUS_OUT, onFocusOut_TitleTextField); view.nodeComponentView.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); @@ -306,11 +312,13 @@ package com.graphmind { */ public static function init():void { // Node info panel - nodeConfigPanel = new ConfigPanelController('Node Settings'); - nodeConfigComponent = new NodeInfo(); - nodeConfigPanel.addItem(nodeConfigComponent); - nodeConfigComponent.saveTitleButton.addEventListener(MouseEvent.CLICK, onClick_saveTitleButton); - nodeConfigComponent.saveURLButton.addEventListener(MouseEvent.CLICK, onClick_saveURLButton); + if (FeatureController.isFeatureEnabled(FeatureController.NODE_INFO)) { + nodeConfigPanel = new ConfigPanelController('Node Settings'); + nodeConfigComponent = new NodeInfo(); + nodeConfigPanel.addItem(nodeConfigComponent); + nodeConfigComponent.saveTitleButton.addEventListener(MouseEvent.CLICK, onClick_saveTitleButton); + nodeConfigComponent.saveURLButton.addEventListener(MouseEvent.CLICK, onClick_saveURLButton); + } if (canHasAttributes && FeatureController.isFeatureEnabled(FeatureController.ATTRIBUTES)) { nodeAttributesPanel = new ConfigPanelController('Attributes'); @@ -324,6 +332,7 @@ package com.graphmind { nodeIconsPanel = new ConfigPanelController('Icons'); nodeIconsComponent = new NodeIcons(); nodeIconsPanel.addItem(nodeIconsComponent); + nodeIconsPanel.addExitItem(nodeIconsComponent.doneButton); EventCenter.subscribe(EventCenterEvent.ICON_SELECTED, onIconSelected); loadDrupalItemPanel = new ConfigPanelController('Load Drupal item'); @@ -355,43 +364,44 @@ package com.graphmind { /** * Get a complete context menu for the UI. */ - public function getContextMenu():ContextMenu { + public function getContextMenu():ContextMenu { var contextMenu:ContextMenu = new ContextMenu(); contextMenu.customItems = []; contextMenu.hideBuiltInItems(); + var contextMenuController:NodeContextMenuController = new NodeContextMenuController(); - var cms:Array = []; - cms.push({title: 'Node info', event: onContextMenuSelected_NodeInfo, separator: false}); + if (FeatureController.isFeatureEnabled(FeatureController.NODE_INFO)) { + contextMenuController.addItem('Node info', onContextMenuSelected_NodeInfo, 0, 'data'); + } if (canHasAttributes && FeatureController.isFeatureEnabled(FeatureController.ATTRIBUTES)) { - cms.push({title: 'Attributes', event: onContextMenuSelected_NodeAttributes, separator: false}); + contextMenuController.addItem('Attributes', onContextMenuSelected_NodeAttributes, 0, 'data'); + if (NodeType.updatableTypes.indexOf(nodeData.type) >= 0) { + contextMenuController.addItem('Fetch Drupal data', onContextMenuSelected_UpdateDrupalItem, 0, 'data'); + } } - cms.push({title: 'Icons', event: onContextMenuSelected_NodeIcons, separator: false}); - cms.push({title: 'Add node', event: onContextMenuSelected_AddSimpleNode, separator: false}); + contextMenuController.addItem('Icons', onContextMenuSelected_NodeIcons, 3, 'data'); + if (canHasNormalChild) { + contextMenuController.addItem('Add node', onContextMenuSelected_AddSimpleNode, 0, 'data'); + } if (FeatureController.isFeatureEnabled(FeatureController.LOAD_DRUPAL_NODE)) { - cms.push({title: 'Load Drupal item', event: onContextMenuSelected_AddDrupalItem, separator: false}); + contextMenuController.addItem('Load Drupal item', onContextMenuSelected_AddDrupalItem, 0, 'data'); } if (FeatureController.areFeaturesEnabled([FeatureController.LOAD_DRUPAL_NODE, FeatureController.LOAD_DRUPAL_VIEWS_LIST])) { - cms.push({title: 'Load Views list', event: onContextMenuSelected_AddDrupalViews, separator: false}); + contextMenuController.addItem('Load Drupal Views list', onContextMenuSelected_AddDrupalViews, 0, 'data'); } - cms.push({title: 'Remove node', event: onContextMenuSelected_RemoveNode, separator: true}); - cms.push({title: 'Expand subtree', event: onContextMenuSelected_OpenSubtree, separator: true}); - cms.push({title: 'Toggle cloud', event: onContextMenuSelected_ToggleCloud, separator: false}); - - if (NodeType.updatableTypes.indexOf(nodeData.type) >= 0) { - cms.push({title: 'Fetch Drupal data', event: onContextMenuSelected_UpdateDrupalItem, separator: false}); + if (FeatureController.isFeatureEnabled(FeatureController.REMOVE_NODE)) { + contextMenuController.addItem('Remove node', onContextMenuSelected_RemoveNode, 3, 'visual'); } + contextMenuController.addItem('Expand subtree', onContextMenuSelected_OpenSubtree, 2, 'visual'); + contextMenuController.addItem('Toggle cloud', onContextMenuSelected_ToggleCloud, 1, 'visual'); + + contextMenuController.setSectionWeight('data', 1); + contextMenuController.setSectionWeight('visual', 2); // Extend context menu items by Plugin provided menu items - PluginManager.alter('context_menu', cms); + PluginManager.alter('context_menu', contextMenuController); - for each (var cmData:Object in cms) { - var cmi:ContextMenuItem = new ContextMenuItem(cmData.title, cmData.separator); - cmi.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, function(_event:ContextMenuEvent):void { - select(); - }); - cmi.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, cmData.event); - contextMenu.customItems.push(cmi); - } + contextMenu.customItems = contextMenuController.getContextMenus(); return contextMenu; } @@ -424,8 +434,10 @@ package com.graphmind { }); } - nodeConfigComponent.nodeLabelRTE.htmlText = view.nodeComponentView.title_label.htmlText || view.nodeComponentView.title_label.text; - nodeConfigComponent.urlField.text = nodeData.link; + if (FeatureController.isFeatureEnabled(FeatureController.NODE_INFO)) { + nodeConfigComponent.nodeLabelRTE.htmlText = view.nodeComponentView.title_label.htmlText || view.nodeComponentView.title_label.text; + nodeConfigComponent.urlField.text = nodeData.link; + } if (nodeAttributesComponent) { nodeAttributesComponent.attrKey.text = ''; @@ -475,8 +487,11 @@ package com.graphmind { output = output + '' + "\n"; } - if (nodeData.connection && nodeData.connection.target) { - output = output + '' + "\n"; + if (nodeData.connection && nodeData.connection.basePath) { + output = output + '' + "\n"; } for each (var iconName:* in nodeData.icons) { @@ -532,9 +547,9 @@ package com.graphmind { /** * Kill a node and each childs. */ - public function kill():void { + public function kill(forceKillRoot:Boolean = false):void { // Root can't be deleted. - if (!parent) return; + if (!forceKillRoot && !parent) return; // @HOOK EventCenter.notify(EventCenterEvent.NODE_IS_KILLED, this); @@ -710,9 +725,6 @@ package com.graphmind { */ public function setLink(link:String):void { nodeData.link = link; - if (canHasAnchor) { - drupalLinkIcon.visible = (link.length > 0); - } update(UP_UI); } @@ -749,6 +761,14 @@ package com.graphmind { public function isCollapsed():Boolean { return _isCollapsed; } + + + /** + * Checks if the node is collapsed directly. + */ + public function isForcedCollapsed():Boolean { + return _isForcedCollapsed; + } /** @@ -971,8 +991,6 @@ package com.graphmind { * Event callback - double click. */ protected function onDoubleClick(event:MouseEvent):void { - if (!ApplicationController.i.isEditable()) return; - view.nodeComponentView.currentState = 'edit_title'; view.nodeComponentView.title_new.text = view.nodeComponentView.title_label.text; view.nodeComponentView.title_new.setFocus(); @@ -983,8 +1001,6 @@ package com.graphmind { * Event callback - key up event on the label edit field. */ protected function onKeyUp_TitleTextField(event:KeyboardEvent):void { - if (!ApplicationController.i.isEditable()) return; - if (event.keyCode == Keyboard.ENTER) { closeLabelEditMode(); select(); @@ -999,7 +1015,6 @@ package com.graphmind { * Event callback - focus loss on the label edit field. */ protected function onFocusOut_TitleTextField(event:FocusEvent):void { - if (!ApplicationController.i.isEditable()) return; closeLabelEditMode(); } @@ -1027,8 +1042,6 @@ package com.graphmind { * Event callback - click on the add node icon. */ protected function onClick_AddSimpleNodeButton(event:MouseEvent):void { - if (!ApplicationController.i.isEditable()) return; - event.stopPropagation(); event.stopImmediatePropagation(); event.preventDefault(); @@ -1067,8 +1080,6 @@ package com.graphmind { * Event callback - click on the node. */ protected function onMouseDown(event:MouseEvent):void { - if (!ApplicationController.i.isEditable()) return; - EventCenter.notify(EventCenterEvent.NODE_PREPARE_DRAG, this); event.stopImmediatePropagation(); } @@ -1078,8 +1089,6 @@ package com.graphmind { * Event callback - mouse up on node. */ protected function onMouseUp(event:MouseEvent):void { - if (!ApplicationController.i.isEditable()) return; - if (NodeViewController.isNodeDragAndDrop) { if (view.mouseX / view.width > (1 - view.mouseY / view.height)) { NodeViewController.dragAndDrop_sourceNode.move(this); @@ -1099,8 +1108,6 @@ package com.graphmind { * Event callback - mouse move on node. */ protected function onMouseMove(event:MouseEvent):void { - if (!ApplicationController.i.isEditable()) return; - if ((!NodeViewController.isPrepairedNodeDragAndDrop) && NodeViewController.isNodeDragAndDrop) { if (view.mouseX / view.width > (1 - view.mouseY / NodeView.HEIGHT)) { view.nodeComponentView.insertLeft.visible = true; @@ -1314,10 +1321,8 @@ package com.graphmind { view.addIcon(icon); nodeData.addIcon(iconName); - if (ApplicationController.i.isEditable()) { - icon.doubleClickEnabled = true; - icon.addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick_icon); - } + icon.doubleClickEnabled = true; + icon.addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick_icon); update(UP_UI); } diff --git a/src/com/graphmind/PluginManager.as b/src/com/graphmind/PluginManager.as index d424e0f..093fbc0 100644 --- a/src/com/graphmind/PluginManager.as +++ b/src/com/graphmind/PluginManager.as @@ -13,6 +13,7 @@ package com.graphmind { */ [Frame(extraClass="plugins.TaxonomyManager")] [Frame(extraClass="plugins.Relationship")] + [Frame(extraClass="plugins.OrganicGroupsPlugin")] public class PluginManager { diff --git a/src/com/graphmind/TreeMapViewController.as b/src/com/graphmind/TreeMapViewController.as index 7285adc..d628fee 100644 --- a/src/com/graphmind/TreeMapViewController.as +++ b/src/com/graphmind/TreeMapViewController.as @@ -2,7 +2,6 @@ package com.graphmind { import com.graphmind.data.NodeDataObject; import com.graphmind.data.NodeType; - import com.graphmind.display.ConfigPanelController; import com.graphmind.display.TreeDrawer; import com.graphmind.event.EventCenter; import com.graphmind.event.EventCenterEvent; @@ -12,12 +11,6 @@ package com.graphmind { import com.graphmind.util.OSD; import com.graphmind.view.NodeView; - import components.DrupalItemLoadPanel; - import components.NodeAttributes; - import components.NodeIcons; - import components.NodeInfo; - import components.ViewLoadPanel; - import flash.display.MovieClip; import flash.events.MouseEvent; import flash.ui.ContextMenu; @@ -65,36 +58,6 @@ package com.graphmind { */ protected var dragAndDropImage:Image = new Image(); - /** - * Node configuration panel. - */ - private static var nodeConfigPanel:ConfigPanelController; - private static var nodeConfigComponent:NodeInfo; - - /** - * Node attributes panel. - */ - private static var nodeAttributesPanel:ConfigPanelController; - private static var nodeAttributesComponent:NodeAttributes; - - /** - * Node icons panel. - */ - private static var nodeIconsPanel:ConfigPanelController; - private static var nodeIconsComponent:NodeIcons; - - /** - * Load Drupal item panel. - */ - private static var loadDrupalItemPanel:ConfigPanelController; - private static var loadDrupalItemComponent:DrupalItemLoadPanel; - - /** - * Load Drupal Views list panel. - */ - private static var loadDrupalViewsPanel:ConfigPanelController; - private static var loadDrupalViewsComponent:ViewLoadPanel; - /** * Active node's attributes -> to display it as attributes. * Sensitive information not included (ie: passwords). @@ -318,8 +281,10 @@ package com.graphmind { protected function onRequestToSave(event:EventCenterEvent):void { + if (!ApplicationController.i.isEditable()) return; + var xml:String = ExportController.getFreeMindXML(rootNode); - ExportController.saveFreeMindXMLToDrupal(ConnectionController.mainConnection, xml, ApplicationController.getHostNodeID()); + ExportController.saveFreeMindXMLToDrupal(xml, ApplicationController.getHostNodeID()); } @@ -328,7 +293,7 @@ package com.graphmind { */ public function onMapSaved(event:EventCenterEvent):void { if (event.data) { - OSD.show('GraphMind data is saved.'); + OSD.show('Map is saved'); } else { OSD.show('This content has been modified by another user, changes cannot be saved.', OSD.WARNING); // @TODO prevent later savings @@ -354,7 +319,7 @@ package com.graphmind { protected function onLoadDrupalItem(event:EventCenterEvent):void { var data:DrupalItemRequestParamObject = event.data as DrupalItemRequestParamObject; ConnectionController.mainConnection.call( - data.type + '.get', + data.type + '.retrieve', function(result:Object):void { var node:NodeViewController = new NodeViewController(new NodeDataObject(result, data.type, data.conn)); data.parentNode.addChildNode(node); @@ -370,9 +335,9 @@ package com.graphmind { protected function onLoadDrupalViews(event:EventCenterEvent):void { var data:DrupalViewsRequestParamObject = event.data as DrupalViewsRequestParamObject; data.views.views.connection.call( - 'views.get', + 'views.retrieve', function(res:Object):void{onSuccess_loadDrupalViews(res, data)}, - null, + ConnectionController.defaultRequestErrorHandler, data.views.name, data.views.fields, [data.views.args], @@ -437,6 +402,16 @@ package com.graphmind { EventCenter.notify(EventCenterEvent.MAP_UPDATED); } + + + /** + * Makes the point to the center of the map. + */ + public function centerMapTo(x:Number, y:Number):void { + view.horizontalScrollPosition = x - (view.width >> 1); + view.verticalScrollPosition = y - (view.height >> 1); + } + } } diff --git a/src/com/graphmind/data/DrupalViews.as b/src/com/graphmind/data/DrupalViews.as index dfa5c8f..fd75409 100644 --- a/src/com/graphmind/data/DrupalViews.as +++ b/src/com/graphmind/data/DrupalViews.as @@ -39,7 +39,7 @@ package com.graphmind.data { public function get sourceURL():String { - return _conn.target; + return _conn.basePath + _conn.endPoint; } diff --git a/src/com/graphmind/data/NodeContextMenu.as b/src/com/graphmind/data/NodeContextMenu.as new file mode 100644 index 0000000..4692f0b --- /dev/null +++ b/src/com/graphmind/data/NodeContextMenu.as @@ -0,0 +1,29 @@ +package com.graphmind.data { + + /** + * Context menu pitem re-object. + */ + public class NodeContextMenu { + + // Weight for ordering + public var weight:Number = 0; + + // Name of the item + public var name:String; + + // Call to action + public var callback:Function; + + + /** + * Constructor. + */ + public function NodeContextMenu(name:String, callback:Function, weight:Number = 0) { + this.weight = weight; + this.name = name; + this.callback = callback; + } + + } + +} diff --git a/src/com/graphmind/data/NodeContextMenuSection.as b/src/com/graphmind/data/NodeContextMenuSection.as new file mode 100644 index 0000000..16344ce --- /dev/null +++ b/src/com/graphmind/data/NodeContextMenuSection.as @@ -0,0 +1,79 @@ +package com.graphmind.data { + + import mx.collections.ArrayCollection; + + /** + * Section class for node context menus. + * Section is a set of context menu links divided be separator lines in the menu. + */ + public class NodeContextMenuSection { + + // Unique name of the section + private var name:String; + + // Weight that sets the position + public var weight:Number = 0; + + // List of context menus + public var contextMenus:Array = []; + + // Static dicitionary to lookup for sections by their name + public static var sectionsNameIndex:Object = {}; + + // List of all sections + public static var sections:Array = []; + + + /** + * Constructor. + * Don't call it directlty - use NodeContextMenuSection.getSection(NAME) instead. + */ + [Deprecated] + public function NodeContextMenuSection(name:String) { + this.name = name; + sections.push(this); + } + + + /** + * Sections instance getter. + */ + public static function getSection(name:String):NodeContextMenuSection { + if (!sectionsNameIndex.hasOwnProperty(name)) { + sectionsNameIndex[name] = new NodeContextMenuSection(name); + } + return sectionsNameIndex[name]; + } + + + /** + * Add new context menu item. + */ + public function addContextMenu(item:NodeContextMenu):void { + var idxToInsert:uint = 0; + while (idxToInsert < contextMenus.length && (contextMenus[idxToInsert] as NodeContextMenu).weight < item.weight) { + idxToInsert++; + } + + for (var i:uint = contextMenus.length; i > idxToInsert; i--) { + contextMenus[i] = contextMenus[i - 1]; + } + + contextMenus[idxToInsert] = item; + } + + + /** + * Sort sections by their weight. + */ + public static function sortSection():void { + sections.sort(function(a:NodeContextMenuSection, b:NodeContextMenuSection):int{ + if (a.weight < b.weight) return -1; + if (a.weight > b.weight) return 1; + return 0; + }); + } + + } + +} diff --git a/src/com/graphmind/data/NodeDataObject.as b/src/com/graphmind/data/NodeDataObject.as index 11421ff..bc17589 100644 --- a/src/com/graphmind/data/NodeDataObject.as +++ b/src/com/graphmind/data/NodeDataObject.as @@ -122,21 +122,21 @@ package com.graphmind.data { var newLink:String = ''; - if (connection && connection.target && drupalID) { - var url:String = connection.target.toString().replace(/services\/amfphp/gi, ''); + if (connection && connection.basePath && drupalID) { + var url:String = connection.basePath; switch (type) { case NodeType.NODE: - newLink = url + '/node/' + drupalID; + newLink = url + 'node/' + drupalID; break; case NodeType.USER: - newLink = url + '/user/' + drupalID; + newLink = url + 'user/' + drupalID; case NodeType.COMMENT: if (drupalData.cid && drupalData.comments_nid) { - newLink = url + '/node/' + drupalData.comments_nid + '#comment-' + drupalData.cid; + newLink = url + 'node/' + drupalData.comments_nid + '#comment-' + drupalData.cid; } break; case NodeType.TERM: - newLink = url + '/taxonomy/term/' + drupalID; + newLink = url + 'taxonomy/term/' + drupalID; } } diff --git a/src/com/graphmind/display/TreeDrawer.as b/src/com/graphmind/display/TreeDrawer.as index cd8f11f..b8ee1a0 100644 --- a/src/com/graphmind/display/TreeDrawer.as +++ b/src/com/graphmind/display/TreeDrawer.as @@ -149,7 +149,7 @@ package com.graphmind.display { return height; } - + } } diff --git a/src/com/graphmind/event/EventCenterEvent.as b/src/com/graphmind/event/EventCenterEvent.as index 770f032..36fc2ee 100644 --- a/src/com/graphmind/event/EventCenterEvent.as +++ b/src/com/graphmind/event/EventCenterEvent.as @@ -25,6 +25,7 @@ package com.graphmind.event { public static var MAP_UPDATED:String = 'mapUpdated'; public static var MAP_SAVED:String = 'mapSaved'; + public static var MAP_SAVED_SILENTLY:String = 'mapSavedSilently'; public static var MAP_SCALE_CHANGED:String = 'mapScaleChanged'; public static var MAP_TREE_IS_COMPLETE:String = 'mapTreeIsComplete'; public static var MAP_LOCK:String = 'mapLock'; @@ -40,6 +41,8 @@ package com.graphmind.event { public static var ICON_SELECTED:String = 'iconSelected'; public static var ALTER_SETTINGS_PANEL:String = 'alterSettingsPanel'; + + public static var FEATURES_CHANGED:String = 'featuresChanged'; /** * Data. diff --git a/src/com/graphmind/util/OSD.as b/src/com/graphmind/util/OSD.as index a85ac1e..67a622f 100644 --- a/src/com/graphmind/util/OSD.as +++ b/src/com/graphmind/util/OSD.as @@ -14,29 +14,25 @@ package com.graphmind.util { private static var container:UIComponent; public static var width:int; public static var padding:int = 12; - - + + public static function init(container:UIComponent, width:int = 300):void { OSD.container = container; OSD.width = width; } - public static function show(text:String, level:String = 'info', sticky:Boolean = false):void { - var msg:UIComponent = getMessagePanel(text, level, sticky); + public static function show(text:String, level:String = 'info', sticky:Boolean = false, name:String = null):void { + var msg:UIComponent = getMessagePanel(text, level, sticky, name); OSD.container.addChild(msg); + + } - private static function getMessagePanel(text:String, level:String, sticky:Boolean = false):Canvas { + private static function getMessagePanel(text:String, level:String, sticky:Boolean = false, name:String = null):Canvas { var osd_msg:OSDMessage; - if (level == OSD.ERROR || sticky) { - osd_msg = new OSDStaticMessage(text, level); - } else { - osd_msg = new OSDMessage(text, level); - osd_msg.countdown(); - } - + osd_msg = new OSDMessage(text, level, (level == OSD.ERROR || sticky), name); return osd_msg; } @@ -50,6 +46,10 @@ package com.graphmind.util { } } + public static function removeNamedMessages(name:String):void { + OSDMessage.removeNamedMessages(name); + } + } } diff --git a/src/com/graphmind/util/OSDMessage.as b/src/com/graphmind/util/OSDMessage.as index 97ded3b..aa89ace 100644 --- a/src/com/graphmind/util/OSDMessage.as +++ b/src/com/graphmind/util/OSDMessage.as @@ -1,16 +1,31 @@ package com.graphmind.util { import flash.events.Event; + import flash.events.MouseEvent; import flash.utils.setTimeout; + import mx.collections.ArrayCollection; import mx.containers.Canvas; + import mx.controls.Image; import mx.controls.Text; import mx.events.FlexEvent; public class OSDMessage extends Canvas { - - public function OSDMessage(text:String, level:String) { + + [Embed(source='assets/images/cross.gif')] + private var _crossIcon:Class; + + /** + * Static store of the named messages. + * With names messages can be identified and removed through code. + */ + public static var namedMessages:Object = {}; + + private var _name:String; + + + public function OSDMessage(text:String, level:String, sticky:Boolean = false, name:String = null) { super(); var tx:Text = new Text(); @@ -23,31 +38,79 @@ package com.graphmind.util { height = tx.measuredHeight + 2 * OSD.padding; }); + addEventListener(MouseEvent.CLICK, function(e:Event):void{kill();}); + tx.setStyle('color', '#FFFFFF'); tx.width = OSD.width - 2 * OSD.padding; tx.x = tx.y = OSD.padding; tx.text = text; addChild(tx); + + if (sticky) { + var icon:Image = new Image(); + icon.addEventListener(MouseEvent.CLICK, function(event:MouseEvent):void{kill();}); + icon.source = new _crossIcon; + icon.x = OSD.width - 16; + icon.y = 4; + addChild(icon); + } + + if (!sticky) { + countdown(); + } + + if (name) { + this._name = name; + if (!namedMessages.hasOwnProperty(name)) { + namedMessages[name] = new ArrayCollection(); + } + (namedMessages[name] as ArrayCollection).addItem(this); + } } + /** + * On enter frame callback. + */ public function onEnterFrame(event:Event):void { alpha -= 0.05; if (alpha <= 0.1) { - removeEventListener(Event.ENTER_FRAME, onEnterFrame); kill(); } } + /** + * Scedule the item to disappear. + */ public function countdown():void { setTimeout(function():void{addEventListener(Event.ENTER_FRAME, onEnterFrame);}, 3000); } + /** + * Removes the object. + */ protected function kill():void { - parent.removeChild(this); + if (_name) { + (namedMessages[_name] as ArrayCollection).removeItemAt((namedMessages[_name] as ArrayCollection).getItemIndex(this)); + } + removeEventListener(Event.ENTER_FRAME, onEnterFrame); + parent.removeChild(this); } + + + /** + * Remvoves all the messages with a specific tag. + */ + public static function removeNamedMessages(name:String):void { + if (namedMessages.hasOwnProperty(name)) { + while ((namedMessages[name] as ArrayCollection).length > 0) { + var osdm:OSDMessage = (namedMessages[name] as ArrayCollection).getItemAt(0) as OSDMessage; + osdm.kill(); + } + } + } } diff --git a/src/com/graphmind/util/OSDStaticMessage.as b/src/com/graphmind/util/OSDStaticMessage.as deleted file mode 100644 index 6d46801..0000000 --- a/src/com/graphmind/util/OSDStaticMessage.as +++ /dev/null @@ -1,33 +0,0 @@ -package com.graphmind.util { - - import flash.events.MouseEvent; - - import mx.controls.Image; - - - public class OSDStaticMessage extends OSDMessage { - - - [Embed(source='assets/images/cross.gif')] - private var _crossIcon:Class; - - - public function OSDStaticMessage(text:String, level:String) { - super(text, level); - - var icon:Image = new Image(); - icon.addEventListener(MouseEvent.CLICK, function(event:MouseEvent):void{kill();}); - icon.source = new _crossIcon; - icon.x = OSD.width - 16; - icon.y = 4; - addChild(icon); - } - - - public override function countdown():void { - // Do nothing insted of counting back. Do not delete. - } - - } - -} diff --git a/src/com/graphmind/view/ConfigPanelView.mxml b/src/com/graphmind/view/ConfigPanelView.mxml index c227c8f..e0b2259 100644 --- a/src/com/graphmind/view/ConfigPanelView.mxml +++ b/src/com/graphmind/view/ConfigPanelView.mxml @@ -1,7 +1,7 @@ + backgroundAlpha="0.8"> @@ -13,6 +13,6 @@ - + diff --git a/src/com/graphmind/view/NodeView.as b/src/com/graphmind/view/NodeView.as index 98ac381..fe6314a 100644 --- a/src/com/graphmind/view/NodeView.as +++ b/src/com/graphmind/view/NodeView.as @@ -100,10 +100,6 @@ package com.graphmind.view { nodeComponentView.height = HEIGHT; var titleExtraWidth:int = _getTitleExtraWidth(); - for (var idx:* in icons) { - Image(icons[idx]).x = titleExtraWidth + ICON_WIDTH * idx + WIDTH_DEFAULT - 4; - Image(icons[idx]).y = (HEIGHT - 16) * 0.5; - } var leftOffset:int = _getIconsExtraWidth() + titleExtraWidth; var actionIconOffset:uint = actionIcons.length * 18; @@ -120,8 +116,13 @@ package com.graphmind.view { this.nodeComponentView.insertLeft.x = ICON_INSERT_LEFT_DEFAULT_X + leftOffset + actionIconOffset; this.nodeComponentView.title_label.width = TITLE_DEFAULT_WIDTH + titleExtraWidth; + for (var idx:* in icons) { + Image(icons[idx]).x = titleExtraWidth + WIDTH_DEFAULT + _getActionIconsExtraWidth() - 4 + ICON_WIDTH * idx; + Image(icons[idx]).y = (HEIGHT - 16) * 0.5; + } + for (var i:* in actionIcons) { - (actionIcons[i] as Image).x = ACTION_ICONS_DEFAULT_X + leftOffset + i * 18; + (actionIcons[i] as Image).x = titleExtraWidth + WIDTH_DEFAULT - 4 + i * ICON_WIDTH; (actionIcons[i] as Image).y = (HEIGHT - 16) * .5; } @@ -150,6 +151,11 @@ package com.graphmind.view { } + protected function _getActionIconsExtraWidth():int { + return actionIcons.length * ICON_WIDTH; + } + + /** * Add icon. */ @@ -179,6 +185,7 @@ package com.graphmind.view { actionIcons.push(actionIcon); nodeComponentView.addChild(actionIcon); refreshGraphics(); + actionIcon.visible = false; } } diff --git a/src/components/ConnectionSettingsComponent.mxml b/src/components/ConnectionSettingsComponent.mxml index 44b03ff..7f340db 100644 --- a/src/components/ConnectionSettingsComponent.mxml +++ b/src/components/ConnectionSettingsComponent.mxml @@ -17,22 +17,27 @@ - - + + + + + + + - + - + + tabIndex="14" /> diff --git a/src/components/NodeIcons.mxml b/src/components/NodeIcons.mxml index a81a71a..a0a598a 100644 --- a/src/components/NodeIcons.mxml +++ b/src/components/NodeIcons.mxml @@ -37,4 +37,6 @@ + + diff --git a/src/plugins/OrganicGroupsPlugin.as b/src/plugins/OrganicGroupsPlugin.as new file mode 100644 index 0000000..af23278 --- /dev/null +++ b/src/plugins/OrganicGroupsPlugin.as @@ -0,0 +1,135 @@ +package plugins { + + import com.graphmind.ApplicationController; + import com.graphmind.ConnectionController; + import com.graphmind.FeatureController; + import com.graphmind.NodeContextMenuController; + import com.graphmind.NodeViewController; + import com.graphmind.data.NodeDataObject; + import com.graphmind.data.NodeType; + import com.graphmind.display.ConfigPanelController; + import com.graphmind.event.EventCenter; + import com.graphmind.event.EventCenterEvent; + import com.graphmind.util.OSD; + + import flash.events.ContextMenuEvent; + import flash.events.MouseEvent; + import flash.external.ExternalInterface; + + import plugins.organicgroupsplugin.LoadDrupalNodeComponent; + + + public class OrganicGroupsPlugin { + + /** + * Separate control panel for loading nodes. + */ + private static var nodeLoadPanel:ConfigPanelController; + private static var nodeLoadComponent:LoadDrupalNodeComponent; + + /** + * Indicates if the feature was enabled but disabled by this plugin. + */ + private static var nodeLoadFeatureWasEnabled:Boolean = false; + + + /** + * Implementation of the plugin init function. + */ + public static function init():void { + EventCenter.subscribe(EventCenterEvent.FEATURES_CHANGED, onFeaturesChanged); + + nodeLoadPanel = new ConfigPanelController('Load Drupal Item'); + nodeLoadComponent = new LoadDrupalNodeComponent(); + nodeLoadPanel.addItem(nodeLoadComponent); + nodeLoadComponent.submitButton.addEventListener(MouseEvent.CLICK, onClick_drupalItemLoadSubmit); + + if (ExternalInterface.available) { + ExternalInterface.addCallback('sendOGNodeLoadRequestToFlex', onSendOGNodeLoadRequestToFlex); + } + } + + + /** + * Callback - when application's feature set is changed. + */ + private static function onFeaturesChanged(event:EventCenterEvent):void { + if (FeatureController.isFeatureEnabled(FeatureController.LOAD_DRUPAL_NODE)) { + FeatureController.removeFeature(FeatureController.LOAD_DRUPAL_NODE); + nodeLoadFeatureWasEnabled = true; + } + } + + + /** + * Alters a node's context menu. + */ + public static function alter_context_menu(contextMenuController:NodeContextMenuController):void { + if (!nodeLoadFeatureWasEnabled) return; + + contextMenuController.addItem('Load Drupal item', onContextMenuSelect_loadDruplaItem, 0, 'data'); + } + + + /** + * Callback - when the node-load context menu item is selected. + */ + private static function onContextMenuSelect_loadDruplaItem(e:ContextMenuEvent):void { + nodeLoadPanel.show(); + } + + + /** + * Callback - when the node load form is submitted. + */ + private static function onClick_drupalItemLoadSubmit(e:MouseEvent):void { + ConnectionController.mainConnection.call( + 'graphmindOG.getNode', + onNodeGetSuccess, + onNodeGetFail, + parseInt(nodeLoadComponent.nodeIDField.text), + ApplicationController.getHostNodeID() + ); + } + + + /** + * Node load success callback. + */ + private static function onNodeGetSuccess(result:Object):void { + nodeLoadPanel.hide(); + + if (!result) { + onNodeGetFail(result); + return; + } + + var node:NodeViewController = new NodeViewController(new NodeDataObject(result, NodeType.NODE, ConnectionController.mainConnection)); + NodeViewController.activeNode.addChildNode(node); + } + + + /** + * Node load failure callback. + */ + private static function onNodeGetFail(result:Object):void { + OSD.show("Sorry, you don\'t have permission to load the node. Maybe it\'s not in the same organic group. \nCheck the node ID again.", OSD.WARNING); + } + + + /** + * Callback from JS - request for a node can be sent. + */ + private static function onSendOGNodeLoadRequestToFlex(nid:uint):void { + ConnectionController.mainConnection.call( + 'graphmindOG.getNode', + onNodeGetSuccess, + onNodeGetFail, + nid, + ApplicationController.getHostNodeID() + ); + } + + } + +} diff --git a/src/plugins/Relationship.as b/src/plugins/Relationship.as index 7a8ea06..616b634 100644 --- a/src/plugins/Relationship.as +++ b/src/plugins/Relationship.as @@ -1,8 +1,14 @@ package plugins { + import com.graphmind.ApplicationController; import com.graphmind.ConnectionController; + import com.graphmind.ExportController; + import com.graphmind.MainMenuController; + import com.graphmind.NodeContextMenuController; import com.graphmind.NodeViewController; import com.graphmind.TreeMapViewController; + import com.graphmind.data.NodeContextMenu; + import com.graphmind.data.NodeContextMenuSection; import com.graphmind.data.NodeDataObject; import com.graphmind.data.NodeType; import com.graphmind.display.ConfigPanelController; @@ -15,12 +21,15 @@ package plugins { import com.graphmind.view.NodeActionIcon; import flash.events.ContextMenuEvent; + import flash.events.Event; import flash.events.MouseEvent; import flash.external.ExternalInterface; + import flash.utils.clearTimeout; import flash.utils.setTimeout; import mx.collections.ArrayCollection; import mx.core.Application; + import mx.events.FlexEvent; import plugins.relationship.RelationshipSettingsPanel; @@ -32,6 +41,11 @@ package plugins { */ private static var DEFAULT_RELATIONSHIP:String = 'default'; + /** + * Unique string for the refresh update warning OSD message. + */ + private static var UPDATE_WARNING_OSD:String = 'updateWarningOSD'; + /** * Image asset for the relationship action icon. */ @@ -43,6 +57,12 @@ package plugins { */ private static var relationshipActionIcon:NodeActionIcon; + /** + * Refresh icon. + */ + [Embed(source="assets/images/arrow_refresh.png")] + private static var refreshImage:Class; + /** * Add icon. */ @@ -98,21 +118,39 @@ package plugins { * Frequency related vars for the update period. */ [Bindable] - public static var frequencies:Array = ['5 seconds', '15 seconds', '1 minute', '5 minutes']; - private static var frequenciesSeconds:Array = [5, 15, 60, 300]; - private static var frequency:uint = frequenciesSeconds[0]; + public static var updateFrequencies:Array = ['5 seconds', '15 seconds', '1 minute', '5 minutes']; + private static var updateFrequenciesSeconds:Array = [5, 15, 60, 300]; + private static var updateFrequency:uint = updateFrequenciesSeconds[0]; + + /** + * Update frequency - 10 seconds. + */ + [Bindable] + public static var saveFrequencies:Array = ['3 seconds', '5 seconds', '10 seconds', '30 seconds']; + public static var saveFrequenciesSeconds:Array = [3000, 5000, 10000, 30000]; + public static var saveFrequency:uint = saveFrequenciesSeconds[1]; + private static var saveTimeout:uint; /** * User color storage. */ private static var userColors:Object = {}; + private static var focusNodeBackupInfo:Array = []; + + /** + * On hard refresh store the nodes have to be collapsed. + */ + private static var collapseStateCache:Array = []; + /** * Implemrentation of init(). */ public static function init():void { Log.info('Relationship plugin is live.'); + + refreshFlag = true; if (Application.application.parameters.hasOwnProperty('graphmindRelationshipDepth')) { depth = Application.application.parameters.graphmindRelationshipDepth; @@ -122,6 +160,7 @@ package plugins { NodeViewController.canHasNormalChild = false; NodeViewController.canHasAnchor = false; NodeViewController.canHasAttributes = false; + NodeViewController.canHasTitleEditing = false; EventCenter.subscribe(EventCenterEvent.NODE_DID_ADDED_TO_PARENT, onNodeDidAddedToParent); EventCenter.subscribe(EventCenterEvent.NODE_IS_KILLED, onNodeIsKilled); @@ -221,20 +260,18 @@ package plugins { /** * Altering a node's context menu. */ - public static function alter_context_menu(cm:Array):void { + public static function alter_context_menu(contextMenuController:NodeContextMenuController):void { // Deleteing the first item: creating normal node. - for (var idx:* in cm) { - if (cm[idx]['title'] == 'Add node') { - delete cm[idx]; + var section:NodeContextMenuSection = NodeContextMenuSection.getSection('default'); + for (var idx:* in section.contextMenus) { + if ((section.contextMenus[idx] as NodeContextMenu).name == 'Add node') { + delete section.contextMenus[idx]; break; } } - cm.push({title: 'Synchronize relationships', event: onMenuItemSelect_RefreshSubtree, separator: true}); - cm.push({title: 'Edit Details', event: onMouseClick_editNodeActionIcon, separator: false}); - cm.push({title: 'View Details', event: onMouseClick_viewNodeActionIcon, separator: false}); - cm.push({title: 'Load subtree', event: onMouseClick_relationshipActionIcon, separator: false}); - cm.push({title: 'Create node', event: onMouseClick_addDrupalNodeIcon, separator: false}); + contextMenuController.addItem('View details', onMouseClick_viewNodeActionIcon, 2, 'data'); + contextMenuController.addItem('Create node', onMouseClick_addDrupalNodeIcon, 1, 'data'); } @@ -264,52 +301,48 @@ package plugins { } - /** - * Event callback when the relationship action icon is clicked. - */ - private static function onMouseClick_relationshipActionIcon(e:ContextMenuEvent):void { - var node:NodeViewController = NodeViewController.activeNode; - ConnectionController.mainConnection.call( - 'graphmindRelationship.getSubtree', - function (result:Object):void { - onSuccess_loadRelationshipSubtree(node, result); - }, - ConnectionController.defaultRequestErrorHandler, - node.nodeData.drupalID, - depth - ); - } - - - /** - * Event callback when a node's relationships are arrived. - */ - private static function onSuccess_loadRelationshipSubtree(node:NodeViewController, result:Object):void { - trace('success of rel subtree'); - addSubtree(node, result as Array); - } - - /** * Adds a list of items to a node recursively. * Returns all the node IDs that are connected. */ - private static function addSubtree(parent:NodeViewController, childs:Array):Array { - var connectedIDs:Array = []; - for (var idx:* in childs) { - connectedIDs.push(childs[idx]['node']['nid']); - // Prevents recursion. - if (!loopCheck(parent, childs[idx]['node']['nid'])) { - var child:NodeViewController = getExistingNodeOfParent(parent, childs[idx]['node']['nid']); - if (!child) { - child = new NodeViewController(new NodeDataObject(childs[idx]['node'], NodeType.NODE, ConnectionController.mainConnection)); - parent.addChildNode(child); - } - addSubtree(child, childs[idx]['relationships']); + private static function addSubtree(parent:NodeViewController, children:Array, mapDataCache:Object):void { + var idx:*; + var cachedOrder:Array = []; + var notCached:Array = []; + + for each (var icon:String in mapDataCache['icons']) { + parent.addIcon(ApplicationController.getIconPath() + icon + '.png'); + } + if (mapDataCache['collapsed']) { + collapseStateCache.push(parent); + } + if (mapDataCache['cloud']) { + parent.toggleCloud(); + } + + for (idx in children) { + var node:NodeViewController = new NodeViewController(new NodeDataObject(children[idx].node, NodeType.NODE, ConnectionController.mainConnection)); + parent.addChildNode(node); + var nodeCachedData:Object; + if (mapDataCache && mapDataCache.hasOwnProperty('children') && mapDataCache['children'].hasOwnProperty(node.nodeData.drupalID)) { + nodeCachedData = mapDataCache['children'][node.nodeData.drupalID] as Object; + nodeCachedData['node'] = node; + cachedOrder.push(nodeCachedData); + } else { + // Node is not in the cache + notCached.push(node); } + addSubtree(node, children[idx].children, nodeCachedData ? nodeCachedData : {}); } - return connectedIDs; + cachedOrder.sort(function(a:Object, b:Object):int{ + return (a['position'] < b['position']) ? -1 : 1; + }); + + // Fix order from cache + parent.getChildNodeAll().removeAll(); + for (idx in cachedOrder) {parent.getChildNodeAll().addItem(cachedOrder[idx]['node']);} + for (idx in notCached) {parent.getChildNodeAll().addItem(notCached[idx]);} } @@ -329,60 +362,66 @@ package plugins { } - /** - * Event callback - clicking on the refresh subtree context menu item. - */ - private static function onMenuItemSelect_RefreshSubtree(event:ContextMenuEvent):void { - refreshSubtree(NodeViewController.activeNode); - } - - - /** - * Refresh a subtree. - */ - public static function refreshSubtree(node:NodeViewController):void { - refreshRequestPending = false; - - EventCenter.notify(EventCenterEvent.MAP_LOCK); - GlobalLock.lock(REFRESH_LOCK); - - ConnectionController.mainConnection.call( - 'graphmindRelationship.getSubtree', - function (result:Object):void { - onSuccess_refreshSubtreeRequest(node, result); - }, - function(error:Object):void { - GlobalLock.unlock(REFRESH_LOCK); - ConnectionController.defaultRequestErrorHandler(error); - }, - node.nodeData.drupalID, - 1 - ); - } - - /** * Event callback when a subtree refresh info is arrived. */ - private static function onSuccess_refreshSubtreeRequest(parent:NodeViewController, result:Object):void { + private static function onSuccess_refreshSubtreeRequest(result:Object):void { refreshFlag = true; - var connectedIDs:Array = addSubtree(parent, result as Array); - var childs:ArrayCollection = parent.getChildNodeAll(); - for (var idx:* in childs) { - var child:NodeViewController = childs[idx] as NodeViewController; - if (child.nodeData.type != NodeType.NODE || connectedIDs.indexOf(child.nodeData.drupalID.toString()) == -1) { - // It's a non existing relationship. - child.kill(); - } else { - if (child.hasChild()) { - refreshSubtree(child); + focusNodeBackupInfo = []; + var parent:NodeViewController = NodeViewController.activeNode; + while (parent) { + focusNodeBackupInfo.push(parent.nodeData.drupalID); + parent = parent.parent; + } + + var mapDataCache:Object = mapDataSnapshot(TreeMapViewController.rootNode); + collapseStateCache = []; + + // Remove old tree + TreeMapViewController.rootNode.kill(true); + + var node:NodeViewController = new NodeViewController(new NodeDataObject(result.node, NodeType.NODE, ConnectionController.mainConnection)); + TreeMapViewController.rootNode = node; + addSubtree(node, result.children, mapDataCache); + + for each (var nodeToCollapse:NodeViewController in collapseStateCache) { + nodeToCollapse.collapse(); + } + + var currentNID:uint = focusNodeBackupInfo.pop(); + var currentNode:NodeViewController = + currentNID == TreeMapViewController.rootNode.nodeData.drupalID ? + TreeMapViewController.rootNode : + null; + while (focusNodeBackupInfo.length > 0 && currentNode) { + currentNID = focusNodeBackupInfo.pop(); + var foundChild:Boolean = false; + for each (var child:NodeViewController in currentNode.getChildNodeAll()) { + if (child.nodeData.drupalID == currentNID) { + currentNode = child; + foundChild = true; + break; } } + if (!foundChild) { + break; + } + } + if (currentNode) { + currentNode.view.addEventListener(FlexEvent.CREATION_COMPLETE, function(e:Event):void{ + setTimeout(function():void { + currentNode.select(); + ApplicationController.i.treeMapViewController.centerMapTo(currentNode.view.x, currentNode.view.y); + }, 100); + }); } refreshFlag = false; + OSD.removeNamedMessages(UPDATE_WARNING_OSD); + + refreshRequestPending = false; GlobalLock.unlock(REFRESH_LOCK); if (!GlobalLock.isLocked(REFRESH_LOCK)) { EventCenter.notify(EventCenterEvent.MAP_UNLOCK); @@ -407,7 +446,6 @@ package plugins { * Update check request - called periodically. */ private static function checkForChangesWithLoop():void { - trace('LOOP CHECK'); setTimeout(function():void{ if (refreshRequestPending) { checkForChangesWithLoop(); @@ -417,7 +455,7 @@ package plugins { checkForChangesWithLoop(); }); } - }, frequency * 1000); + }, updateFrequency * 1000); } @@ -427,12 +465,15 @@ package plugins { private static function checkForChangesWithCallback(callback:Function):void { if (refreshRequestPending) return; var tree:Object = {}; - tree[TreeMapViewController.rootNode.nodeData.drupalID] = collectSubtreeIDs(TreeMapViewController.rootNode, []); + tree['nid'] = TreeMapViewController.rootNode.nodeData.drupalID; + tree['node'] = {title: TreeMapViewController.rootNode.nodeData.drupalData.title}; + tree['children'] = collectSubtreeIDs(TreeMapViewController.rootNode, depth); ConnectionController.mainConnection.call( 'graphmindRelationship.checkUpdate', callback, ConnectionController.defaultRequestErrorHandler, - tree + tree, + depth ); } @@ -443,7 +484,8 @@ package plugins { private static function onSuccess_refreshInfoArrived(result:Object):void { if (!result) { // Structure is changed at the backend. - OSD.show('Structure is changed. Please refresh your map in the \'Relationships\' panel.', OSD.WARNING, true); + OSD.removeNamedMessages(UPDATE_WARNING_OSD); + OSD.show('The map has new data - click Refresh Map to refresh.', OSD.WARNING, true, UPDATE_WARNING_OSD); refreshRequestPending = true; } } @@ -452,23 +494,19 @@ package plugins { /** * Creates a structured ID array object from the tree as a parameter. */ - private static function collectSubtreeIDs(node:NodeViewController, cycleCheckArray:Array):Array { - var ids:Array = []; - var childs:ArrayCollection = node.getChildNodeAll(); - for (var idx:* in childs) { - var child:NodeViewController = childs[idx] as NodeViewController; - if (child.nodeData.type == NodeType.NODE && child.nodeData.drupalID) { - var subtree:Object = {}; - if (cycleCheckArray.indexOf(child.nodeData.drupalID) == -1) { - cycleCheckArray.push(child.nodeData.drupalID); - subtree[child.nodeData.drupalID] = collectSubtreeIDs(child, cycleCheckArray); - } else { - subtree[child.nodeData.drupalID] = []; - } - ids.push(subtree); + private static function collectSubtreeIDs(node:NodeViewController, depth:uint):Array { + var children:ArrayCollection = node.getChildNodeAll(); + var data:Array = []; + if (depth > 0) { + for (var idx:* in children) { + var child:Object = {}; + child['nid'] = (children[idx] as NodeViewController).nodeData.drupalID; + child['node'] = {title: (children[idx] as NodeViewController).nodeData.drupalData.title}; + child['children'] = collectSubtreeIDs(children[idx], depth - 1); + data.push(child); } } - return ids; + return data; } @@ -476,7 +514,7 @@ package plugins { * Set the update check frequency. */ public static function setUpdateCheckFrequency(idx:uint):void { - frequency = frequenciesSeconds[idx]; + updateFrequency = updateFrequenciesSeconds[idx]; } @@ -486,6 +524,10 @@ package plugins { private static function onMapTreeIsComplete(event:EventCenterEvent):void { // Start checking the updates. checkForChangesWithLoop(); + startAutoSave(); + refreshFlag = false; + + MainMenuController.createIconMenuItem(refreshImage, 'Refresh map', onMenuClick_RefreshMap); } @@ -545,18 +587,6 @@ package plugins { } - /** - * Event callback - click on the view-node-page action icon. - */ - private static function onMouseClick_editNodeActionIcon(e:ContextMenuEvent):void { - if (!ExternalInterface.available) { - OSD.show('Popup window is not available.'); - } - var node:NodeViewController = NodeViewController.activeNode; - ExternalInterface.call('GraphmindRelationship.openPopupWindow', node.nodeData.link + '/edit'); - } - - /** * Event callback - click on the view-node-edit-page action icon. */ @@ -606,6 +636,70 @@ package plugins { } } + + private static function onMenuClick_RefreshMap(e:MouseEvent):void { + hardRefreshTree(); + } + + + private static function hardRefreshTree():void { + + EventCenter.notify(EventCenterEvent.MAP_LOCK); + ConnectionController.mainConnection.call( + 'graphmindRelationship.getSubtree', + function(e:Object):void{ + GlobalLock.lock(REFRESH_LOCK); + onSuccess_refreshSubtreeRequest(e); + }, + function(e:Object):void{ + GlobalLock.unlock(REFRESH_LOCK); + EventCenter.notify(EventCenterEvent.MAP_UNLOCK); + ConnectionController.defaultRequestErrorHandler(e); + }, + TreeMapViewController.rootNode.nodeData.drupalID, + depth + ); + } + + + /** + * Begin autosaving. + */ + private static function startAutoSave():void { + EventCenter.subscribe(EventCenterEvent.MAP_SAVED_SILENTLY, function(e:Event):void{ + autoSave(); + }); + autoSave(); + } + + + /** + * Autosave. + */ + private static function autoSave():void { + clearTimeout(saveTimeout); + saveTimeout = setTimeout(function():void{ + var xml:String = ExportController.getFreeMindXML(TreeMapViewController.rootNode); + ExportController.saveFreeMindXMLToDrupalSilent(xml, ApplicationController.getHostNodeID()); + }, saveFrequency); + } + + + /** + * Cache a snapshot of the tree to preserve map info, such as cloud, collapse state, icons. + */ + private static function mapDataSnapshot(node:NodeViewController):Object { + var data:Object = {}; + data['cloud'] = node.hasCloud(); + data['collapsed'] = node.isForcedCollapsed(); + data['icons'] = node.nodeData.icons; + data['children'] = {}; + data['position'] = node.parent ? node.parent.getChildNodeAll().getItemIndex(node) : 0; + for each (var child:NodeViewController in node.getChildNodeAll()) { + data['children'][child.nodeData.drupalID] = mapDataSnapshot(child); + } + return data; + } } } diff --git a/src/plugins/TaxonomyManager.as b/src/plugins/TaxonomyManager.as index a9eb05b..64ec34c 100644 --- a/src/plugins/TaxonomyManager.as +++ b/src/plugins/TaxonomyManager.as @@ -1,6 +1,7 @@ package plugins { import com.graphmind.ConnectionController; + import com.graphmind.NodeContextMenuController; import com.graphmind.NodeViewController; import com.graphmind.data.NodeDataObject; import com.graphmind.data.NodeType; @@ -73,8 +74,8 @@ package plugins { /** * Implementation of hook_node_context_menu_alter(). */ - public static function alter_context_menu(cm:Array):void { - cm.push({title: 'Load taxonomy', event: TaxonomyManager.loadFullTaxonomyTree, separator: true}); + public static function alter_context_menu(contextMenuController:NodeContextMenuController):void { + contextMenuController.addItem('Load taxonomy', loadFullTaxonomyTree); } diff --git a/src/plugins/organicgroupsplugin/LoadDrupalNodeComponent.mxml b/src/plugins/organicgroupsplugin/LoadDrupalNodeComponent.mxml new file mode 100644 index 0000000..495434d --- /dev/null +++ b/src/plugins/organicgroupsplugin/LoadDrupalNodeComponent.mxml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/plugins/relationship/RelationshipSettingsPanel.mxml b/src/plugins/relationship/RelationshipSettingsPanel.mxml index 22b26db..0e01b19 100644 --- a/src/plugins/relationship/RelationshipSettingsPanel.mxml +++ b/src/plugins/relationship/RelationshipSettingsPanel.mxml @@ -21,7 +21,12 @@ - + + + + + +