Page MenuHomePhabricator

D9031.diff
No OneTemporary

D9031.diff

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

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)

Event Timeline