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";
+ return '' + "\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 @@
-
+
+
+
+
+
+