Page MenuHomePhabricator

D21150.diff
No OneTemporary

D21150.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -155,6 +155,7 @@
'rsrc/css/phui/phui-fontkit.css' => '1ec937e5',
'rsrc/css/phui/phui-form-view.css' => '01b796c0',
'rsrc/css/phui/phui-form.css' => '1f177cb7',
+ 'rsrc/css/phui/phui-formation-view.css' => 'aec68a01',
'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
'rsrc/css/phui/phui-header-view.css' => '36c86a58',
'rsrc/css/phui/phui-hovercard.css' => '6ca90fa0',
@@ -519,6 +520,7 @@
'rsrc/js/phui/behavior-phui-submenu.js' => 'b5e9bff9',
'rsrc/js/phui/behavior-phui-tab-group.js' => '242aa08b',
'rsrc/js/phui/behavior-phui-timer-control.js' => 'f84bcbf4',
+ 'rsrc/js/phui/behavior-phuix-formation-view.js' => '1a12beef',
'rsrc/js/phuix/PHUIXActionListView.js' => 'c68f183f',
'rsrc/js/phuix/PHUIXActionView.js' => 'aaa08f3b',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '2fbe234d',
@@ -526,6 +528,9 @@
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '7acfd98b',
'rsrc/js/phuix/PHUIXExample.js' => 'c2c500a7',
'rsrc/js/phuix/PHUIXFormControl.js' => '38c1f3fb',
+ 'rsrc/js/phuix/PHUIXFormationColumnView.js' => '08fc09e9',
+ 'rsrc/js/phuix/PHUIXFormationFlankView.js' => '6648270a',
+ 'rsrc/js/phuix/PHUIXFormationView.js' => '0113c54c',
'rsrc/js/phuix/PHUIXIconView.js' => 'a5257c4e',
),
'symbols' => array(
@@ -667,6 +672,7 @@
'javelin-behavior-phui-tab-group' => '242aa08b',
'javelin-behavior-phui-timer-control' => 'f84bcbf4',
'javelin-behavior-phuix-example' => 'c2c500a7',
+ 'javelin-behavior-phuix-formation-view' => '1a12beef',
'javelin-behavior-policy-control' => '0eaa33a9',
'javelin-behavior-policy-rule-editor' => '9347f172',
'javelin-behavior-project-boards' => '58cb6a88',
@@ -844,6 +850,7 @@
'phui-fontkit-css' => '1ec937e5',
'phui-form-css' => '1f177cb7',
'phui-form-view-css' => '01b796c0',
+ 'phui-formation-view-css' => 'aec68a01',
'phui-head-thing-view-css' => 'd7f293df',
'phui-header-view-css' => '36c86a58',
'phui-hovercard' => '074f0783',
@@ -886,6 +893,9 @@
'phuix-button-view' => '55a24e84',
'phuix-dropdown-menu' => '7acfd98b',
'phuix-form-control-view' => '38c1f3fb',
+ 'phuix-formation-column-view' => '08fc09e9',
+ 'phuix-formation-flank-view' => '6648270a',
+ 'phuix-formation-view' => '0113c54c',
'phuix-icon-view' => 'a5257c4e',
'policy-css' => 'ceb56a08',
'policy-edit-css' => '8794e2ed',
@@ -912,6 +922,10 @@
'unhandled-exception-css' => '9ecfc00d',
),
'requires' => array(
+ '0113c54c' => array(
+ 'javelin-install',
+ 'javelin-dom',
+ ),
'0116d3e8' => array(
'javelin-behavior',
'javelin-dom',
@@ -984,6 +998,10 @@
'javelin-util',
'javelin-magical-init',
),
+ '08fc09e9' => array(
+ 'javelin-install',
+ 'javelin-dom',
+ ),
'0922e81d' => array(
'herald-rule-editor',
'javelin-behavior',
@@ -1036,6 +1054,12 @@
'16e97ebc' => array(
'javelin-dom',
),
+ '1a12beef' => array(
+ 'javelin-behavior',
+ 'phuix-formation-view',
+ 'phuix-formation-column-view',
+ 'phuix-formation-flank-view',
+ ),
'1a844c06' => array(
'javelin-install',
'javelin-util',
@@ -1520,6 +1544,10 @@
'javelin-stratcom',
'javelin-dom',
),
+ '6648270a' => array(
+ 'javelin-install',
+ 'javelin-dom',
+ ),
'6a1583a8' => array(
'javelin-behavior',
'javelin-history',
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
@@ -179,6 +179,7 @@
'AphrontAccessDeniedQueryException' => 'infrastructure/storage/exception/AphrontAccessDeniedQueryException.php',
'AphrontAjaxResponse' => 'aphront/response/AphrontAjaxResponse.php',
'AphrontApplicationConfiguration' => 'aphront/configuration/AphrontApplicationConfiguration.php',
+ 'AphrontAutoIDView' => 'view/AphrontAutoIDView.php',
'AphrontBarView' => 'view/widget/bars/AphrontBarView.php',
'AphrontBaseMySQLDatabaseConnection' => 'infrastructure/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php',
'AphrontBoolHTTPParameterType' => 'aphront/httpparametertype/AphrontBoolHTTPParameterType.php',
@@ -2035,6 +2036,14 @@
'PHUIFormLayoutView' => 'view/form/PHUIFormLayoutView.php',
'PHUIFormNumberControl' => 'view/form/control/PHUIFormNumberControl.php',
'PHUIFormTimerControl' => 'view/form/control/PHUIFormTimerControl.php',
+ 'PHUIFormationColumnDynamicView' => 'view/formation/PHUIFormationColumnDynamicView.php',
+ 'PHUIFormationColumnItem' => 'view/formation/PHUIFormationColumnItem.php',
+ 'PHUIFormationColumnView' => 'view/formation/PHUIFormationColumnView.php',
+ 'PHUIFormationContentView' => 'view/formation/PHUIFormationContentView.php',
+ 'PHUIFormationExpanderView' => 'view/formation/PHUIFormationExpanderView.php',
+ 'PHUIFormationFlankView' => 'view/formation/PHUIFormationFlankView.php',
+ 'PHUIFormationResizerView' => 'view/formation/PHUIFormationResizerView.php',
+ 'PHUIFormationView' => 'view/formation/PHUIFormationView.php',
'PHUIHandleListView' => 'applications/phid/view/PHUIHandleListView.php',
'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php',
'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php',
@@ -6193,6 +6202,7 @@
'AphrontAccessDeniedQueryException' => 'AphrontQueryException',
'AphrontAjaxResponse' => 'AphrontResponse',
'AphrontApplicationConfiguration' => 'Phobject',
+ 'AphrontAutoIDView' => 'AphrontView',
'AphrontBarView' => 'AphrontView',
'AphrontBaseMySQLDatabaseConnection' => 'AphrontDatabaseConnection',
'AphrontBoolHTTPParameterType' => 'AphrontHTTPParameterType',
@@ -8315,6 +8325,14 @@
'PHUIFormLayoutView' => 'AphrontView',
'PHUIFormNumberControl' => 'AphrontFormControl',
'PHUIFormTimerControl' => 'AphrontFormControl',
+ 'PHUIFormationColumnDynamicView' => 'PHUIFormationColumnView',
+ 'PHUIFormationColumnItem' => 'Phobject',
+ 'PHUIFormationColumnView' => 'AphrontAutoIDView',
+ 'PHUIFormationContentView' => 'PHUIFormationColumnView',
+ 'PHUIFormationExpanderView' => 'AphrontAutoIDView',
+ 'PHUIFormationFlankView' => 'PHUIFormationColumnDynamicView',
+ 'PHUIFormationResizerView' => 'PHUIFormationColumnView',
+ 'PHUIFormationView' => 'AphrontView',
'PHUIHandleListView' => 'AphrontTagView',
'PHUIHandleTagListView' => 'AphrontTagView',
'PHUIHandleView' => 'AphrontView',
diff --git a/src/view/AphrontAutoIDView.php b/src/view/AphrontAutoIDView.php
new file mode 100644
--- /dev/null
+++ b/src/view/AphrontAutoIDView.php
@@ -0,0 +1,15 @@
+<?php
+
+abstract class AphrontAutoIDView
+ extends AphrontView {
+
+ private $id;
+
+ final public function getID() {
+ if (!$this->id) {
+ $this->id = celerity_generate_unique_node_id();
+ }
+ return $this->id;
+ }
+
+}
diff --git a/src/view/formation/PHUIFormationColumnDynamicView.php b/src/view/formation/PHUIFormationColumnDynamicView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationColumnDynamicView.php
@@ -0,0 +1,37 @@
+<?php
+
+abstract class PHUIFormationColumnDynamicView
+ extends PHUIFormationColumnView {
+
+ private $isVisible = true;
+ private $isResizable;
+ private $width;
+
+ public function setIsVisible($is_visible) {
+ $this->isVisible = $is_visible;
+ return $this;
+ }
+
+ public function getIsVisible() {
+ return $this->isVisible;
+ }
+
+ public function setIsResizable($is_resizable) {
+ $this->isResizable = $is_resizable;
+ return $this;
+ }
+
+ public function getIsResizable() {
+ return $this->isResizable;
+ }
+
+ public function setWidth($width) {
+ $this->width = $width;
+ return $this;
+ }
+
+ public function getWidth() {
+ return $this->width;
+ }
+
+}
diff --git a/src/view/formation/PHUIFormationColumnItem.php b/src/view/formation/PHUIFormationColumnItem.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationColumnItem.php
@@ -0,0 +1,116 @@
+<?php
+
+final class PHUIFormationColumnItem
+ extends Phobject {
+
+ private $id;
+ private $column;
+ private $controlItem;
+ private $resizerItem;
+ private $isRightAligned;
+ private $expander;
+ private $expanders = array();
+
+ public function getID() {
+ if (!$this->id) {
+ $this->id = celerity_generate_unique_node_id();
+ }
+ return $this->id;
+ }
+
+ public function setColumn(PHUIFormationColumnView $column) {
+ $this->column = $column;
+ return $this;
+ }
+
+ public function getColumn() {
+ return $this->column;
+ }
+
+ public function setControlItem(PHUIFormationColumnItem $control_item) {
+ $this->controlItem = $control_item;
+ return $this;
+ }
+
+ public function getControlItem() {
+ return $this->controlItem;
+ }
+
+ public function setIsRightAligned($is_right_aligned) {
+ $this->isRightAligned = $is_right_aligned;
+ return $this;
+ }
+
+ public function getIsRightAligned() {
+ return $this->isRightAligned;
+ }
+
+ public function setResizerItem(PHUIFormationColumnItem $resizer_item) {
+ $this->resizerItem = $resizer_item;
+ return $this;
+ }
+
+ public function getResizerItem() {
+ return $this->resizerItem;
+ }
+
+ public function setExpander(PHUIFormationExpanderView $expander) {
+ $this->expander = $expander;
+ return $this;
+ }
+
+ public function getExpander() {
+ return $this->expander;
+ }
+
+ public function appendExpander(PHUIFormationExpanderView $expander) {
+ $this->expanders[] = $expander;
+ return $this;
+ }
+
+ public function getExpanders() {
+ return $this->expanders;
+ }
+
+ public function newClientProperties() {
+ $expander_id = null;
+
+ $expander = $this->getExpander();
+ if ($expander) {
+ $expander_id = $expander->getID();
+ }
+
+
+ $resizer_details = null;
+ $resizer_item = $this->getResizerItem();
+ if ($resizer_item) {
+ $resizer_details = array(
+ 'itemID' => $resizer_item->getID(),
+ 'controlID' => $resizer_item->getColumn()->getID(),
+ );
+ }
+
+ $column = $this->getColumn();
+
+ $width = $column->getWidth();
+ if ($width !== null) {
+ $width = (int)$width;
+ }
+
+ $is_visible = (bool)$column->getIsVisible();
+ $is_right_aligned = $this->getIsRightAligned();
+
+ $column_details = $column->newClientProperties();
+
+ return array(
+ 'itemID' => $this->getID(),
+ 'width' => $width,
+ 'isVisible' => $is_visible,
+ 'isRightAligned' => $is_right_aligned,
+ 'expanderID' => $expander_id,
+ 'resizer' => $resizer_details,
+ 'column' => $column_details,
+ );
+ }
+
+}
diff --git a/src/view/formation/PHUIFormationColumnView.php b/src/view/formation/PHUIFormationColumnView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationColumnView.php
@@ -0,0 +1,37 @@
+<?php
+
+abstract class PHUIFormationColumnView
+ extends AphrontAutoIDView {
+
+ private $item;
+
+ final public function setColumnItem(PHUIFormationColumnItem $item) {
+ $this->item = $item;
+ return $this;
+ }
+
+ final public function getColumnItem() {
+ return $this->item;
+ }
+
+ public function getWidth() {
+ return null;
+ }
+
+ public function getIsResizable() {
+ return false;
+ }
+
+ public function getIsVisible() {
+ return true;
+ }
+
+ public function getIsControlColumn() {
+ return false;
+ }
+
+ public function newClientProperties() {
+ return null;
+ }
+
+}
diff --git a/src/view/formation/PHUIFormationContentView.php b/src/view/formation/PHUIFormationContentView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationContentView.php
@@ -0,0 +1,21 @@
+<?php
+
+final class PHUIFormationContentView
+ extends PHUIFormationColumnView {
+
+ public function getIsControlColumn() {
+ return true;
+ }
+
+ public function render() {
+ require_celerity_resource('phui-formation-view-css');
+
+ return phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phui-formation-view-content',
+ ),
+ $this->renderChildren());
+ }
+
+}
diff --git a/src/view/formation/PHUIFormationExpanderView.php b/src/view/formation/PHUIFormationExpanderView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationExpanderView.php
@@ -0,0 +1,64 @@
+<?php
+
+final class PHUIFormationExpanderView
+ extends AphrontAutoIDView {
+
+ private $tooltip;
+ private $columnItem;
+
+ public function setTooltip($tooltip) {
+ $this->tooltip = $tooltip;
+ return $this;
+ }
+
+ public function getTooltip() {
+ return $this->tooltip;
+ }
+
+ public function setColumnItem($column_item) {
+ $this->columnItem = $column_item;
+ return $this;
+ }
+
+ public function getColumnItem() {
+ return $this->columnItem;
+ }
+
+ public function render() {
+ $classes = array();
+ $classes[] = 'phui-formation-view-expander';
+
+ $is_right = $this->getColumnItem()->getIsRightAligned();
+ if ($is_right) {
+ $icon = id(new PHUIIconView())
+ ->setIcon('fa-chevron-left grey');
+ $classes[] = 'phui-formation-view-expander-right';
+ } else {
+ $icon = id(new PHUIIconView())
+ ->setIcon('fa-chevron-right grey');
+ $classes[] = 'phui-formation-view-expander-left';
+ }
+
+ $icon_view = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phui-formation-view-expander-icon',
+ ),
+ $icon);
+
+ return javelin_tag(
+ 'div',
+ array(
+ 'id' => $this->getID(),
+ 'class' => implode(' ', $classes),
+ 'sigil' => 'has-tooltip',
+ 'style' => 'display: none',
+ 'meta' => array(
+ 'tip' => $this->getTooltip(),
+ 'align' => 'E',
+ ),
+ ),
+ $icon_view);
+ }
+
+}
diff --git a/src/view/formation/PHUIFormationFlankView.php b/src/view/formation/PHUIFormationFlankView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationFlankView.php
@@ -0,0 +1,177 @@
+<?php
+
+final class PHUIFormationFlankView
+ extends PHUIFormationColumnDynamicView {
+
+ private $isFixed;
+
+ private $head;
+ private $body;
+ private $tail;
+
+ private $headID;
+ private $bodyID;
+ private $tailID;
+
+ private $headerText;
+
+ public function setIsFixed($fixed) {
+ $this->isFixed = $fixed;
+ return $this;
+ }
+
+ public function getIsFixed() {
+ return $this->isFixed;
+ }
+
+ public function setHead($head) {
+ $this->head = $head;
+ return $this;
+ }
+
+ public function setBody($body) {
+ $this->body = $body;
+ return $this;
+ }
+
+ public function setTail($tail) {
+ $this->tail = $tail;
+ return $this;
+ }
+
+ public function getHeadID() {
+ if (!$this->headID) {
+ $this->headID = celerity_generate_unique_node_id();
+ }
+ return $this->headID;
+ }
+
+ public function getBodyID() {
+ if (!$this->bodyID) {
+ $this->bodyID = celerity_generate_unique_node_id();
+ }
+ return $this->bodyID;
+ }
+
+ public function getTailID() {
+ if (!$this->tailID) {
+ $this->tailID = celerity_generate_unique_node_id();
+ }
+ return $this->tailID;
+ }
+
+ public function setHeaderText($header_text) {
+ $this->headerText = $header_text;
+ return $this;
+ }
+
+ public function getHeaderText() {
+ return $this->headerText;
+ }
+
+ public function newClientProperties() {
+ return array(
+ 'type' => 'flank',
+ 'nodeID' => $this->getID(),
+ 'isFixed' => (bool)$this->getIsFixed(),
+ 'headID' => $this->getHeadID(),
+ 'bodyID' => $this->getBodyID(),
+ 'tailID' => $this->getTailID(),
+ );
+ }
+
+ public function render() {
+ require_celerity_resource('phui-formation-view-css');
+
+ $width = $this->getWidth();
+
+ $style = array();
+ $style[] = sprintf('width: %dpx;', $width);
+
+ $classes = array();
+ $classes[] = 'phui-flank-view';
+
+ if ($this->getIsFixed()) {
+ $classes[] = 'phui-flank-view-fixed';
+ }
+
+ $head_id = $this->getHeadID();
+ $body_id = $this->getBodyID();
+ $tail_id = $this->getTailID();
+
+ $head_content = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phui-flank-header',
+ ),
+ array(
+ phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phui-flank-header-text',
+ ),
+ $this->getHeaderText()),
+ $this->newHideButton(),
+ ));
+
+ $content = phutil_tag(
+ 'div',
+ array(
+ 'id' => $this->getID(),
+ 'class' => implode(' ', $classes),
+ 'style' => implode(' ', $style),
+ ),
+ array(
+ phutil_tag(
+ 'div',
+ array(
+ 'id' => $head_id,
+ 'class' => 'phui-flank-view-head',
+ ),
+ $head_content),
+ phutil_tag(
+ 'div',
+ array(
+ 'id' => $body_id,
+ 'class' => 'phui-flank-view-body',
+ ),
+ $this->getBody()),
+ phutil_tag(
+ 'div',
+ array(
+ 'id' => $tail_id,
+ 'class' => 'phui-flank-view-tail',
+ ),
+ $this->getTail()),
+ ));
+
+ return $content;
+ }
+
+ private function newHideButton() {
+ $item = $this->getColumnItem();
+ $is_right = $item->getIsRightAligned();
+
+ $hide_classes = array();
+ $hide_classes[] = 'phui-flank-header-hide';
+
+ if ($is_right) {
+ $hide_icon = id(new PHUIIconView())
+ ->setIcon('fa-chevron-right grey');
+ $hide_classes[] = 'phui-flank-header-hide-right';
+ } else {
+ $hide_icon = id(new PHUIIconView())
+ ->setIcon('fa-chevron-left grey');
+ $hide_classes[] = 'phui-flank-header-hide-left';
+ }
+
+ return javelin_tag(
+ 'div',
+ array(
+ 'sigil' => 'phui-flank-header-hide',
+ 'class' => implode(' ', $hide_classes),
+ ),
+ $hide_icon);
+ }
+
+}
diff --git a/src/view/formation/PHUIFormationResizerView.php b/src/view/formation/PHUIFormationResizerView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationResizerView.php
@@ -0,0 +1,34 @@
+<?php
+
+final class PHUIFormationResizerView
+ extends PHUIFormationColumnView {
+
+ private $isVisible;
+
+ public function setIsVisible($is_visible) {
+ $this->isVisible = $is_visible;
+ return $this;
+ }
+
+ public function getIsVisible() {
+ return $this->isVisible;
+ }
+
+ public function getWidth() {
+ return 8;
+ }
+
+ public function render() {
+ $width = $this->getWidth();
+ $style = sprintf('width: %dpx;', $width);
+
+ return phutil_tag(
+ 'div',
+ array(
+ 'id' => $this->getID(),
+ 'class' => 'phui-formation-resizer',
+ 'style' => $style,
+ ));
+ }
+
+}
diff --git a/src/view/formation/PHUIFormationView.php b/src/view/formation/PHUIFormationView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationView.php
@@ -0,0 +1,188 @@
+<?php
+
+final class PHUIFormationView
+ extends AphrontView {
+
+ private $items = array();
+
+ public function newFlankColumn() {
+ $item = $this->newItem(new PHUIFormationFlankView());
+ return $item->getColumn();
+ }
+
+ public function newContentColumn() {
+ $item = $this->newItem(new PHUIFormationContentView());
+ return $item->getColumn();
+ }
+
+ private function newItem(PHUIFormationColumnView $column) {
+ $item = id(new PHUIFormationColumnItem())
+ ->setColumn($column);
+
+ $column->setColumnItem($item);
+
+ $this->items[] = $item;
+
+ return $item;
+ }
+
+ public function render() {
+ require_celerity_resource('phui-formation-view-css');
+
+ $items = $this->items;
+
+ $items = $this->generateControlBindings($items);
+ $items = $this->generateExpanders($items);
+ $items = $this->generateResizers($items);
+
+ $cells = array();
+ foreach ($items as $item) {
+ $style = array();
+
+ $column = $item->getColumn();
+
+ $width = $column->getWidth();
+ if ($width !== null) {
+ $style[] = sprintf('width: %dpx;', $width);
+ }
+
+ if (!$column->getIsVisible()) {
+ $style[] = 'display: none;';
+ }
+
+ $cells[] = phutil_tag(
+ 'td',
+ array(
+ 'id' => $item->getID(),
+ 'style' => implode(' ', $style),
+ ),
+ array(
+ $column,
+ $item->getExpanders(),
+ ));
+ }
+
+ $formation_id = celerity_generate_unique_node_id();
+
+ $table_row = phutil_tag('tr', array(), $cells);
+ $table_body = phutil_tag('tbody', array(), $table_row);
+ $table = phutil_tag(
+ 'table',
+ array(
+ 'class' => 'phui-formation-view',
+ 'id' => $formation_id,
+ ),
+ $table_body);
+
+ $phuix_columns = array();
+ foreach ($items as $item) {
+ $phuix_columns[] = $item->newClientProperties();
+ }
+
+ Javelin::initBehavior(
+ 'phuix-formation-view',
+ array(
+ 'nodeID' => $formation_id,
+ 'columns' => $phuix_columns,
+ ));
+
+ return $table;
+ }
+
+ private function newColumnExpanderView() {
+ return new PHUIFormationExpanderView();
+ }
+
+ private function newResizerItem() {
+ return $this->newItem(new PHUIFormationResizerView());
+ }
+
+ private function generateControlBindings(array $items) {
+ $count = count($items);
+
+ if (!$count) {
+ return $items;
+ }
+
+ $last_control = null;
+
+ for ($ii = 0; $ii < $count; $ii++) {
+ $item = $items[$ii];
+ $column = $item->getColumn();
+
+ $is_control = $column->getIsControlColumn();
+ if ($is_control) {
+ $last_control = $ii;
+ }
+ }
+
+ if ($last_control === null) {
+ return $items;
+ }
+
+ for ($ii = ($count - 1); $ii >= 0; $ii--) {
+ $item = $items[$ii];
+ $column = $item->getColumn();
+
+ $is_control = $column->getIsControlColumn();
+ if ($is_control) {
+ $last_control = $ii;
+ continue;
+ }
+
+ $is_right = ($last_control < $ii);
+
+ $item
+ ->setControlItem($items[$last_control])
+ ->setIsRightAligned($is_right);
+ }
+
+ return $items;
+ }
+
+ private function generateResizers(array $items) {
+ $result = array();
+ foreach ($items as $item) {
+ $column = $item->getColumn();
+
+ $resizer_item = null;
+ if ($column->getIsResizable()) {
+ $resizer_item = $this->newResizerItem();
+ $item->setResizerItem($resizer_item);
+
+ $resizer_item
+ ->getColumn()
+ ->setIsVisible($column->getIsVisible());
+ }
+
+ if (!$resizer_item) {
+ $result[] = $item;
+ } else if ($item->getIsRightAligned()) {
+ $result[] = $resizer_item;
+ $result[] = $item;
+ } else {
+ $result[] = $item;
+ $result[] = $resizer_item;
+ }
+ }
+
+ return $result;
+ }
+
+ private function generateExpanders(array $items) {
+ foreach ($items as $item) {
+ $control_item = $item->getControlItem();
+ if ($control_item) {
+ $expander = $this->newColumnExpanderView();
+
+ $expander->setColumnItem($item);
+ $item->setExpander($expander);
+
+ $control_item->appendExpander($expander);
+ }
+ }
+
+ return $items;
+ }
+
+}
diff --git a/webroot/rsrc/css/phui/phui-formation-view.css b/webroot/rsrc/css/phui/phui-formation-view.css
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/css/phui/phui-formation-view.css
@@ -0,0 +1,145 @@
+/**
+ * @provides phui-formation-view-css
+ */
+
+.phui-formation-view {
+ table-layout: fixed;
+ width: 100%;
+}
+
+.phui-formation-view-expander {
+ position: fixed;
+ width: 24px;
+ height: 36px;
+ top: 64px;
+ border-style: solid;
+ box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
+ border-color: {$lightgreyborder};
+ background: {$lightgreybackground};
+ z-index: 4;
+}
+
+.phui-formation-view-expander-left {
+ border-radius: 0 12px 12px 0;
+ border-width: 1px 1px 1px 0;
+ cursor: e-resize;
+}
+
+.phui-formation-view-expander-right {
+ border-radius: 12px 0 0 12px;
+ border-width: 1px 0 1px 1px;
+ cursor: w-resize;
+}
+
+.phui-formation-view-expander-icon {
+ position: absolute;
+ width: 18px;
+ height: 18px;
+ top: 9px;
+ left: 3px;
+ text-align: center;
+}
+
+.device-desktop .phui-formation-view-expander:hover {
+ box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1);
+ background: {$darkgreybackground};
+ transition: 0.1s;
+}
+
+.device-desktop .phui-formation-view-expander:hover
+ .phui-icon-view {
+ color: {$bluetext};
+ transition: 0.1s;
+}
+
+.phui-flank-header {
+ padding: 8px;
+ background: {$greybackground};
+ border-bottom: 1px solid {$lightgreyborder};
+}
+
+.phui-flank-header-text {
+ color: {$darkgreytext};
+ font-weight: bold;
+}
+
+.phui-flank-header-hide {
+ font-size: {$normalfontsize};
+ position: absolute;
+ display: inline-block;
+ top: 6px;
+ right: 6px;
+ width: 20px;
+ height: 20px;
+ text-align: center;
+ border: 1px solid {$lightgreyborder};
+ border-radius: 4px;
+ line-height: 20px;
+}
+
+.phui-flank-header-hide-left {
+ cursor: w-resize;
+}
+
+
+.device-desktop .phui-flank-header-hide:hover {
+ box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.05);
+ background: {$darkgreybackground};
+ transition: 0.1s;
+}
+
+.device-desktop .phui-flank-header-hide:hover
+ .phui-icon-view {
+ color: {$bluetext};
+ transition: 0.1s;
+}
+
+.phui-formation-resizer {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+
+ cursor: col-resize;
+ background: #f5f5f5;
+ border-style: solid;
+ border-width: 0 1px 0 1px;
+ border-color: #fff #999c9e #fff #999c9e;
+ box-sizing: border-box;
+
+ box-shadow: inset -1px 0px 1px rgba({$alphablack}, 0.15);
+
+ background-image: url(/rsrc/image/divot.png);
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+.phui-flank-view-fixed {
+ position: fixed;
+ top: {$menu.main.height};
+ bottom: 0;
+ overflow: hidden;
+}
+
+.phui-flank-view-fixed .phui-flank-view-body {
+ overflow: hidden auto;
+}
+
+.device-desktop .phui-flank-view-fixed
+ .phui-flank-view-body::-webkit-scrollbar {
+ height: 6px;
+ width: 6px;
+ background: rgba(0, 0, 0, 0.1);
+ border-radius: 4px;
+}
+
+.device-desktop .phui-flank-view-fixed
+ .phui-flank-view-body::-webkit-scrollbar-thumb {
+ background: rgba(0, 0, 0, 0.25);
+ border-radius: 4px;
+}
+
+.phui-flank-view-fixed .phui-flank-view-tail {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+}
diff --git a/webroot/rsrc/js/phui/behavior-phuix-formation-view.js b/webroot/rsrc/js/phui/behavior-phuix-formation-view.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phui/behavior-phuix-formation-view.js
@@ -0,0 +1,54 @@
+/**
+ * @provides javelin-behavior-phuix-formation-view
+ * @requires javelin-behavior
+ * phuix-formation-view
+ * phuix-formation-column-view
+ * phuix-formation-flank-view
+ */
+
+JX.behavior('phuix-formation-view', function(config) {
+
+ var formation_node = JX.$(config.nodeID);
+ var formation = new JX.PHUIXFormationView(formation_node);
+
+ var count = config.columns.length;
+ for (var ii = 0; ii < count; ii++) {
+ var spec = config.columns[ii];
+ var node = JX.$(spec.itemID);
+
+ var column = new JX.PHUIXFormationColumnView(node)
+ .setIsRightAligned(spec.isRightAligned)
+ .setWidth(spec.width)
+ .setIsVisible(spec.isVisible);
+
+ if (spec.expanderID) {
+ column.setExpanderNode(JX.$(spec.expanderID));
+ }
+
+ if (spec.resizer) {
+ column
+ .setResizerItem(JX.$(spec.resizer.itemID))
+ .setResizerControl(JX.$(spec.resizer.controlID));
+ }
+
+ var colspec = spec.column;
+ if (colspec) {
+ if (colspec.type === 'flank') {
+ var flank_node = JX.$(colspec.nodeID);
+
+ var head = JX.$(colspec.headID);
+ var body = JX.$(colspec.bodyID);
+ var tail = JX.$(colspec.tailID);
+
+ var flank = new JX.PHUIXFormationFlankView(flank_node, head, body, tail)
+ .setIsFixed(colspec.isFixed);
+
+ column.setFlank(flank);
+ }
+ }
+
+ formation.addColumn(column);
+ }
+
+ formation.start();
+});
diff --git a/webroot/rsrc/js/phuix/PHUIXFormationColumnView.js b/webroot/rsrc/js/phuix/PHUIXFormationColumnView.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phuix/PHUIXFormationColumnView.js
@@ -0,0 +1,174 @@
+/**
+ * @provides phuix-formation-column-view
+ * @requires javelin-install
+ * javelin-dom
+ */
+
+JX.install('PHUIXFormationColumnView', {
+
+ construct: function(node) {
+ this._node = node;
+ },
+
+ properties: {
+ isRightAligned: false,
+ isVisible: true,
+ expanderNode: null,
+ resizerItem: null,
+ resizerControl: null,
+ width: null,
+ flank: null
+ },
+
+ members: {
+ _node: null,
+ _resizingWidth: null,
+ _resizingBarPosition: null,
+ _dragging: null,
+
+ start: function() {
+ var onshow = JX.bind(this, this._setVisibility, true);
+ var onhide = JX.bind(this, this._setVisibility, false);
+
+ JX.DOM.listen(this._node, 'click', 'phui-flank-header-hide', onhide);
+
+ var expander = this.getExpanderNode();
+ if (expander) {
+ JX.DOM.listen(expander, 'click', null, onshow);
+ }
+
+ var resizer = this.getResizerItem();
+ if (resizer) {
+ var ondown = JX.bind(this, this._onresizestart);
+ JX.DOM.listen(resizer, 'mousedown', null, ondown);
+
+ var onmove = JX.bind(this, this._onresizemove);
+ JX.Stratcom.listen('mousemove', null, onmove);
+
+ var onup = JX.bind(this, this._onresizeend);
+ JX.Stratcom.listen('mouseup', null, onup);
+ }
+
+ this.repaint();
+ },
+
+ _onresizestart: function(e) {
+ if (!e.isNormalMouseEvent()) {
+ return;
+ }
+
+ this._dragging = JX.$V(e);
+ this._resizingWidth = this.getWidth();
+ this._resizingBarPosition = JX.$V(this.getResizerControl());
+
+ // Show the "col-resize" cursor on the whole document while we're
+ // dragging, since the mouse will slip off the actual bar fairly often
+ // and we don't want it to flicker.
+ JX.DOM.alterClass(document.body, 'jx-drag-col', true);
+
+ e.kill();
+ },
+
+ _onresizemove: function(e) {
+ if (!this._dragging) {
+ return;
+ }
+
+ var dx = (JX.$V(e).x - this._dragging.x);
+
+ var width;
+ if (this.getIsRightAligned()) {
+ width = this.getWidth() - dx;
+ } else {
+ width = this.getWidth() + dx;
+ }
+
+ // TODO: Make these configurable?
+ width = Math.max(width, 150);
+ width = Math.min(width, 512);
+
+ this._resizingWidth = width;
+
+ this._node.style.width = this._resizingWidth + 'px';
+
+ var adjust_x = (this._resizingWidth - this.getWidth());
+ if (this.getIsRightAligned()) {
+ adjust_x = -adjust_x;
+ }
+
+ this.getResizerControl().style.left =
+ (this._resizingBarPosition.x + adjust_x) + 'px';
+
+ var flank = this.getFlank();
+ if (flank) {
+ flank
+ .setWidth(this._resizingWidth)
+ .repaint();
+ }
+ },
+
+ _onresizeend: function(e) {
+ if (!this._dragging) {
+ return;
+ }
+
+ this.setWidth(this._resizingWidth);
+
+ JX.log('new width is ' + this.getWidth());
+
+ JX.DOM.alterClass(document.body, 'jx-drag-col', false);
+ this._dragging = null;
+
+ // TODO: Save new width setting.
+
+ // new JX.Request('/settings/adjust/', JX.bag)
+ // .setData(
+ // {
+ // key: 'filetree.width',
+ // value: get_width()
+ // })
+ // .send();
+
+ },
+
+ _setVisibility: function(visible, e) {
+ e.kill();
+
+ // TODO: Save the visibility setting.
+
+ this.setIsVisible(visible);
+ this.repaint();
+ },
+
+ repaint: function() {
+ var resizer = this.getResizerItem();
+ var expander = this.getExpanderNode();
+
+ if (this.getIsVisible()) {
+ JX.DOM.show(this._node);
+ if (resizer) {
+ JX.DOM.show(resizer);
+ }
+ if (expander) {
+ JX.DOM.hide(expander);
+ }
+ } else {
+ JX.DOM.hide(this._node);
+ if (resizer) {
+ JX.DOM.hide(resizer);
+ }
+ if (expander) {
+ JX.DOM.show(expander);
+ }
+ }
+
+ if (this.getFlank()) {
+ this.getFlank().repaint();
+ }
+
+ },
+
+
+ }
+
+});
diff --git a/webroot/rsrc/js/phuix/PHUIXFormationFlankView.js b/webroot/rsrc/js/phuix/PHUIXFormationFlankView.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phuix/PHUIXFormationFlankView.js
@@ -0,0 +1,57 @@
+/**
+ * @provides phuix-formation-flank-view
+ * @requires javelin-install
+ * javelin-dom
+ */
+
+JX.install('PHUIXFormationFlankView', {
+
+ construct: function(node, head, body, tail) {
+ this._node = node;
+
+ this._headNode = head;
+ this._bodyNode = body;
+ this._tailNode = tail;
+ },
+
+ properties: {
+ isFixed: false,
+ bannerHeight: null,
+ width: null
+ },
+
+ members: {
+ _node: null,
+ _headNode: null,
+ _bodyNode: null,
+ _tailNode: null,
+
+ getBodyNode: function() {
+ return this._bodyNode;
+ },
+
+ getTailNode: function() {
+ return this._tailNode;
+ },
+
+ repaint: function() {
+ if (!this.getIsFixed()) {
+ return;
+ }
+
+ this._node.style.top = this.getBannerHeight() + 'px';
+ this._node.style.width = this.getWidth() + 'px';
+
+ var body = this.getBodyNode();
+ var body_pos = JX.$V(body);
+
+ var tail = this.getTailNode();
+ var tail_pos = JX.$V(tail);
+
+ var max_height = (tail_pos.y - body_pos.y);
+
+ body.style.maxHeight = max_height + 'px';
+ }
+ }
+
+});
diff --git a/webroot/rsrc/js/phuix/PHUIXFormationView.js b/webroot/rsrc/js/phuix/PHUIXFormationView.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phuix/PHUIXFormationView.js
@@ -0,0 +1,68 @@
+/**
+ * @provides phuix-formation-view
+ * @requires javelin-install
+ * javelin-dom
+ */
+
+JX.install('PHUIXFormationView', {
+
+ construct: function() {
+ this._columns = [];
+ },
+
+ members: {
+ _columns: null,
+
+ addColumn: function(column) {
+ this._columns.push(column);
+ },
+
+ start: function() {
+ JX.enableDispatch(document.body, 'mousemove');
+
+ for (var ii = 0; ii < this._columns.length; ii++) {
+ this._columns[ii].start();
+ }
+
+ var repaint = JX.bind(this, this.repaint);
+ JX.Stratcom.listen(['scroll', 'resize'], null, repaint);
+
+ this.repaint();
+ },
+
+ repaint: function(e) {
+ // Unless we've scrolled past it, the page has a 44px main menu banner.
+ var menu_height = (44 - JX.Vector.getScroll().y);
+
+ // When the buoyant header is visible, move the menu down below it. This
+ // is a bit of a hack.
+ var banner_height = 0;
+ try {
+ var banner = JX.$('diff-banner');
+ banner_height = JX.Vector.getDim(banner).y;
+ } catch (error) {
+ // Ignore if there's no banner on the page.
+ }
+
+ var header_height = Math.max(0, menu_height, banner_height);
+
+ var column;
+ var flank;
+ for (var ii = 0; ii < this._columns.length; ii++) {
+ column = this._columns[ii];
+
+ flank = column.getFlank();
+ if (!flank) {
+ continue;
+ }
+
+ flank
+ .setBannerHeight(header_height)
+ .setWidth(column.getWidth())
+ .repaint();
+ }
+ }
+
+ }
+
+});

File Metadata

Mime Type
text/plain
Expires
Sat, Mar 22, 1:48 AM (2 d, 9 h ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/7f/7l/rseeafqlb3clk5hr
Default Alt Text
D21150.diff (35 KB)

Event Timeline