Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14706088
D9031.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
38 KB
Referenced Files
None
Subscribers
None
D9031.diff
View Options
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -52,6 +52,7 @@
'rsrc/css/application/conpherence/widget-pane.css' => 'bf275a6c',
'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4',
'rsrc/css/application/countdown/timer.css' => '86b7b0a0',
+ 'rsrc/css/application/dashboard/dashboard.css' => '5b532b7b',
'rsrc/css/application/diff/inline-comment-summary.css' => '8cfd34e8',
'rsrc/css/application/differential/add-comment.css' => 'c478bcaa',
'rsrc/css/application/differential/changeset-view.css' => '1570a1ff',
@@ -358,6 +359,7 @@
'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90',
'rsrc/js/application/countdown/timer.js' => '889c96f3',
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '4398eabb',
+ 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'aa3f313b',
'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'f2441746',
'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => '533a187b',
'rsrc/js/application/differential/behavior-comment-jump.js' => '71755c79',
@@ -552,6 +554,7 @@
'javelin-behavior-countdown-timer' => '889c96f3',
'javelin-behavior-dark-console' => 'e9fdb5e5',
'javelin-behavior-dashboard-async-panel' => '4398eabb',
+ 'javelin-behavior-dashboard-move-panels' => 'aa3f313b',
'javelin-behavior-device' => '03d6ed07',
'javelin-behavior-differential-add-reviewers-and-ccs' => '533a187b',
'javelin-behavior-differential-comment-jump' => '71755c79',
@@ -698,6 +701,7 @@
'phabricator-core-css' => '40151074',
'phabricator-countdown-css' => '86b7b0a0',
'phabricator-crumbs-view-css' => '6a23399c',
+ 'phabricator-dashboard-css' => '5b532b7b',
'phabricator-drag-and-drop-file-upload' => 'ae6abfba',
'phabricator-draggable-list' => '1681c4d4',
'phabricator-fatal-config-template-css' => '25d446d6',
@@ -1266,6 +1270,18 @@
2 => 'javelin-util',
3 => 'phabricator-shaped-request',
),
+ '7319e029' =>
+ array(
+ 0 => 'javelin-behavior',
+ 1 => 'javelin-dom',
+ ),
+ '62e18640' =>
+ array(
+ 0 => 'javelin-install',
+ 1 => 'javelin-util',
+ 2 => 'javelin-dom',
+ 3 => 'javelin-typeahead-normalizer',
+ ),
'6453c869' =>
array(
0 => 'javelin-install',
@@ -1313,18 +1329,6 @@
1 => 'javelin-stratcom',
2 => 'javelin-dom',
),
- '7319e029' =>
- array(
- 0 => 'javelin-behavior',
- 1 => 'javelin-dom',
- ),
- '62e18640' =>
- array(
- 0 => 'javelin-install',
- 1 => 'javelin-util',
- 2 => 'javelin-dom',
- 3 => 'javelin-typeahead-normalizer',
- ),
'76f4ebed' =>
array(
0 => 'javelin-install',
@@ -1594,6 +1598,15 @@
1 => 'javelin-stratcom',
2 => 'javelin-dom',
),
+ 'aa3f313b' =>
+ array(
+ 0 => 'javelin-behavior',
+ 1 => 'javelin-dom',
+ 2 => 'javelin-util',
+ 3 => 'javelin-stratcom',
+ 4 => 'javelin-workflow',
+ 5 => 'phabricator-draggable-list',
+ ),
'ad7a69ca' =>
array(
0 => 'javelin-install',
diff --git a/resources/sql/autopatches/20140509.dashboardlayoutconfig.sql b/resources/sql/autopatches/20140509.dashboardlayoutconfig.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20140509.dashboardlayoutconfig.sql
@@ -0,0 +1,4 @@
+ALTER TABLE {$NAMESPACE}_dashboard.dashboard
+ ADD COLUMN layoutConfig LONGTEXT NOT NULL COLLATE utf8_bin AFTER name;
+
+UPDATE {$NAMESPACE}_dashboard.dashboard SET layoutConfig = '[]';
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1456,10 +1456,13 @@
'PhabricatorDaemonTaskGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php',
'PhabricatorDashboard' => 'applications/dashboard/storage/PhabricatorDashboard.php',
'PhabricatorDashboardAddPanelController' => 'applications/dashboard/controller/PhabricatorDashboardAddPanelController.php',
+ 'PhabricatorDashboardArrangeController' => 'applications/dashboard/controller/PhabricatorDashboardArrangeController.php',
'PhabricatorDashboardController' => 'applications/dashboard/controller/PhabricatorDashboardController.php',
'PhabricatorDashboardDAO' => 'applications/dashboard/storage/PhabricatorDashboardDAO.php',
'PhabricatorDashboardEditController' => 'applications/dashboard/controller/PhabricatorDashboardEditController.php',
+ 'PhabricatorDashboardLayoutConfig' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php',
'PhabricatorDashboardListController' => 'applications/dashboard/controller/PhabricatorDashboardListController.php',
+ 'PhabricatorDashboardMovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardMovePanelController.php',
'PhabricatorDashboardPHIDTypeDashboard' => 'applications/dashboard/phid/PhabricatorDashboardPHIDTypeDashboard.php',
'PhabricatorDashboardPHIDTypePanel' => 'applications/dashboard/phid/PhabricatorDashboardPHIDTypePanel.php',
'PhabricatorDashboardPanel' => 'applications/dashboard/storage/PhabricatorDashboardPanel.php',
@@ -4235,10 +4238,12 @@
1 => 'PhabricatorPolicyInterface',
),
'PhabricatorDashboardAddPanelController' => 'PhabricatorDashboardController',
+ 'PhabricatorDashboardArrangeController' => 'PhabricatorDashboardController',
'PhabricatorDashboardController' => 'PhabricatorController',
'PhabricatorDashboardDAO' => 'PhabricatorLiskDAO',
'PhabricatorDashboardEditController' => 'PhabricatorDashboardController',
'PhabricatorDashboardListController' => 'PhabricatorDashboardController',
+ 'PhabricatorDashboardMovePanelController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPHIDTypeDashboard' => 'PhabricatorPHIDType',
'PhabricatorDashboardPHIDTypePanel' => 'PhabricatorPHIDType',
'PhabricatorDashboardPanel' =>
diff --git a/src/applications/dashboard/application/PhabricatorApplicationDashboard.php b/src/applications/dashboard/application/PhabricatorApplicationDashboard.php
--- a/src/applications/dashboard/application/PhabricatorApplicationDashboard.php
+++ b/src/applications/dashboard/application/PhabricatorApplicationDashboard.php
@@ -21,10 +21,11 @@
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorDashboardListController',
'view/(?P<id>\d+)/' => 'PhabricatorDashboardViewController',
+ 'arrange/(?P<id>\d+)/' => 'PhabricatorDashboardArrangeController',
'create/' => 'PhabricatorDashboardEditController',
'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardEditController',
'addpanel/(?P<id>\d+)/' => 'PhabricatorDashboardAddPanelController',
-
+ 'movepanel/(?P<id>\d+)/' => 'PhabricatorDashboardMovePanelController',
'panel/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorDashboardPanelListController',
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php b/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php
--- a/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php
+++ b/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php
@@ -26,7 +26,14 @@
return new Aphront404Response();
}
- $dashboard_uri = $this->getApplicationURI('view/'.$dashboard->getID().'/');
+ if ($request->getStr('src', 'edit') == 'edit') {
+ $redirect_uri = $this->getApplicationURI(
+ 'view/'.$dashboard->getID().'/');
+ } else {
+ $redirect_uri = $this->getApplicationURI(
+ 'arrange/'.$dashboard->getID().'/');
+ }
+ $layout_config = $dashboard->getLayoutConfigObject();
$v_panel = $request->getStr('panel');
$e_panel = true;
@@ -61,6 +68,13 @@
),
));
+ if ($layout_config->isMultiColumnLayout()) {
+ $layout_config->setPanelLocation(
+ $request->getInt('column'),
+ $panel->getPHID());
+ $dashboard->setLayoutConfigFromObject($layout_config);
+ }
+
$editor = id(new PhabricatorDashboardTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
@@ -68,12 +82,13 @@
->setContinueOnNoEffect(true)
->applyTransactions($dashboard, $xactions);
- return id(new AphrontRedirectResponse())->setURI($dashboard_uri);
+ return id(new AphrontRedirectResponse())->setURI($redirect_uri);
}
}
$form = id(new AphrontFormView())
->setUser($viewer)
+ ->addHiddenInput('src', $request->getStr('src', 'edit'))
->appendRemarkupInstructions(
pht('Enter a panel monogram like `W123`.'))
->appendChild(
@@ -83,11 +98,23 @@
->setValue($v_panel)
->setError($e_panel));
+ if ($layout_config->isMultiColumnLayout()) {
+ $form
+ ->appendRemarkupInstructions(
+ pht('Choose which column the panel should reside in.'))
+ ->appendChild(
+ id(new AphrontFormSelectControl())
+ ->setName('column')
+ ->setLabel(pht('Column'))
+ ->setOptions($layout_config->getColumnSelectOptions())
+ ->setValue($request->getInt('column')));
+ }
+
return $this->newDialog()
->setTitle(pht('Add Panel'))
->setErrors($errors)
->appendChild($form->buildLayoutView())
- ->addCancelButton($dashboard_uri)
+ ->addCancelButton($redirect_uri)
->addSubmitButton(pht('Add Panel'));
}
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardArrangeController.php b/src/applications/dashboard/controller/PhabricatorDashboardArrangeController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/dashboard/controller/PhabricatorDashboardArrangeController.php
@@ -0,0 +1,54 @@
+<?php
+
+final class PhabricatorDashboardArrangeController
+ extends PhabricatorDashboardController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $dashboard = id(new PhabricatorDashboardQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->needPanels(true)
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->executeOne();
+ if (!$dashboard) {
+ return new Aphront404Response();
+ }
+
+ $title = $dashboard->getName();
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addTextCrumb(
+ pht('Dashboard %d', $dashboard->getID()),
+ $this->getApplicationURI('view/'.$dashboard->getID().'/'));
+ $crumbs->addTextCrumb(pht('Arrange'));
+
+ $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine())
+ ->setViewer($viewer)
+ ->setDashboard($dashboard)
+ ->setArrangeMode(true)
+ ->renderDashboard();
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $rendered_dashboard,
+ ),
+ array(
+ 'title' => $title,
+ 'device' => true,
+ ));
+ }
+
+}
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php b/src/applications/dashboard/controller/PhabricatorDashboardEditController.php
--- a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php
+++ b/src/applications/dashboard/controller/PhabricatorDashboardEditController.php
@@ -17,6 +17,7 @@
$dashboard = id(new PhabricatorDashboardQuery())
->setViewer($viewer)
->withIDs(array($this->id))
+ ->needPanels(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
@@ -56,19 +57,25 @@
}
$v_name = $dashboard->getName();
+ $v_layout_mode = $dashboard->getLayoutConfigObject()->getLayoutMode();
$e_name = true;
$validation_exception = null;
if ($request->isFormPost()) {
$v_name = $request->getStr('name');
+ $v_layout_mode = $request->getStr('layout_mode');
$xactions = array();
$type_name = PhabricatorDashboardTransaction::TYPE_NAME;
+ $type_layout_mode = PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE;
$xactions[] = id(new PhabricatorDashboardTransaction())
->setTransactionType($type_name)
->setNewValue($v_name);
+ $xactions[] = id(new PhabricatorDashboardTransaction())
+ ->setTransactionType($type_layout_mode)
+ ->setNewValue($v_layout_mode);
try {
$editor = id(new PhabricatorDashboardTransactionEditor())
@@ -77,8 +84,12 @@
->setContentSourceFromRequest($request)
->applyTransactions($dashboard, $xactions);
- return id(new AphrontRedirectResponse())
- ->setURI($this->getApplicationURI('view/'.$dashboard->getID().'/'));
+ if ($is_new) {
+ $uri = $this->getApplicationURI('arrange/'.$dashboard->getID().'/');
+ } else {
+ $uri = $this->getApplicationURI('view/'.$dashboard->getID().'/');
+ }
+ return id(new AphrontRedirectResponse())->setURI($uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
@@ -86,6 +97,8 @@
}
}
+ $layout_mode_options =
+ PhabricatorDashboardLayoutConfig::getLayoutModeSelectOptions();
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
@@ -95,11 +108,16 @@
->setValue($v_name)
->setError($e_name))
->appendChild(
+ id(new AphrontFormSelectControl())
+ ->setLabel(pht('Layout Mode'))
+ ->setName('layout_mode')
+ ->setValue($v_layout_mode)
+ ->setOptions($layout_mode_options))
+ ->appendChild(
id(new AphrontFormSubmitControl())
->setValue($button)
->addCancelButton($cancel_uri));
-
$box = id(new PHUIObjectBoxView())
->setHeaderText($header)
->setForm($form)
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardMovePanelController.php b/src/applications/dashboard/controller/PhabricatorDashboardMovePanelController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/dashboard/controller/PhabricatorDashboardMovePanelController.php
@@ -0,0 +1,86 @@
+<?php
+
+final class PhabricatorDashboardMovePanelController
+ extends PhabricatorDashboardController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $column_id = $request->getStr('columnID');
+ $panel_phid = $request->getStr('objectPHID');
+ $after_phid = $request->getStr('afterPHID');
+ $before_phid = $request->getStr('beforePHID');
+
+ $dashboard = id(new PhabricatorDashboardQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->needPanels(true)
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->executeOne();
+ if (!$dashboard) {
+ return new Aphront404Response();
+ }
+ $panels = mpull($dashboard->getPanels(), null, 'getPHID');
+ $panel = idx($panels, $panel_phid);
+ if (!$panel) {
+ return new Aphront404Response();
+ }
+
+ $layout_config = $dashboard->getLayoutConfigObject();
+ $panel_location_grid = $layout_config->getPanelLocations();
+
+ foreach ($panel_location_grid as $column => $panel_columns) {
+ $found_old_column = array_search($panel_phid, $panel_columns);
+ if ($found_old_column !== false) {
+ $new_panel_columns = $panel_columns;
+ array_splice(
+ $new_panel_columns,
+ $found_old_column,
+ 1,
+ array());
+ $panel_location_grid[$column] = $new_panel_columns;
+ break;
+ }
+ }
+ $panel_columns = idx($panel_location_grid, $column_id, array());
+ if ($panel_columns) {
+ $insert_at = 0;
+ $new_panel_columns = $panel_columns;
+ foreach ($panel_columns as $index => $curr_panel_phid) {
+ if ($curr_panel_phid === $before_phid) {
+ $insert_at = max($index - 1, 0);
+ break;
+ }
+ if ($curr_panel_phid === $after_phid) {
+ $insert_at = $index;
+ break;
+ }
+ }
+ array_splice(
+ $new_panel_columns,
+ $insert_at,
+ 0,
+ array($panel_phid));
+ } else {
+ $new_panel_columns = array(0 => $panel_phid);
+ }
+ $panel_location_grid[$column_id] = $new_panel_columns;
+ $layout_config->setPanelLocations($panel_location_grid);
+ $dashboard->setLayoutConfigFromObject($layout_config);
+ $dashboard->save();
+
+ return id(new AphrontAjaxResponse())->setContent('');
+ }
+
+}
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php
--- a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php
+++ b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php
@@ -86,6 +86,14 @@
$actions->addAction(
id(new PhabricatorActionView())
+ ->setName(pht('Arrange Dashboard'))
+ ->setIcon('fa-arrows')
+ ->setHref($this->getApplicationURI("arrange/{$id}/"))
+ ->setDisabled(!$can_edit)
+ ->setWorkflow(!$can_edit));
+
+ $actions->addAction(
+ id(new PhabricatorActionView())
->setName(pht('Add Panel'))
->setIcon('fa-plus')
->setHref($this->getApplicationURI("addpanel/{$id}/"))
diff --git a/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php b/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php
--- a/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php
+++ b/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php
@@ -11,6 +11,7 @@
$types[] = PhabricatorTransactions::TYPE_EDGE;
$types[] = PhabricatorDashboardTransaction::TYPE_NAME;
+ $types[] = PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE;
return $types;
}
@@ -24,6 +25,12 @@
return null;
}
return $object->getName();
+ case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE:
+ if ($this->getIsNewObject()) {
+ return null;
+ }
+ $layout_config = $object->getLayoutConfigObject();
+ return $layout_config->getLayoutMode();
}
return parent::getCustomTransactionOldValue($object, $xaction);
@@ -34,6 +41,7 @@
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorDashboardTransaction::TYPE_NAME:
+ case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE:
return $xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
@@ -46,6 +54,21 @@
case PhabricatorDashboardTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
return;
+ case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE:
+ $old_layout = $object->getLayoutConfigObject();
+ $new_layout = clone $old_layout;
+ $new_layout->setLayoutMode($xaction->getNewValue());
+ if ($old_layout->isMultiColumnLayout() !=
+ $new_layout->isMultiColumnLayout()) {
+ $panel_phids = $object->getPanelPHIDs();
+ $new_locations = $new_layout->getDefaultPanelLocations();
+ foreach ($panel_phids as $panel_phid) {
+ $new_locations[0][] = $panel_phid;
+ }
+ $new_layout->setPanelLocations($new_locations);
+ }
+ $object->setLayoutConfigFromObject($new_layout);
+ return;
case PhabricatorTransactions::TYPE_VIEW_POLICY:
$object->setViewPolicy($xaction->getNewValue());
return;
@@ -65,6 +88,7 @@
switch ($xaction->getTransactionType()) {
case PhabricatorDashboardTransaction::TYPE_NAME:
+ case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE:
return;
case PhabricatorTransactions::TYPE_EDGE:
return;
diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
--- a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
+++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
@@ -79,6 +79,9 @@
));
return id(new PHUIObjectBoxView())
+ ->addSigil('dashboard-panel')
+ ->setMetadata(array(
+ 'objectPHID' => $panel->getPHID()))
->setHeaderText($panel->getName())
->setID($panel_id)
->appendChild(pht('Loading...'));
diff --git a/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php
--- a/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php
+++ b/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php
@@ -4,6 +4,7 @@
private $dashboard;
private $viewer;
+ private $arrangeMode;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
@@ -15,20 +16,101 @@
return $this;
}
+ public function setArrangeMode($mode) {
+ $this->arrangeMode = $mode;
+ return $this;
+ }
+
public function renderDashboard() {
+ require_celerity_resource('phabricator-dashboard-css');
$dashboard = $this->dashboard;
$viewer = $this->viewer;
- $result = array();
- foreach ($dashboard->getPanels() as $panel) {
- $result[] = id(new PhabricatorDashboardPanelRenderingEngine())
- ->setViewer($viewer)
- ->setPanel($panel)
- ->setEnableAsyncRendering(true)
- ->renderPanel();
+ $layout_config = $dashboard->getLayoutConfigObject();
+ $panel_grid_locations = $layout_config->getPanelLocations();
+ $panels = mpull($dashboard->getPanels(), null, 'getPHID');
+ $dashboard_id = celerity_generate_unique_node_id();
+ $result = id(new AphrontMultiColumnView())
+ ->setID($dashboard_id)
+ ->setFluidlayout(true);
+
+ foreach ($panel_grid_locations as $column => $panel_column_locations) {
+ $panel_phids = $panel_column_locations;
+ $column_panels = array_select_keys($panels, $panel_phids);
+ $column_result = array();
+ foreach ($column_panels as $panel) {
+ $column_result[] = id(new PhabricatorDashboardPanelRenderingEngine())
+ ->setViewer($viewer)
+ ->setPanel($panel)
+ ->setEnableAsyncRendering(true)
+ ->renderPanel();
+ }
+ $column_class = $layout_config->getColumnClass(
+ $column,
+ $this->arrangeMode);
+ if ($this->arrangeMode) {
+ $column_result[] = $this->renderAddPanelPlaceHolder($column);
+ $column_result[] = $this->renderAddPanelUI($column);
+ }
+ $result->addColumn(
+ $column_result,
+ $column_class,
+ $sigil = 'dashboard-column',
+ $metadata = array('columnID' => $column));
+ }
+
+ if ($this->arrangeMode) {
+ Javelin::initBehavior(
+ 'dashboard-move-panels',
+ array(
+ 'dashboardID' => $dashboard_id,
+ 'moveURI' => '/dashboard/movepanel/'.$dashboard->getID().'/',
+ ));
}
return $result;
}
+ private function renderAddPanelPlaceHolder($column) {
+ $uri = $this->getAddPanelURI($column);
+
+ $dashboard = $this->dashboard;
+ $panels = $dashboard->getPanels();
+ $layout_config = $dashboard->getLayoutConfigObject();
+ if ($layout_config->isMultiColumnLayout() && count($panels)) {
+ $text = pht('Drag a panel here or click to add a panel.');
+ } else {
+ $text = pht('Click to add a panel.');
+ }
+ return javelin_tag(
+ 'a',
+ array(
+ 'sigil' => 'workflow',
+ 'class' => 'drag-ghost dashboard-panel-placeholder',
+ 'href' => (string) $uri),
+ $text);
+ }
+
+ private function renderAddPanelUI($column) {
+ $uri = $this->getAddPanelURI($column);
+
+ return id(new PHUIButtonView())
+ ->setTag('a')
+ ->setHref((string) $uri)
+ ->setWorkflow(true)
+ ->setColor(PHUIButtonView::GREY)
+ ->setIcon(id(new PHUIIconView())
+ ->setIconFont('fa-plus'))
+ ->setText(pht('Add Panel'))
+ ->addClass(PHUI::MARGIN_LARGE);
+ }
+
+ private function getAddPanelURI($column) {
+ $dashboard = $this->dashboard;
+ $uri = id(new PhutilURI('/dashboard/addpanel/'.$dashboard->getID().'/'))
+ ->setQueryParam('column', $column)
+ ->setQueryParam('src', 'arrange');
+ return $uri;
+ }
+
}
diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php
new file mode 100644
--- /dev/null
+++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php
@@ -0,0 +1,133 @@
+<?php
+
+final class PhabricatorDashboardLayoutConfig {
+
+ const MODE_FULL = 'layout-mode-full';
+ const MODE_HALF_AND_HALF = 'layout-mode-half-and-half';
+ const MODE_THIRD_AND_THIRDS = 'layout-mode-third-and-thirds';
+ const MODE_THIRDS_AND_THIRD = 'layout-mode-thirds-and-third';
+
+ private $layoutMode = self::MODE_FULL;
+ private $panelLocations = array();
+
+ public function setLayoutMode($mode) {
+ $this->layoutMode = $mode;
+ return $this;
+ }
+ public function getLayoutMode() {
+ return $this->layoutMode;
+ }
+
+ public function setPanelLocation($which_column, $panel_phid) {
+ $this->panelLocations[$which_column][] = $panel_phid;
+ return $this;
+ }
+
+ public function setPanelLocations(array $locations) {
+ $this->panelLocations = $locations;
+ return $this;
+ }
+
+ public function getPanelLocations() {
+ return $this->panelLocations;
+ }
+
+ public function getDefaultPanelLocations() {
+ switch ($this->getLayoutMode()) {
+ case self::MODE_HALF_AND_HALF:
+ case self::MODE_THIRD_AND_THIRDS:
+ case self::MODE_THIRDS_AND_THIRD:
+ $locations = array(array(), array());
+ break;
+ case self::MODE_FULL:
+ default:
+ $locations = array(array());
+ break;
+ }
+ return $locations;
+ }
+
+ public function getColumnClass($column_index, $grippable = false) {
+ switch ($this->getLayoutMode()) {
+ case self::MODE_HALF_AND_HALF:
+ $class = 'half';
+ break;
+ case self::MODE_THIRD_AND_THIRDS:
+ if ($column_index) {
+ $class = 'thirds';
+ } else {
+ $class = 'third';
+ }
+ break;
+ case self::MODE_THIRDS_AND_THIRD:
+ if ($column_index) {
+ $class = 'third';
+ } else {
+ $class = 'thirds';
+ }
+ break;
+ case self::MODE_FULL:
+ default:
+ $class = null;
+ break;
+ }
+ if ($grippable) {
+ $class .= ' grippable';
+ }
+ return $class;
+ }
+
+ public function isMultiColumnLayout() {
+ return $this->getLayoutMode() != self::MODE_FULL;
+ }
+
+ public function getColumnSelectOptions() {
+ $options = array();
+
+ switch ($this->getLayoutMode()) {
+ case self::MODE_HALF_AND_HALF:
+ case self::MODE_THIRD_AND_THIRDS:
+ case self::MODE_THIRDS_AND_THIRD:
+ return array(
+ 0 => pht('Left'),
+ 1 => pht('Right'));
+ break;
+ case self::MODE_FULL:
+ throw new Exception('There is only one column in mode full.');
+ break;
+ default:
+ throw new Exception('Unknown layout mode!');
+ break;
+ }
+
+ return $options;
+ }
+
+ public static function getLayoutModeSelectOptions() {
+ return array(
+ self::MODE_FULL => pht('One full-width column'),
+ self::MODE_HALF_AND_HALF => pht('Two columns, 1/2 and 1/2'),
+ self::MODE_THIRD_AND_THIRDS => pht('Two columns, 1/3 and 2/3'),
+ self::MODE_THIRDS_AND_THIRD => pht('Two columns, 2/3 and 1/3'),
+ );
+ }
+
+ public static function newFromDictionary(array $dict) {
+ $layout_config = id(new PhabricatorDashboardLayoutConfig())
+ ->setLayoutMode(idx($dict, 'layoutMode', self::MODE_FULL));
+ $layout_config->setPanelLocations(idx(
+ $dict,
+ 'panelLocations',
+ $layout_config->getDefaultPanelLocations()));
+
+ return $layout_config;
+ }
+
+ public function toDictionary() {
+ return array(
+ 'layoutMode' => $this->getLayoutMode(),
+ 'panelLocations' => $this->getPanelLocations()
+ );
+ }
+
+}
diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php
--- a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php
+++ b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php
@@ -47,6 +47,9 @@
$content = $this->renderPanelContent($viewer, $panel);
return id(new PHUIObjectBoxView())
+ ->addSigil('dashboard-panel')
+ ->setMetadata(array(
+ 'objectPHID' => $panel->getPHID()))
->setHeaderText($panel->getName())
->appendChild($content);
}
diff --git a/src/applications/dashboard/storage/PhabricatorDashboard.php b/src/applications/dashboard/storage/PhabricatorDashboard.php
--- a/src/applications/dashboard/storage/PhabricatorDashboard.php
+++ b/src/applications/dashboard/storage/PhabricatorDashboard.php
@@ -9,6 +9,7 @@
protected $name;
protected $viewPolicy;
protected $editPolicy;
+ protected $layoutConfig = array();
private $panelPHIDs = self::ATTACHABLE;
private $panels = self::ATTACHABLE;
@@ -17,12 +18,16 @@
return id(new PhabricatorDashboard())
->setName('')
->setViewPolicy(PhabricatorPolicies::POLICY_USER)
- ->setEditPolicy($actor->getPHID());
+ ->setEditPolicy($actor->getPHID())
+ ->attachPanels(array())
+ ->attachPanelPHIDs(array());
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
+ self::CONFIG_SERIALIZATION => array(
+ 'layoutConfig' => self::SERIALIZATION_JSON),
) + parent::getConfiguration();
}
@@ -31,6 +36,17 @@
PhabricatorDashboardPHIDTypeDashboard::TYPECONST);
}
+ public function getLayoutConfigObject() {
+ return PhabricatorDashboardLayoutConfig::newFromDictionary(
+ $this->getLayoutConfig());
+ }
+
+ public function setLayoutConfigFromObject(
+ PhabricatorDashboardLayoutConfig $object) {
+ $this->setLayoutConfig($object->toDictionary());
+ return $this;
+ }
+
public function attachPanelPHIDs(array $phids) {
$this->panelPHIDs = $phids;
return $this;
diff --git a/src/applications/dashboard/storage/PhabricatorDashboardTransaction.php b/src/applications/dashboard/storage/PhabricatorDashboardTransaction.php
--- a/src/applications/dashboard/storage/PhabricatorDashboardTransaction.php
+++ b/src/applications/dashboard/storage/PhabricatorDashboardTransaction.php
@@ -4,6 +4,7 @@
extends PhabricatorApplicationTransaction {
const TYPE_NAME = 'dashboard:name';
+ const TYPE_LAYOUT_MODE = 'dashboard:layoutmode';
public function getApplicationName() {
return 'dashboard';
@@ -86,4 +87,15 @@
return parent::getColor();
}
+
+ public function shouldHide() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ switch ($this->getTransactionType()) {
+ case self::TYPE_LAYOUT_MODE:
+ return true;
+ }
+ return parent::shouldHide();
+ }
}
diff --git a/src/view/layout/AphrontMultiColumnView.php b/src/view/layout/AphrontMultiColumnView.php
--- a/src/view/layout/AphrontMultiColumnView.php
+++ b/src/view/layout/AphrontMultiColumnView.php
@@ -6,14 +6,32 @@
const GUTTER_MEDIUM = 'mmr';
const GUTTER_LARGE = 'mlr';
+ private $id;
private $columns = array();
private $fluidLayout = false;
private $fluidishLayout = false;
private $gutter;
private $border;
- public function addColumn($column) {
- $this->columns[] = $column;
+ public function setID($id) {
+ $this->id = $id;
+ return $this;
+ }
+
+ public function getID() {
+ return $this->id;
+ }
+
+ public function addColumn(
+ $column,
+ $class = null,
+ $sigil = null,
+ $metadata = null) {
+ $this->columns[] = array(
+ 'column' => $column,
+ 'class' => $class,
+ 'sigil' => $sigil,
+ 'metadata' => $metadata);
return $this;
}
@@ -55,30 +73,34 @@
$classes[] = 'aphront-multi-column-'.count($this->columns).'-up';
$columns = array();
- $column_class = array();
- $column_class[] = 'aphront-multi-column-column';
- $outer_class = array();
- $outer_class[] = 'aphront-multi-column-column-outer';
- if ($this->gutter) {
- $column_class[] = $this->gutter;
- }
$i = 0;
- foreach ($this->columns as $column) {
+ foreach ($this->columns as $column_data) {
+ $column_class = array('aphront-multi-column-column');
+ if ($this->gutter) {
+ $column_class[] = $this->gutter;
+ }
+ $outer_class = array('aphront-multi-column-column-outer');
if (++$i === count($this->columns)) {
$column_class[] = 'aphront-multi-column-column-last';
$outer_class[] = 'aphront-multi-colum-column-outer-last';
}
- $column_inner = phutil_tag(
+ $column = $column_data['column'];
+ if ($column_data['class']) {
+ $outer_class[] = $column_data['class'];
+ }
+ $column_sigil = idx($column_data, 'sigil');
+ $column_metadata = idx($column_data, 'metadata');
+ $column_inner = javelin_tag(
'div',
- array(
- 'class' => implode(' ', $column_class)
- ),
+ array(
+ 'class' => implode(' ', $column_class),
+ 'sigil' => $column_sigil,
+ 'meta' => $column_metadata),
$column);
$columns[] = phutil_tag(
'div',
- array(
- 'class' => implode(' ', $outer_class)
- ),
+ array(
+ 'class' => implode(' ', $outer_class)),
$column_inner);
}
@@ -120,7 +142,8 @@
return phutil_tag(
'div',
array(
- 'class' => 'aphront-multi-column-view'
+ 'class' => 'aphront-multi-column-view',
+ 'id' => $this->getID(),
),
$board);
}
diff --git a/webroot/rsrc/css/application/dashboard/dashboard.css b/webroot/rsrc/css/application/dashboard/dashboard.css
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/css/application/dashboard/dashboard.css
@@ -0,0 +1,59 @@
+/**
+ * @provides phabricator-dashboard-css
+ */
+
+.aphront-multi-column-fluid .aphront-multi-column-2-up
+.aphront-multi-column-column-outer.half {
+ width: 50%;
+}
+
+.aphront-multi-column-fluid .aphront-multi-column-2-up
+.aphront-multi-column-column-outer.third {
+ width: 33.34%;
+}
+
+.aphront-multi-column-fluid .aphront-multi-column-2-up
+.aphront-multi-column-column-outer.thirds {
+ width: 66.66%;
+}
+
+.aphront-multi-column-fluid
+.aphront-multi-column-column-outer.grippable
+.aphront-multi-column-column .phui-object-box {
+ cursor: move;
+}
+
+.aphront-multi-column-fluid
+.aphront-multi-column-column .drag-ghost {
+ list-style-type: none;
+ margin: 16px;
+}
+
+.aphront-multi-column-fluid
+.aphront-multi-column-column
+.dashboard-panel-placeholder {
+ display: none;
+}
+
+.aphront-multi-column-fluid
+.aphront-multi-column-column.dashboard-column-empty
+.dashboard-panel-placeholder {
+ color: {$greytext};
+ display: block;
+ padding: 24px;
+ margin: 16px 16px 0px 16px;
+}
+
+.aphront-multi-column-fluid
+.aphront-multi-column-column.dashboard-column-empty
+.dashboard-panel-placeholder:hover {
+ text-decoration: none;
+ border: 1px {$greyborder} dashed;
+ color: {$darkgreytext};
+}
+
+.aphront-multi-column-fluid
+.aphront-multi-column-column.drag-target-list
+.dashboard-panel-placeholder {
+ display: none;
+}
diff --git a/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js b/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js
@@ -0,0 +1,103 @@
+/**
+ * @provides javelin-behavior-dashboard-move-panels
+ * @requires javelin-behavior
+ * javelin-dom
+ * javelin-util
+ * javelin-stratcom
+ * javelin-workflow
+ * phabricator-draggable-list
+ */
+
+JX.behavior('dashboard-move-panels', function(config) {
+
+ var itemSigil = 'dashboard-panel';
+
+ function finditems(col) {
+ return JX.DOM.scry(col, 'div', itemSigil);
+ }
+
+ function markcolempty(col, toggle) {
+ JX.DOM.alterClass(col, 'dashboard-column-empty', toggle);
+ }
+
+ function onupdate(col) {
+ markcolempty(col, !this.findItems().length);
+ }
+
+ function onresponse(response, item, list) {
+ list.unlock();
+ JX.DOM.alterClass(item, 'drag-sending', false);
+ }
+
+ function ondrop(list, item, after, from) {
+ list.lock();
+ JX.DOM.alterClass(item, 'drag-sending', true);
+
+ var item_phid = JX.Stratcom.getData(item).objectPHID;
+ var data = {
+ objectPHID: item_phid,
+ columnID: JX.Stratcom.getData(list.getRootNode()).columnID
+ };
+
+ var after_phid = null;
+ var items = finditems(list.getRootNode());
+ if (after) {
+ after_phid = JX.Stratcom.getData(after).objectPHID;
+ data.afterPHID = after_phid;
+ }
+ var ii;
+ var ii_item;
+ var ii_item_phid;
+ var ii_prev_item_phid = null;
+ var before_phid = null;
+ for (ii = 0; ii < items.length; ii++) {
+ ii_item = items[ii];
+ ii_item_phid = JX.Stratcom.getData(ii_item).objectPHID;
+ if (ii_item_phid == item_phid) {
+ // skip the item we just dropped
+ continue;
+ }
+ // note this handles when there is no after phid - we are at the top of
+ // the list - quite nicely
+ if (ii_prev_item_phid == after_phid) {
+ before_phid = ii_item_phid;
+ break;
+ }
+ ii_prev_item_phid = ii_item_phid;
+ }
+ if (before_phid) {
+ data.beforePHID = before_phid;
+ }
+
+ var workflow = new JX.Workflow(config.moveURI, data)
+ .setHandler(function(response) {
+ onresponse(response, item, list);
+ });
+
+ workflow.start();
+ }
+
+ var lists = [];
+ var ii;
+ var cols = JX.DOM.scry(JX.$(config.dashboardID), 'div', 'dashboard-column');
+ var col = null;
+
+ for (ii = 0; ii < cols.length; ii++) {
+ col = cols[ii];
+ var list = new JX.DraggableList(itemSigil, col)
+ .setFindItemsHandler(JX.bind(null, finditems, col));
+
+ list.listen('didSend', JX.bind(list, onupdate, col));
+ list.listen('didReceive', JX.bind(list, onupdate, col));
+
+ list.listen('didDrop', JX.bind(null, ondrop, list));
+
+ lists.push(list);
+ markcolempty(col, finditems(col).length === 0);
+ }
+
+ for (ii = 0; ii < lists.length; ii++) {
+ lists[ii].setGroup(lists);
+ }
+
+});
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Jan 17, 11:06 PM (5 m, 10 s)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6999363
Default Alt Text
D9031.diff (38 KB)
Attached To
Mode
D9031: Dashboards - add layout mode to dashboards
Attached
Detach File
Event Timeline
Log In to Comment