1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 00:32:42 +01:00

Add a "FormationView" to support dynamic flank panels

Summary:
Ref T13516. Currently, the "File Tree" element is a semi-dynamic side panel that's implemented as a special mode of a side nav panel.

This implementation is fairly clunky, and arose from organic growth out of the side nav. As such, it has some weird behaviors, doesn't have builtin support for show/hide, and can't generalize easily.

Introduce a "FormationView" which supports loading a page up with piles of side panels in various modes.

Test Plan: No callers and no user-visible impact.

Maniphest Tasks: T13516

Differential Revision: https://secure.phabricator.com/D21150
This commit is contained in:
epriestley 2020-04-20 06:51:27 -07:00
parent ef69c7969f
commit fef2cdabfe
16 changed files with 1233 additions and 0 deletions

View file

@ -155,6 +155,7 @@ return array(
'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 @@ return array(
'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 @@ return array(
'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 @@ return array(
'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 @@ return array(
'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 @@ return array(
'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 @@ return array(
'unhandled-exception-css' => '9ecfc00d',
),
'requires' => array(
'0113c54c' => array(
'javelin-install',
'javelin-dom',
),
'0116d3e8' => array(
'javelin-behavior',
'javelin-dom',
@ -984,6 +998,10 @@ return array(
'javelin-util',
'javelin-magical-init',
),
'08fc09e9' => array(
'javelin-install',
'javelin-dom',
),
'0922e81d' => array(
'herald-rule-editor',
'javelin-behavior',
@ -1036,6 +1054,12 @@ return array(
'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 @@ return array(
'javelin-stratcom',
'javelin-dom',
),
'6648270a' => array(
'javelin-install',
'javelin-dom',
),
'6a1583a8' => array(
'javelin-behavior',
'javelin-history',

View file

@ -179,6 +179,7 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'AphrontAccessDeniedQueryException' => 'AphrontQueryException',
'AphrontAjaxResponse' => 'AphrontResponse',
'AphrontApplicationConfiguration' => 'Phobject',
'AphrontAutoIDView' => 'AphrontView',
'AphrontBarView' => 'AphrontView',
'AphrontBaseMySQLDatabaseConnection' => 'AphrontDatabaseConnection',
'AphrontBoolHTTPParameterType' => 'AphrontHTTPParameterType',
@ -8315,6 +8325,14 @@ phutil_register_library_map(array(
'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',

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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,
);
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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,
));
}
}

View file

@ -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;
}
}

View file

@ -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%;
}

View file

@ -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();
});

View file

@ -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();
}
},
}
});

View file

@ -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';
}
}
});

View file

@ -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();
}
}
}
});