diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => 'a419cf4b', - 'core.pkg.js' => 'cf262309', + 'core.pkg.js' => 'ac1e2574', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', 'differential.pkg.js' => '64e69521', @@ -152,8 +152,8 @@ 'rsrc/css/phui/phui-text.css' => 'cf019f54', 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', 'rsrc/css/phui/phui-two-column-view.css' => '39ecafb1', - 'rsrc/css/phui/phui-workboard-view.css' => '24fe2a66', - 'rsrc/css/phui/phui-workpanel-view.css' => 'adec7699', + 'rsrc/css/phui/phui-workboard-view.css' => '5e3b37a3', + 'rsrc/css/phui/phui-workpanel-view.css' => 'bf9ff43b', 'rsrc/css/sprite-login.css' => '60e8560e', 'rsrc/css/sprite-main-header.css' => 'f07bbb87', 'rsrc/css/sprite-menu.css' => '9dd65b92', @@ -418,7 +418,8 @@ 'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef', 'rsrc/js/application/policy/behavior-policy-control.js' => 'ae45872f', 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', - 'rsrc/js/application/projects/behavior-project-boards.js' => 'ba4fa35c', + 'rsrc/js/application/projects/behavior-phantom-headers.js' => '655dc685', + 'rsrc/js/application/projects/behavior-project-boards.js' => 'ee2be7b3', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', 'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb', 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', @@ -450,7 +451,7 @@ 'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5', 'rsrc/js/core/Busy.js' => '59a7976a', 'rsrc/js/core/DragAndDropFileUpload.js' => 'ad10aeac', - 'rsrc/js/core/DraggableList.js' => 'a16ec1c6', + 'rsrc/js/core/DraggableList.js' => '831e790c', 'rsrc/js/core/FileUpload.js' => '477359c8', 'rsrc/js/core/Hovercard.js' => '14ac66f5', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', @@ -654,7 +655,7 @@ 'javelin-behavior-phui-object-box-tabs' => '2bfa2836', 'javelin-behavior-policy-control' => 'ae45872f', 'javelin-behavior-policy-rule-editor' => '5e9f347c', - 'javelin-behavior-project-boards' => 'ba4fa35c', + 'javelin-behavior-project-boards' => 'ee2be7b3', 'javelin-behavior-project-create' => '065227cc', 'javelin-behavior-quicksand-blacklist' => '7927a7d3', 'javelin-behavior-recurring-edit' => '5f1c4d5f', @@ -677,6 +678,7 @@ 'javelin-behavior-typeahead-browse' => '635de1ec', 'javelin-behavior-typeahead-search' => '93d0c9e3', 'javelin-behavior-view-placeholder' => '47830651', + 'javelin-behavior-workboard-phantom-headers' => '655dc685', 'javelin-behavior-workflow' => '0a3f3021', 'javelin-color' => '7e41274a', 'javelin-cookie' => '62dfea03', @@ -741,7 +743,7 @@ 'phabricator-countdown-css' => 'e7544472', 'phabricator-dashboard-css' => 'eb458607', 'phabricator-drag-and-drop-file-upload' => 'ad10aeac', - 'phabricator-draggable-list' => 'a16ec1c6', + 'phabricator-draggable-list' => '831e790c', 'phabricator-fatal-config-template-css' => '8e6c6fcd', 'phabricator-feed-css' => 'ecd4ec57', 'phabricator-file-upload' => '477359c8', @@ -828,8 +830,8 @@ 'phui-theme-css' => '6b451f24', 'phui-timeline-view-css' => '2efceff8', 'phui-two-column-view-css' => '39ecafb1', - 'phui-workboard-view-css' => '24fe2a66', - 'phui-workpanel-view-css' => 'adec7699', + 'phui-workboard-view-css' => '5e3b37a3', + 'phui-workpanel-view-css' => 'bf9ff43b', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '8cf6d262', 'phuix-dropdown-menu' => 'bd4c8dca', @@ -1288,6 +1290,12 @@ 'javelin-request', 'javelin-workflow', ), + '655dc685' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-workflow', + ), '65ef6074' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1426,6 +1434,14 @@ 'javelin-workflow', 'phabricator-draggable-list', ), + '831e790c' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + 'javelin-vector', + 'javelin-magical-init', + ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -1576,14 +1592,6 @@ 'javelin-dom', 'javelin-reactor-dom', ), - 'a16ec1c6' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - 'javelin-vector', - 'javelin-magical-init', - ), 'a205cf28' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1740,15 +1748,6 @@ 'b6b0d1bb' => array( 'phui-inline-comment-view-css', ), - 'ba4fa35c' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', - ), 'bd4c8dca' => array( 'javelin-install', 'javelin-util', @@ -1973,6 +1972,15 @@ 'javelin-behavior', 'javelin-uri', ), + 'ee2be7b3' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', + ), 'efe49472' => array( 'javelin-install', 'javelin-util', diff --git a/src/view/phui/PHUIWorkboardView.php b/src/view/phui/PHUIWorkboardView.php --- a/src/view/phui/PHUIWorkboardView.php +++ b/src/view/phui/PHUIWorkboardView.php @@ -19,19 +19,78 @@ protected function getTagContent() { require_celerity_resource('phui-workboard-view-css'); - $view = new AphrontMultiColumnView(); - $view->setGutter(AphrontMultiColumnView::GUTTER_MEDIUM); + $columns = array(); + $headers = array(); + $header_map = array(); foreach ($this->panels as $panel) { - $view->addColumn($panel); + $target = phutil_tag( + 'div', + array( + 'class' => 'phui-workboard-target', + ), + $panel); + + $column = javelin_tag( + 'div', + array( + 'class' => 'phui-workboard-column', + 'sigil' => 'phui-workboard-column', + ), + $target); + + $columns[] = $column; + + $headers[] = phutil_tag( + 'div', + array( + 'class' => 'phui-workboard-header', + 'id' => $panel->getDesktopID(), + ), + null); + + $header_map[] = array( + 'headerID' => $panel->getHeaderID(), + 'desktopID' => $panel->getDesktopID(), + 'deviceID' => $panel->getDeviceID(), + ); } + Javelin::initBehavior( + 'workboard-phantom-headers', + array( + 'map' => $header_map, + )); + + $table = javelin_tag( + 'div', + array( + 'class' => 'phui-workboard-view-table', + 'sigil' => 'phui-workboard-view-table', + ), + $columns); + $board = phutil_tag( 'div', - array( - 'class' => 'phui-workboard-view-shadow', - ), - $view); + array( + 'class' => 'phui-workboard-view-board', + ), + $table); + + $headers = phutil_tag( + 'div', + array( + 'class' => 'phui-workboard-view-headers', + ), + $headers); - return $board; + return phutil_tag( + 'div', + array( + 'class' => 'phui-workboard-view-shadow', + ), + array( + $headers, + $board, + )); } } diff --git a/src/view/phui/PHUIWorkpanelView.php b/src/view/phui/PHUIWorkpanelView.php --- a/src/view/phui/PHUIWorkpanelView.php +++ b/src/view/phui/PHUIWorkpanelView.php @@ -10,6 +10,10 @@ private $headerTag; private $headerIcon; + private $headerID; + private $desktopID; + private $deviceID; + public function setHeaderIcon(PHUIIconView $header_icon) { $this->headerIcon = $header_icon; return $this; @@ -55,6 +59,27 @@ ); } + public function getDeviceID() { + if (!$this->deviceID) { + $this->deviceID = celerity_generate_unique_node_id(); + } + return $this->deviceID; + } + + public function getDesktopID() { + if (!$this->desktopID) { + $this->desktopID = celerity_generate_unique_node_id(); + } + return $this->desktopID; + } + + public function getHeaderID() { + if (!$this->headerID) { + $this->headerID = celerity_generate_unique_node_id(); + } + return $this->headerID; + } + protected function getTagContent() { require_celerity_resource('phui-workpanel-view-css'); @@ -87,20 +112,30 @@ $header->addActionIcon($action); } + $header->setID($this->getHeaderID()); + $body = phutil_tag( 'div', - array( - 'class' => 'phui-workpanel-body', - ), + array( + 'class' => 'phui-workpanel-body', + ), $this->cards); + $header_slot = phutil_tag( + 'div', + array( + 'class' => 'phui-workpanel-header-slot', + 'id' => $this->getDeviceID(), + ), + $header); + $view = phutil_tag( 'div', array( 'class' => implode(' ', $classes), ), array( - $header, + $header_slot, $body, $footer, )); diff --git a/webroot/rsrc/css/phui/phui-workboard-view.css b/webroot/rsrc/css/phui/phui-workboard-view.css --- a/webroot/rsrc/css/phui/phui-workboard-view.css +++ b/webroot/rsrc/css/phui/phui-workboard-view.css @@ -11,8 +11,37 @@ position: absolute; top: 96px; bottom: 0; - left: 0; right: 0; + left: 0; + padding-left: 53px; + border-spacing: 4px 0; + background-color: {$lightgreybackground}; +} + +.device-desktop .phui-workboard-view-headers, +.device-desktop .phui-workboard-view-table { + display: table; +} + +.device-desktop .phui-workboard-view-table { + height: 100%; +} + +.device .phui-workboard-view-headers { + display: none; +} + +.device-desktop .phui-workboard-view-board { + overflow-y: auto; + overflow-x: hidden; + top: 48px; + bottom: 0; + left: 53px; + position: absolute; +} + +.device-desktop .phui-workboard-view-board .phui-header-shell { + display: none; } .device-desktop .page-has-warning .phui-workboard-view-shadow { @@ -27,30 +56,47 @@ right: 312px; } -.phui-workboard-view-shadow::-webkit-scrollbar { +.phui-workboard-view-shadow::-webkit-scrollbar, +.device-desktop .phui-workboard-view-board::-webkit-scrollbar { height: 12px; width: 12px; background: rgba(200,200,200,.6); } -.phui-workboard-view-shadow::-webkit-scrollbar-thumb { +.phui-workboard-view-shadow::-webkit-scrollbar-thumb, +.device-desktop .phui-workboard-view-board::-webkit-scrollbar-thumb { background: {$lightbluetext}; } -.device-desktop .project-board-wrapper .phui-workboard-view-shadow { - left: 53px; +.device-tablet .project-board-wrapper { + margin-left: 8px; + margin-right: 8px; } -.device-desktop .phui-workboard-view .aphront-multi-column-fixed - .aphront-multi-column-inner { - margin-left: 0; +.device-desktop .phui-workboard-header, +.device-desktop .phui-workboard-column { + display: table-cell; + padding: 0 4px; } -.device-tablet .project-board-wrapper { - margin-left: 8px; - margin-right: 8px; +.device-desktop .phui-workboard-column { + vertical-align: top; + background: {$greybackground}; } +.device-desktop .phui-workboard-column.is-drop-target { + background: {$darkgreybackground}; +} + +.phui-workboard-target { + border-spacing: 0; +} + +.device-desktop .phui-workboard-target .phui-workpanel-body { + width: 300px; +} + + .project-board-header { background-color: #fff; border-bottom: 1px solid {$lightblueborder}; diff --git a/webroot/rsrc/css/phui/phui-workpanel-view.css b/webroot/rsrc/css/phui/phui-workpanel-view.css --- a/webroot/rsrc/css/phui/phui-workpanel-view.css +++ b/webroot/rsrc/css/phui/phui-workpanel-view.css @@ -2,20 +2,23 @@ * @provides phui-workpanel-view-css */ -.phui-workpanel-view .phui-header-shell { +.phui-workboard-view .phui-header-shell { padding: 16px 0 8px; border-color: {$lightgreyborder}; - width: 300px; background-color: transparent; } -.phui-workpanel-view .phui-header-shell .phui-header-header { +.device-desktop .phui-workboard-view .phui-header-shell { + width: 300px; +} + +.phui-workboard-view .phui-header-shell .phui-header-header { font-size: {$biggerfontsize}; line-height: 18px; color: {$darkbluetext}; } -.phui-workpanel-view .phui-header-shell .phui-header-subheader { +.phui-workboard-view .phui-header-shell .phui-header-subheader { padding: 0 4px; margin: 0; display: inline-block; @@ -62,7 +65,7 @@ } .phui-workpanel-view .phui-workpanel-body { - padding-top: 8px; + padding-top: 4px; } .phui-workpanel-view .phui-workpanel-footer-action a { @@ -108,21 +111,6 @@ opacity: 0.75; } -.project-panel-empty .phui-object-item-list-view { - background: {$sh-indigobackground}; - border-radius: 3px; - margin-bottom: 4px; - border: 1px dashed {$sh-indigoborder}; -} - -.project-panel-empty .phui-object-item-list-view .drag-ghost { - display: none; -} - -.project-panel-empty .phui-object-item-list-view.drag-target-list { - background: rgba(255,255,255,.7); -} - .phui-workpanel-view.project-panel-over-limit .phui-header-header { color: {$red}; } diff --git a/webroot/rsrc/js/application/projects/behavior-phantom-headers.js b/webroot/rsrc/js/application/projects/behavior-phantom-headers.js new file mode 100644 --- /dev/null +++ b/webroot/rsrc/js/application/projects/behavior-phantom-headers.js @@ -0,0 +1,40 @@ +/** + * @provides javelin-behavior-workboard-phantom-headers + * @requires javelin-behavior + * javelin-dom + * javelin-stratcom + * javelin-workflow + */ + +JX.behavior('workboard-phantom-headers', function(config) { + + // Move workboard headers out of the card columns on desktop and back in to + // the columns on devices. This allows them to stick to the top of the screen + // on desktops. + + function move_headers() { + var is_device = (JX.Device.getDevice() != 'desktop'); + + var header; + var target; + var map = config.map; + var item; + for (var ii = 0; ii < map.length; ii++) { + item = map[ii]; + + header = JX.$(item.headerID); + + if (is_device) { + target = JX.$(item.deviceID); + } else { + target = JX.$(item.desktopID); + } + + target.appendChild(header); + } + } + + JX.Stratcom.listen('phabricator-device-change', null, move_headers); + + move_headers(); +}); diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js --- a/webroot/rsrc/js/application/projects/behavior-project-boards.js +++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js @@ -44,7 +44,6 @@ var panel_map = { - 'project-panel-empty': !cards.length, 'project-panel-over-limit': over_limit }; var panel = JX.DOM.findAbove(col, 'div', 'workpanel'); @@ -66,6 +65,8 @@ list.unlock(); JX.DOM.alterClass(item, 'drag-sending', false); JX.DOM.replace(item, JX.$H(response.task)); + + resize_columns(); } function getcolumns() { @@ -93,7 +94,7 @@ return JX.DOM.find( JX.$(statics.boardID), 'div', - 'aphront-multi-column-view'); + 'phui-workboard-view-table'); } function onbegindrag(item) { @@ -172,6 +173,8 @@ }); workflow.start(); + + resize_columns(); } function onedit(column, r) { @@ -224,6 +227,16 @@ statics.createURI = update_config.createURI; } + function hittarget(col) { + var column = JX.DOM.findAbove(col, 'div', 'phui-workboard-column'); + JX.DOM.alterClass(column, 'is-drop-target', true); + } + + function misstarget(col) { + var column = JX.DOM.findAbove(col, 'div', 'phui-workboard-column'); + JX.DOM.alterClass(column, 'is-drop-target', false); + } + function init_board() { var lists = []; var ii; @@ -231,7 +244,9 @@ for (ii = 0; ii < cols.length; ii++) { var list = new JX.DraggableList('project-card', cols[ii]) - .setFindItemsHandler(JX.bind(null, finditems, cols[ii])); + .setFindItemsHandler(JX.bind(null, finditems, cols[ii])) + .setHitTargetHandler(JX.bind(null, hittarget, cols[ii])) + .setMissTargetHandler(JX.bind(null, misstarget, cols[ii])); list.listen('didSend', JX.bind(list, onupdate, cols[ii])); list.listen('didReceive', JX.bind(list, onupdate, cols[ii])); @@ -249,6 +264,30 @@ for (ii = 0; ii < lists.length; ii++) { lists[ii].setGroup(lists); } + + JX.Stratcom.listen('phabricator-device-change', null, resize_columns); + resize_columns(); + } + + function resize_columns() { + var columns = getcolumns(); + + for (var ii = 0; ii < columns.length; ii++) { + columns[ii].style.minHeight = null; + } + + if (JX.Device.getDevice() != 'desktop') { + return; + } + + var max = 54; + for (ii = 0; ii < columns.length; ii++) { + max = Math.max(max, JX.Vector.getDim(columns[ii]).y); + } + + for (ii = 0; ii < columns.length; ii++) { + columns[ii].style.minHeight = max + 'px'; + } } function setup() { diff --git a/webroot/rsrc/js/core/DraggableList.js b/webroot/rsrc/js/core/DraggableList.js --- a/webroot/rsrc/js/core/DraggableList.js +++ b/webroot/rsrc/js/core/DraggableList.js @@ -37,7 +37,9 @@ 'didReceive'], properties : { - findItemsHandler : null + findItemsHandler: null, + hitTargetHandler: null, + missTargetHandler: null }, members : { @@ -272,6 +274,9 @@ // If the handler returns explicit `false`, prevent the drag. if (ok === false) { cur_target = false; + } else { + var handler = this.getHitTargetHandler(); + handler && handler(); } } @@ -289,7 +294,11 @@ JX.DOM.remove(ghost); } + var handler = this.getMissTargetHandler(); + handler && handler(); + this._target = false; + return this; },